trade: apply runtime strategy/fx pnl split on sell paths (#370) #383
@@ -111,6 +111,7 @@ Updated: 2026-03-02
|
|||||||
|
|
||||||
- **위치**: `src/db.py` (`fx_pnl`, `strategy_pnl` 컬럼 존재)
|
- **위치**: `src/db.py` (`fx_pnl`, `strategy_pnl` 컬럼 존재)
|
||||||
- **현 상태**: 런타임 SELL 경로에서 `strategy_pnl`/`fx_pnl` 분리 계산 및 전달을 적용함 (`#370`).
|
- **현 상태**: 런타임 SELL 경로에서 `strategy_pnl`/`fx_pnl` 분리 계산 및 전달을 적용함 (`#370`).
|
||||||
|
- **운영 메모**: `trading_cycle`은 scanner 기반 `selection_context`에 `fx_rate`를 추가하고, `run_daily_session`은 scanner 컨텍스트 없이 `fx_rate` 스냅샷만 기록한다.
|
||||||
- **잔여**: 과거 BUY 레코드에 `fx_rate`가 없으면 해외 구간도 `fx_pnl=0` fallback으로 기록됨.
|
- **잔여**: 과거 BUY 레코드에 `fx_rate`가 없으면 해외 구간도 `fx_pnl=0` fallback으로 기록됨.
|
||||||
- **영향**: USD 거래에서 환율 손익과 전략 손익이 분리되지 않아 성과 분석 부정확
|
- **영향**: USD 거래에서 환율 손익과 전략 손익이 분리되지 않아 성과 분석 부정확
|
||||||
- **요구사항**: REQ-V3-007
|
- **요구사항**: REQ-V3-007
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ def _resolve_sell_qty_for_pnl(*, sell_qty: int | None, buy_qty: int | None) -> i
|
|||||||
|
|
||||||
def _extract_fx_rate_from_sources(*sources: dict[str, Any] | None) -> float | None:
|
def _extract_fx_rate_from_sources(*sources: dict[str, Any] | None) -> float | None:
|
||||||
"""Best-effort FX rate extraction from broker payloads."""
|
"""Best-effort FX rate extraction from broker payloads."""
|
||||||
|
# KIS overseas payloads expose exchange-rate fields with varying key names
|
||||||
|
# across endpoints/responses (price, balance, buying power). Keep this list
|
||||||
|
# centralised so schema drifts can be patched in one place.
|
||||||
rate_keys = (
|
rate_keys = (
|
||||||
"frst_bltn_exrt",
|
"frst_bltn_exrt",
|
||||||
"bass_exrt",
|
"bass_exrt",
|
||||||
@@ -3392,10 +3395,13 @@ async def run_daily_session(
|
|||||||
sell_fx_rate=sell_fx_rate,
|
sell_fx_rate=sell_fx_rate,
|
||||||
)
|
)
|
||||||
if sell_fx_rate is not None and not market.is_domestic:
|
if sell_fx_rate is not None and not market.is_domestic:
|
||||||
|
# Daily path does not carry scanner candidate metrics, so this
|
||||||
|
# context intentionally stores FX snapshot only.
|
||||||
selection_context = {"fx_rate": sell_fx_rate}
|
selection_context = {"fx_rate": sell_fx_rate}
|
||||||
elif not market.is_domestic:
|
elif not market.is_domestic:
|
||||||
snapshot_fx_rate = _extract_fx_rate_from_sources(balance_info, stock_data)
|
snapshot_fx_rate = _extract_fx_rate_from_sources(balance_info, stock_data)
|
||||||
if snapshot_fx_rate is not None:
|
if snapshot_fx_rate is not None:
|
||||||
|
# BUY/HOLD in daily path: persist FX snapshot for later SELL split.
|
||||||
selection_context = {"fx_rate": snapshot_fx_rate}
|
selection_context = {"fx_rate": snapshot_fx_rate}
|
||||||
log_trade(
|
log_trade(
|
||||||
conn=db_conn,
|
conn=db_conn,
|
||||||
|
|||||||
Reference in New Issue
Block a user