인과적 GT 신호·복리 배분 시뮬을 도입하고 운영 정합성을 맞춘다.

미래 데이터를 쓰지 않는 causal 신호/tier와 전기간 복리 포트폴리오 비교로 GT 대비 sim_sized 검증 경로를 정리하고, 일한도·매수 상한·live_buy 스케일을 제거한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xavis
2026-05-31 19:50:54 +09:00
parent 5842cc9fa3
commit e68bb44083
16 changed files with 1817 additions and 474 deletions

View File

@@ -26,10 +26,10 @@ from deepcoin.ground_truth.ground_truth import (
)
from deepcoin.matching.portfolio_sim import (
fires_to_trade_list,
select_capped_fires,
simulate_fixed_order_portfolio,
simulate_fixed_order_portfolio_steps,
simulate_sized_portfolio,
sort_fires_chronological,
)
from deepcoin.matching.select_rules import _split_train_valid_holdout
from deepcoin.ops.chart_report import (
@@ -231,7 +231,7 @@ def _summary_cards_html(
gt_pnl, "정답 GT", len(gt_trades)
)
sim_sized_title = (
"시뮬·총자산% (EV/WF·leg상위) — "
"시뮬·GT tier 복리 (전기간, 상한 없음) — "
f'<span class="{go_cls}">{"GO" if go_flag else "NO-GO"}</span>'
)
sim_fixed_title = f"시뮬·고정 ₩{LIVE_ORDER_KRW:,}/회 (비교)"
@@ -280,15 +280,17 @@ def build_simulation_page_html(
monitor_ids = {r["rule_id"] for r in monitor_rules}
rules_by_id = {r["rule_id"]: r for r in monitor_rules}
sim_fires = pd.DataFrame()
holdout_fires = pd.DataFrame()
if op.is_file():
outcomes = pd.read_csv(op)
outcomes["split"] = _split_train_valid_holdout(outcomes)
sim_fires = outcomes[outcomes["rule_id"].isin(monitor_ids)].copy()
holdout_fires = outcomes[
(outcomes["rule_id"].isin(monitor_ids)) & (outcomes["split"] == "holdout")
].copy()
capped = select_capped_fires(holdout_fires)
compound_fires = sort_fires_chronological(sim_fires)
gt_data = load_ground_truth(gt_path or resolve_ground_truth_file()) or {}
gt_trades = gt_data.get("trades") or []
@@ -316,8 +318,8 @@ def build_simulation_page_html(
elif gt_summary.get("mark_price"):
close_val = float(gt_summary["mark_price"])
sim_trades_sized = fires_to_trade_list(capped, apply_dynamic_sizing=True)
sim_trades_fixed = fires_to_trade_list(capped, apply_dynamic_sizing=False)
sim_trades_sized = fires_to_trade_list(compound_fires, apply_dynamic_sizing=True)
sim_trades_fixed = fires_to_trade_list(compound_fires, apply_dynamic_sizing=False)
gt_pnl: dict[str, Any] = {}
if gt_trades:
@@ -377,14 +379,14 @@ def build_simulation_page_html(
return ""
sim_table = f"""
<h2>시뮬 타점 (holdout {len(holdout_fires)}건 → 체결 가정 {len(capped)}건)</h2>
<p class="meta">총자산×최적비중·현금한도·EV/WF통과·leg상위 대형 매수. 일한도·최대 거래수 적용.
<h2>시뮬 타점 (전기간 {len(sim_fires)}건 → 복리 체결 {len(compound_fires)}건)</h2>
<p class="meta">총자산×GT비중×leg tier·보유현금 한도·전기간 복리(일한도 없음).
가격 열 (+/-) = <b>{label_mode}</b> 구간 수익%.{_mark_note(close_val)}</p>
<div class="table-scroll">
<table>
<thead><tr><th>시각</th><th>구분</th><th>규칙</th><th>유형</th><th>가격</th>
<th>총 평가금액</th><th>승/패</th><th>비고</th></tr></thead>
<tbody>{_sim_fire_table_rows(capped, rules_by_id, sim_steps)}</tbody>
<tbody>{_sim_fire_table_rows(compound_fires, rules_by_id, sim_steps)}</tbody>
</table>
</div>"""
@@ -409,12 +411,12 @@ def build_simulation_page_html(
note = (
f"1단계 시뮬 · holdout {report.get('holdout_ratio', 0.15)} · "
f"발화 {len(holdout_fires)}건 / 체결가정 {len(capped)}건. "
f"전기간 발화 {len(sim_fires)}건 / holdout {len(holdout_fires)}건. "
"상단 카드: 초기 금액·총보유자산·초기 대비 증감율·수수료."
)
legend = (
"▲ <b>정답 매수</b> · ▼ <b>정답 매도</b> — 삼각형 = GT 체결 금액.<br>"
"● <b>시뮬</b> — 원 = holdout 발화 (차트). 테이블 = 일한도 적용 체결 순서."
"● <b>시뮬</b> — holdout 발화 (차트). 테이블 = 전기간 GT tier 복리 체결."
)
if frames is not None:
meta_line = (
@@ -434,7 +436,7 @@ def build_simulation_page_html(
gt_pnl,
sim_sized_pnl,
sim_fixed_pnl,
len(capped),
len(compound_fires),
go_flag,
model_note=model_note,
)
@@ -446,7 +448,7 @@ def build_simulation_page_html(
note=note,
truth_trades=gt_trades,
sim_trades=_fires_to_chart_trades(holdout_fires),
# 차트 마커는 holdout 전체; 카드·테이블은 일한도 capped
# 차트 마커는 holdout; 카드·테이블은 전기간 GT tier 복리
title_suffix="1단계 시뮬레이션 (monitor · holdout)",
legend_html=legend,
footer_sections=sections,