123 lines
5.4 KiB
C#
123 lines
5.4 KiB
C#
using Bitget_Server.Api.Dtos;
|
|
using Bitget_Server.Application.Services;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Bitget_Server.Api.Controllers;
|
|
|
|
/// <summary>
|
|
/// 기존 WinForms 의 주문/취소/잔고 버튼을 REST 화.
|
|
/// Bitget Private API 를 호출하므로 반드시 선택된 API 키가 설정되어 있어야 한다.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/trading")]
|
|
[Produces("application/json")]
|
|
public class TradingController : ControllerBase
|
|
{
|
|
private readonly BitgetTradingService _trading;
|
|
private readonly ILogger<TradingController> _logger;
|
|
|
|
public TradingController(BitgetTradingService trading, ILogger<TradingController> logger)
|
|
{
|
|
_trading = trading;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>Bitget 서버 타임스탬프 조회 (헬스체크 용도로도 활용).</summary>
|
|
[HttpGet("timestamp")]
|
|
[ProducesResponseType(typeof(long), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<long>> GetTimestamp(CancellationToken ct)
|
|
=> Ok(await _trading.GetTimeStampAsync(ct));
|
|
|
|
/// <summary>선물 계좌 잔고/포지션 조회.</summary>
|
|
[HttpGet("account")]
|
|
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> GetAccount(CancellationToken ct)
|
|
{
|
|
var result = await _trading.GetFutureAccountListAsync(ct);
|
|
if (result == null)
|
|
return Problem(detail: "Bitget API 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Account fetch failed");
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>미체결 주문 조회.</summary>
|
|
[HttpGet("orders/pending")]
|
|
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> GetPendingOrders(CancellationToken ct)
|
|
{
|
|
var result = await _trading.GetPendingOrdersAsync(ct);
|
|
if (result == null)
|
|
return Problem(detail: "Bitget API 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Pending orders fetch failed");
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>주문 상세 조회.</summary>
|
|
[HttpGet("orders/{symbol}/{orderId}")]
|
|
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> GetOrderDetail(string symbol, string orderId, CancellationToken ct)
|
|
{
|
|
var result = await _trading.GetFutureOrderDetailAsync(symbol, orderId, ct);
|
|
if (result == null)
|
|
return Problem(detail: "주문 상세 응답 없음", statusCode: StatusCodes.Status404NotFound, title: "Order detail not found");
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>신규 주문.</summary>
|
|
[HttpPost("orders")]
|
|
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> PlaceOrder([FromBody] PlaceOrderRequest req, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var result = await _trading.FutureOrderAsync(
|
|
req.Symbol,
|
|
req.MarginMode,
|
|
req.Size,
|
|
req.Price,
|
|
req.Side,
|
|
req.OrderType,
|
|
req.Leverage,
|
|
req.ClientOrderId ?? string.Empty,
|
|
ct);
|
|
|
|
if (result == null)
|
|
return Problem(detail: "주문 실패 (MarginMode/Leverage/Order 체인 중 단계 실패)",
|
|
statusCode: StatusCodes.Status502BadGateway, title: "Order failed");
|
|
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "주문 처리 실패: {Symbol}", req.Symbol);
|
|
return Problem(detail: ex.Message, statusCode: StatusCodes.Status500InternalServerError, title: "Order processing error");
|
|
}
|
|
}
|
|
|
|
/// <summary>주문 취소.</summary>
|
|
[HttpDelete("orders")]
|
|
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> CancelOrder([FromBody] CancelOrderRequest req, CancellationToken ct)
|
|
{
|
|
var result = await _trading.CancelFutureOrderAsync(req.Symbol, req.OrderId, ct);
|
|
if (result == null)
|
|
return Problem(detail: "취소 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Cancel failed");
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>포지션 모드 변경(hedge / one_way).</summary>
|
|
[HttpPut("position-mode")]
|
|
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> ChangePositionMode([FromBody] ChangePositionModeRequest req, CancellationToken ct)
|
|
{
|
|
var result = await _trading.ChangeFuturePositionModeAsync(req.Mode, ct);
|
|
if (result == null)
|
|
return Problem(detail: "포지션 모드 변경 실패", statusCode: StatusCodes.Status502BadGateway, title: "Position mode change failed");
|
|
return Ok(result);
|
|
}
|
|
}
|