[BE-10] PaperTradeExecutor·LiveTradeExecutor

작업 내용 (설계 의도)

변경 사항

집행 전략 두 구현을 만든다. PaperTradeExecutorOrderGateway·ConditionalOrderGateway를 생성자에서 받지 않는다 — 토스에 sandbox가 없어 코드가 유일한 방어선이므로, 실주문을 “설정 실수로 나갈 수 있는 것”이 아니라 참조할 대상이 없는 것으로 만든다. 근거: ADR-003.

  • PaperTradeExecutor(costModel) — 모의 체결만 생성. 손절·익절 도달은 가격 조회로 판정한다(브로커 위임 불가·불필요).
  • LiveTradeExecutor(orderGateway, conditionalOrderGateway, costModel) — 토스 실주문 + 매수 체결 직후 OCO 등록. OCO 등록 실패 시 즉시 시장가 청산(SellReason.OCO_REGISTRATION_FAILED) — 손절 없는 포지션을 보유하지 않는다.

주문 멱등 키는 clientOrderId = "AT-{proposalId}"로 부여한다. 제출 실패 시 재시도하지 않고 미체결 조회로 접수 여부를 확인한다(PRD FR-20).

LiveTradeExecutor는 빈으로 등록되되 정책 모드가 PAPER인 한 선택되지 않는다 — 빈 등록 자체를 토글하지 않는다(no-conditional-on-property).

롤백: 정책 모드를 PAPER로 되돌리면 Live 경로에 진입하지 않는다(배포 불필요).

의존

  • BE-05 (포지션·체결), BE-06 (비용모델), BE-08 (OCO 어댑터)

다이어그램

처리 흐름

sequenceDiagram
    participant Svc as AutoTradingDomainService
    participant Live as LiveTradeExecutor
    participant Order as OrderGateway
    participant Oco as ConditionalOrderGateway
    Svc->>Live: buy(proposal, marketPrice)
    Live->>Order: placeOrder(clientOrderId=AT-42)
    Order-->>Live: tossOrderId
    Live->>Oco: placeOcoOrder(손절가, 목표수익률)
    Oco-->>Live: 실패
    Live->>Order: 즉시 시장가 청산

클래스 의존

flowchart LR
    Iface[TradeExecutor] -.->|implements| Paper[PaperTradeExecutor]
    Iface -.->|implements| Live[LiveTradeExecutor]
    Paper --> Cost[TradingCostModel]
    Live --> Cost
    Live --> OrderGw[OrderGateway]
    Live --> OcoGw[ConditionalOrderGateway]

PaperTradeExecutor에서 Gateway로 가는 간선이 없다.

테스트 케이스

  • PaperTradeExecutor.buy()가 비용 모델을 적용한 모의 체결을 반환한다.
  • PaperTradeExecutor 생성자에 Gateway 타입 파라미터가 없다(리플렉션 검증).
  • PaperTradeExecutor로 매수·매도를 수행해도 토스 요청이 0건이다(MockWebServer).
  • LiveTradeExecutor.buy()가 체결 후 OCO를 등록하고 포지션에 ID를 붙인다.
  • OCO 등록이 실패하면 즉시 시장가 청산이 호출된다(손절 없는 포지션 방지).
  • 같은 제안을 두 번 집행해도 동일 clientOrderId로 중복 주문이 방지된다.
  • 주문 제출 타임아웃 시 재시도하지 않고 미체결 조회로 접수 여부를 확인한다.