GT 총자산 비율 매수·leg 티어 배분과 시뮬/실거래 포지션 사이징을 통합한다.
타점·비중을 gt_model로 일반화하고, amount_krw 시각순 배분·EV/WF·상위 leg 대형 매수를 position_sizing과 시뮬 HTML(고정 ₩/회 비교)에 반영한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from config import (
|
||||
GT_INITIAL_CASH_KRW,
|
||||
LIVE_DAILY_KRW_MAX,
|
||||
LIVE_MAX_TRADES_PER_DAY,
|
||||
LIVE_ORDER_KRW,
|
||||
@@ -28,7 +29,19 @@ from config import (
|
||||
SIM_WALK_FORWARD_MIN_MONTHS,
|
||||
TRADING_FEE_RATE,
|
||||
)
|
||||
from deepcoin.ground_truth.ground_truth import (
|
||||
load_ground_truth,
|
||||
order_trades_chronological,
|
||||
simulate_truth_portfolio,
|
||||
)
|
||||
from deepcoin.matching.portfolio_sim import (
|
||||
fires_to_trade_list,
|
||||
select_capped_fires,
|
||||
simulate_fixed_order_portfolio,
|
||||
simulate_sized_portfolio,
|
||||
)
|
||||
from deepcoin.matching.select_rules import _rule_metrics, _split_train_valid_holdout
|
||||
from deepcoin.paths import resolve_ground_truth_file
|
||||
from deepcoin.paths import (
|
||||
ANALYSIS_GT_CALIBRATION_JSON,
|
||||
MATCHING_FIRE_OUTCOMES,
|
||||
@@ -109,12 +122,19 @@ def walk_forward_summary(wf_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]:
|
||||
def simulate_live_order_cap(
|
||||
outcomes: pd.DataFrame,
|
||||
*,
|
||||
rule_ids: set[str] | None = None,
|
||||
holdout_only: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
1회·일 한도·슬리피지 가정으로 체결 가능한 발화만 집계.
|
||||
|
||||
Args:
|
||||
outcomes: fire_outcomes.
|
||||
outcomes: fire_outcomes (split 컬럼 있으면 holdout 필터 가능).
|
||||
rule_ids: None이면 전 규칙, 지정 시 해당 rule만.
|
||||
holdout_only: True면 split==holdout 만.
|
||||
|
||||
Returns:
|
||||
규칙별·전체 요약.
|
||||
@@ -122,12 +142,27 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]:
|
||||
if outcomes.empty:
|
||||
return {"rules": {}, "note": "발화 없음"}
|
||||
|
||||
df = outcomes.sort_values("dt").copy()
|
||||
df = outcomes
|
||||
if holdout_only and "split" in df.columns:
|
||||
df = df[df["split"] == "holdout"]
|
||||
if rule_ids is not None:
|
||||
df = df[df["rule_id"].isin(rule_ids)]
|
||||
df = df.sort_values("dt").copy()
|
||||
df["ts"] = pd.to_datetime(df["dt"])
|
||||
df["day"] = df["ts"].dt.date.astype(str)
|
||||
slip = LIVE_SLIPPAGE_PCT
|
||||
taken_rows: list[pd.DataFrame] = []
|
||||
|
||||
from deepcoin.matching.position_sizing import (
|
||||
compute_buy_amount_krw,
|
||||
live_buy_asset_pct_scale,
|
||||
load_sizing_context_from_gt,
|
||||
)
|
||||
|
||||
gt_trades, large_legs, approved = load_sizing_context_from_gt()
|
||||
cash = float(GT_INITIAL_CASH_KRW)
|
||||
qty = 0.0
|
||||
|
||||
for day, day_grp in df.groupby("day", sort=True):
|
||||
spent = 0.0
|
||||
n_trades = 0
|
||||
@@ -135,9 +170,34 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]:
|
||||
for idx, row in day_grp.iterrows():
|
||||
if n_trades >= LIVE_MAX_TRADES_PER_DAY:
|
||||
break
|
||||
if spent + LIVE_ORDER_KRW > LIVE_DAILY_KRW_MAX:
|
||||
break
|
||||
spent += LIVE_ORDER_KRW
|
||||
side = row["side"]
|
||||
price = float(row["close"])
|
||||
if side == "buy":
|
||||
scale = live_buy_asset_pct_scale(
|
||||
str(row["rule_id"]),
|
||||
str(row["dt"]),
|
||||
gt_trades,
|
||||
approved_rules=approved,
|
||||
large_legs=large_legs,
|
||||
)
|
||||
planned = compute_buy_amount_krw(
|
||||
cash, qty, price, 1.0, 1.0, asset_pct_scale=scale
|
||||
)
|
||||
else:
|
||||
planned = float(LIVE_ORDER_KRW)
|
||||
if side == "buy":
|
||||
if planned <= 0:
|
||||
continue
|
||||
if spent + planned > LIVE_DAILY_KRW_MAX:
|
||||
break
|
||||
fee = planned * TRADING_FEE_RATE
|
||||
cash -= planned + fee
|
||||
qty += planned / price if price > 0 else 0.0
|
||||
spent += planned
|
||||
elif side == "sell" and qty > 0:
|
||||
gross = qty * price
|
||||
cash += gross * (1.0 - TRADING_FEE_RATE)
|
||||
qty = 0.0
|
||||
n_trades += 1
|
||||
taken_idx.append(idx)
|
||||
if taken_idx:
|
||||
@@ -164,6 +224,7 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]:
|
||||
"order_krw": LIVE_ORDER_KRW,
|
||||
"daily_krw_max": LIVE_DAILY_KRW_MAX,
|
||||
"slippage_pct": slip,
|
||||
"sizing": "total_asset_pct_ev_wf_large_leg",
|
||||
},
|
||||
"taken_count": int(len(taken)),
|
||||
"total_count": int(len(df)),
|
||||
@@ -267,9 +328,35 @@ def build_simulation_report(
|
||||
sub.assign(forward_ret_pct=adj)
|
||||
)
|
||||
|
||||
live_cap = simulate_live_order_cap(outcomes)
|
||||
monitor_ids = {r["rule_id"] for r in matched.get("monitor_rules", [])}
|
||||
live_cap = simulate_live_order_cap(
|
||||
outcomes, rule_ids=monitor_ids, holdout_only=True
|
||||
)
|
||||
go = evaluate_go_no_go(matched, wf_sum, fee_stress, live_cap)
|
||||
|
||||
portfolio_compare: dict[str, Any] = {}
|
||||
gt_data = load_ground_truth(resolve_ground_truth_file()) or {}
|
||||
gt_trades = gt_data.get("trades") or []
|
||||
mark = (gt_data.get("summary") or {}).get("mark_price")
|
||||
if gt_trades:
|
||||
portfolio_compare["ground_truth_chrono"] = simulate_truth_portfolio(
|
||||
order_trades_chronological(gt_trades),
|
||||
last_price=float(mark) if mark else None,
|
||||
)
|
||||
holdout = outcomes[
|
||||
outcomes["rule_id"].isin(monitor_ids) & (outcomes["split"] == "holdout")
|
||||
]
|
||||
capped = select_capped_fires(holdout)
|
||||
if not capped.empty:
|
||||
portfolio_compare["sim_sized"] = simulate_sized_portfolio(
|
||||
fires_to_trade_list(capped, apply_dynamic_sizing=True),
|
||||
last_price=float(mark) if mark else None,
|
||||
)
|
||||
portfolio_compare["sim_fixed_order"] = simulate_fixed_order_portfolio(
|
||||
fires_to_trade_list(capped, apply_dynamic_sizing=False),
|
||||
last_price=float(mark) if mark else None,
|
||||
)
|
||||
|
||||
gt_portfolio: dict[str, Any] = {}
|
||||
if ANALYSIS_GT_CALIBRATION_JSON.is_file():
|
||||
cal = json.loads(ANALYSIS_GT_CALIBRATION_JSON.read_text(encoding="utf-8"))
|
||||
@@ -301,6 +388,8 @@ def build_simulation_report(
|
||||
"fee_stress_by_rule": fee_stress,
|
||||
"live_order_cap_sim": live_cap,
|
||||
"go_no_go": go,
|
||||
"portfolio_compare": portfolio_compare,
|
||||
"gt_model": gt_data.get("model"),
|
||||
"monitor_rules": matched.get("monitor_rules", []),
|
||||
"gt_portfolio_calibration": gt_portfolio,
|
||||
"criteria": {
|
||||
|
||||
Reference in New Issue
Block a user