fix(bat-052): CHG-20260626-02 보정 — buy_fee_only_assets 제거 + NFR-2 엄격화
portfolio-hub WAC가 매수 fee를 cost basis에 편입함에 따라(REQ-110 change-req) 이중계상을 방지하기 위해 buy_fee_only_assets 관련 로직을 전면 제거하고, 일중 닫힌 거래 AC-2 단언을 엄격 동치(realized_pnl_wac == realized_pnl)로 강화. 변경: - OperatorReport.buy_fee_only_assets 필드 제거 - apply_wac_to_report buy_fee_only_assets 인자 제거 - buy_fee_by_asset 헬퍼 함수 제거 - render_text 매수전용 수수료 섹션 제거 - render_json buy_fee_only_assets 필드 제거 (WAC 필드 4개→3개) - cli.py: buy_fee_by_asset import/사용 제거, 미사용 'from datetime import date, timedelta' 제거 - test_wac_equals_net_when_same_day_buy_sell: WAC 957.0, 엄격 동치 단언 - test_buy_only_assets_has_fee → test_buy_only_asset_no_fee_line (fee 라인 없음) - test_render_json_has_all_four_wac_fields → test_render_json_has_three_wac_fields - 동일 CLI 통합 테스트 보정 결과: 717 passed, 47 skipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -53,7 +53,6 @@ class OperatorReport:
|
||||
# REQ-110 WAC 실현손익 (portfolio-hub 통합)
|
||||
realized_pnl_wac: float = 0.0
|
||||
realized_pnl_wac_by_asset: dict[str, float] = field(default_factory=dict)
|
||||
buy_fee_only_assets: dict[str, float] = field(default_factory=dict)
|
||||
data_completeness_warning: bool = False
|
||||
# REQ-111 degrade flag
|
||||
wac_unavailable: bool = False
|
||||
@@ -104,34 +103,19 @@ def apply_wac_to_report(
|
||||
*,
|
||||
wac_total: float,
|
||||
wac_by_asset: dict[str, float],
|
||||
buy_fee_only_assets: dict[str, float],
|
||||
data_completeness_warning: bool,
|
||||
) -> None:
|
||||
"""REQ-110: WAC 실현손익을 OperatorReport에 in-place로 적용한다.
|
||||
|
||||
순수 도메인 계층에서는 이 헬퍼가 아닌 build_report를 사용하고,
|
||||
CLI(_cmd_report)에서 portfolio-hub 응답을 받아 이 함수로 주입한다.
|
||||
CHG-20260626-02: 매수 fee가 cost basis에 편입되어 buy_fee_only_assets 제거됨.
|
||||
"""
|
||||
rep.realized_pnl_wac = wac_total
|
||||
rep.realized_pnl_wac_by_asset = dict(wac_by_asset)
|
||||
rep.buy_fee_only_assets = dict(buy_fee_only_assets)
|
||||
rep.data_completeness_warning = data_completeness_warning
|
||||
|
||||
|
||||
def buy_fee_by_asset(trades: list[TradeRow], *, exclude_symbols: set[str] | None = None) -> dict[str, float]:
|
||||
"""OQ-3: 당일 매수 거래 fee 합산, 자산별 (매도 없는 자산 대상).
|
||||
|
||||
exclude_symbols: 매도 실현손익이 이미 WAC에 반영된 자산 집합.
|
||||
"""
|
||||
fee_map: dict[str, float] = {}
|
||||
for t in trades:
|
||||
if t.side == "buy":
|
||||
fee_map[t.asset_id] = fee_map.get(t.asset_id, 0.0) + t.fee
|
||||
if exclude_symbols:
|
||||
return {k: v for k, v in fee_map.items() if k not in exclude_symbols}
|
||||
return fee_map
|
||||
|
||||
|
||||
def render_text(rep: OperatorReport) -> str:
|
||||
"""사람용 표 요약."""
|
||||
lines: list[str] = []
|
||||
@@ -155,10 +139,6 @@ def render_text(rep: OperatorReport) -> str:
|
||||
lines.append(f" {asset}: {pnl:,.0f}")
|
||||
if rep.data_completeness_warning:
|
||||
lines.append(" [!] 이력 불완전 경고: cost basis 산출이 부정확할 수 있습니다.")
|
||||
if rep.buy_fee_only_assets:
|
||||
lines.append("=== 매수 전용 자산 수수료 ===")
|
||||
for asset, fee in sorted(rep.buy_fee_only_assets.items()):
|
||||
lines.append(f" {asset}: -{fee:,.0f}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -177,10 +157,9 @@ def render_json(rep: OperatorReport) -> dict:
|
||||
"fills_count": rep.fills_count,
|
||||
"realized_pnl": rep.realized_pnl,
|
||||
"realized_pnl_by_asset": rep.realized_pnl_by_asset,
|
||||
# REQ-110 신규 WAC 필드
|
||||
# REQ-110 신규 WAC 필드 (CHG-20260626-02: buy_fee_only_assets 제거)
|
||||
"realized_pnl_wac": None if rep.wac_unavailable else rep.realized_pnl_wac,
|
||||
"realized_pnl_wac_by_asset": None if rep.wac_unavailable else rep.realized_pnl_wac_by_asset,
|
||||
"buy_fee_only_assets": None if rep.wac_unavailable else rep.buy_fee_only_assets,
|
||||
"data_completeness_warning": None if rep.wac_unavailable else rep.data_completeness_warning,
|
||||
}
|
||||
# REQ-111: degrade 표기
|
||||
|
||||
@@ -668,7 +668,6 @@ def _cmd_verify(args, cfg) -> int:
|
||||
def _cmd_report(args, cfg) -> int:
|
||||
"""REQ-088/110/111: holdings / today's fills / WAC realized PnL / pending — direct-DB."""
|
||||
import psycopg
|
||||
from datetime import date, timedelta
|
||||
|
||||
from trex_core.application.operator_report import (
|
||||
Holding,
|
||||
@@ -676,7 +675,6 @@ def _cmd_report(args, cfg) -> int:
|
||||
TradeRow,
|
||||
apply_wac_to_report,
|
||||
build_report,
|
||||
buy_fee_by_asset,
|
||||
kst_day_bounds_utc,
|
||||
render_json,
|
||||
render_text,
|
||||
@@ -749,15 +747,10 @@ def _cmd_report(args, cfg) -> int:
|
||||
key = item.symbol or pid
|
||||
wac_by_asset_accum[key] = wac_by_asset_accum.get(key, 0.0) + item.realized_pnl
|
||||
|
||||
# OQ-3: buy_fee_only_assets = 당일 매수 거래 fee 합산 (매도 없는 자산 대상)
|
||||
sell_assets = {t.asset_id for t in trades if t.side == "sell"}
|
||||
buy_fee_only = buy_fee_by_asset(trades, exclude_symbols=sell_assets)
|
||||
|
||||
apply_wac_to_report(
|
||||
rep,
|
||||
wac_total=wac_total,
|
||||
wac_by_asset=wac_by_asset_accum,
|
||||
buy_fee_only_assets=buy_fee_only,
|
||||
data_completeness_warning=any_warning,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user