using Bitget_Server.Api.Dtos;
using Bitget_Server.Application.Services;
using Microsoft.AspNetCore.Mvc;
namespace Bitget_Server.Api.Controllers;
///
/// 기존 WinForms 의 주문/취소/잔고 버튼을 REST 화.
/// Bitget Private API 를 호출하므로 반드시 선택된 API 키가 설정되어 있어야 한다.
///
[ApiController]
[Route("api/trading")]
[Produces("application/json")]
public class TradingController : ControllerBase
{
private readonly BitgetTradingService _trading;
private readonly ILogger _logger;
public TradingController(BitgetTradingService trading, ILogger logger)
{
_trading = trading;
_logger = logger;
}
/// Bitget 서버 타임스탬프 조회 (헬스체크 용도로도 활용).
[HttpGet("timestamp")]
[ProducesResponseType(typeof(long), StatusCodes.Status200OK)]
public async Task> GetTimestamp(CancellationToken ct)
=> Ok(await _trading.GetTimeStampAsync(ct));
/// 선물 계좌 잔고/포지션 조회.
[HttpGet("account")]
[ProducesResponseType(typeof(List>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
public async Task 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);
}
/// 미체결 주문 조회.
[HttpGet("orders/pending")]
[ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
public async Task 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);
}
/// 주문 상세 조회.
[HttpGet("orders/{symbol}/{orderId}")]
[ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task 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);
}
/// 신규 주문.
[HttpPost("orders")]
[ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
public async Task 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");
}
}
/// 주문 취소.
[HttpDelete("orders")]
[ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
public async Task 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);
}
/// 포지션 모드 변경(hedge / one_way).
[HttpPut("position-mode")]
[ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
public async Task 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);
}
}