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); } }