[BE-05] TradingPosition·TradingFill 애그리게이트
작업 내용 (설계 의도)
변경 사항
포지션과 체결을 한 티켓으로 묶는다 — 체결이 포지션의 수량·평단·실현손익을 바꾸므로 두 Entity가 강결합이고, 나누면 서로를 기다리게 된다.
TradingPosition— 수량·평단·손절가·익절가·OCO 주문 ID.applyBuyFill()이 부분 체결을 누적 반영하고,applySellFill()이 실현손익을 산출한다.isStopLossHit(price)·isTakeProfitHit(price)로 도달 판정을 캡슐화한다.TradingFill— 체결가·실효단가·비용·실현손익.execution_mode로 paper/live를 구분해 같은 테이블에 저장하되 집계는 분리한다(PRD FR-23).
같은 종목·같은 모드에 열린 포지션이 하나만 존재해야 한다 — 이 불변식은 DB 유일성 제약(DB-01)과 findOpenBy() 양쪽으로 지킨다.
의존
- BE-01 (도메인 계약), DB-01 (스키마)
다이어그램
처리 흐름
sequenceDiagram participant Exec as TradeExecutor participant Pos as TradingPosition participant Fill as TradingFill Exec->>Fill: 매수 체결 생성 Exec->>Pos: applyBuyFill(fill) Pos-->>Exec: 수량·평단 갱신 Exec->>Pos: isStopLossHit(현재가) Pos-->>Exec: true
클래스 의존
flowchart LR Pos[TradingPosition] --> Fill[TradingFill] Fill --> Mode[ExecutionMode] Fill --> Reason[SellReason] PosRepo[TradingPositionRepository] -.->|implements| PosImpl[TradingPositionRepositoryImpl] FillRepo[TradingFillRepository] -.->|implements| FillImpl[TradingFillRepositoryImpl] PosImpl --> T1[(trading_positions)] FillImpl --> T2[(trading_fills)]
테스트 케이스
- 매수 체결 2건을 적용하면 수량이 합산되고 평단이 가중평균으로 갱신된다(부분 체결).
- 매도 체결 시 실현손익이 실효단가 기준으로 산출된다.
- 전량 매도 시 포지션이 종료 상태가 된다.
- 현재가가 손절가 이하면
isStopLossHit()이 true를 반환한다(경계값 포함). - 현재가가 손절가보다 1원 높으면 false를 반환한다(경계값).
findAllOpen(PAPER)가 LIVE 포지션을 반환하지 않는다(모드 분리, TestContainers MySQL).- 같은 종목·모드로 열린 포지션을 두 개 저장하면 제약에 걸린다.