[BE-08] 토스 OCO 조건부주문 어댑터

작업 내용 (설계 의도)

변경 사항

토스 /api/v1/conditional-orders를 호출하는 어댑터를 만든다. STOP(손절 트리거가) + PROFIT_RATE(목표 수익률)를 OCO 타입으로 쌍 등록해, 봇이 죽어도 브로커 측에서 손절이 작동하게 한다. 근거: ADR-004.

order 도메인에 신규 파일만 추가한다 — 기존 OrderGateway.kt·TossOrderGatewayImpl.kt는 수정하지 않는다(Single Writer 보호).

제약:

  • CONDITIONAL_ORDER 레이트리밋 초당 5회 → 기존 TossRateLimiterFacade에 그룹을 추가해 준수한다.
  • 계좌 스코프 API이므로 X-Tossinvest-Account 헤더가 필수다(누락 시 400 account-header-required).
  • 401 시 토큰 재발급 후 1회 재시도 — 기존 TossAuthClient#refresh 패턴을 따른다.

롤백: 신규 파일 제거. 호출부(BE-10 LiveTradeExecutor)가 M4까지 동작하지 않으므로 되돌리기가 안전하다.

의존

  • BE-01 (도메인 계약 — ConditionalOrderGateway·OcoOrderSpec)

다이어그램

처리 흐름

sequenceDiagram
    participant Exec as LiveTradeExecutor
    participant Gw as TossConditionalOrderGatewayImpl
    participant Toss as Toss Open API
    Exec->>Gw: placeOcoOrder(spec)
    Gw->>Toss: POST /api/v1/conditional-orders
    Toss-->>Gw: conditionalOrderId
    Gw-->>Exec: conditionalOrderId

클래스 의존

flowchart LR
    Iface[ConditionalOrderGateway] -.->|implements| Impl[TossConditionalOrderGatewayImpl]
    Impl --> Client[tossRestClient]
    Impl --> Auth[TossAuthClient]
    Impl --> Limiter[TossRateLimiterFacade]
    Impl --> Dtos[TossConditionalOrderDtos]

테스트 케이스

  • OCO 등록 성공 시 conditionalOrderId를 반환한다(MockWebServer).
  • 손절가와 목표수익률이 요청 본문의 first·second에 각각 담긴다.
  • X-Tossinvest-Account 헤더가 요청에 포함된다.
  • 401 응답 시 토큰을 재발급하고 1회 재시도한다.
  • 429 응답 시 레이트리밋 예외를 던진다.
  • 조건부 주문 취소가 정상 호출된다.