인과적 GT 신호·복리 배분 시뮬을 도입하고 운영 정합성을 맞춘다.
미래 데이터를 쓰지 않는 causal 신호/tier와 전기간 복리 포트폴리오 비교로 GT 대비 sim_sized 검증 경로를 정리하고, 일한도·매수 상한·live_buy 스케일을 제거한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
150
deepcoin/ground_truth/gt_allocation_analysis.py
Normal file
150
deepcoin/ground_truth/gt_allocation_analysis.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
GT 체결 amount_krw·총자산 비율 분석 — 시뮬 tier·배분율 최적 추정.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from config import (
|
||||
GT_BUY_PCT_LARGE_LEG,
|
||||
GT_BUY_PCT_SMALL_LEG,
|
||||
GT_INITIAL_CASH_KRW,
|
||||
GT_LARGE_LEG_TOP_PCT,
|
||||
TRADING_FEE_RATE,
|
||||
)
|
||||
from deepcoin.matching.position_sizing import (
|
||||
leg_asset_pct_scale,
|
||||
optimal_weight_share,
|
||||
portfolio_totals,
|
||||
top_leg_ids_by_forward_return,
|
||||
)
|
||||
|
||||
|
||||
def analyze_gt_buy_allocation(
|
||||
trades: list[dict[str, Any]],
|
||||
*,
|
||||
initial_cash: float = GT_INITIAL_CASH_KRW,
|
||||
fee_rate: float = TRADING_FEE_RATE,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
GT 시각순 체결에서 매수별 (실제투입/총자산) 비율을 분석합니다.
|
||||
|
||||
Args:
|
||||
trades: amount_krw·weight·leg_id가 채워진 GT trade dict.
|
||||
initial_cash: 시작 현금.
|
||||
fee_rate: 수수료율.
|
||||
|
||||
Returns:
|
||||
leg tier별·전체 배분 통계 및 권장 pct_large/pct_small.
|
||||
"""
|
||||
chron = sorted(trades, key=lambda x: x["dt"])
|
||||
if not chron:
|
||||
return {"note": "체결 없음"}
|
||||
|
||||
large_legs = top_leg_ids_by_forward_return(chron, GT_LARGE_LEG_TOP_PCT)
|
||||
cash = float(initial_cash)
|
||||
qty = 0.0
|
||||
|
||||
ratios_large: list[float] = []
|
||||
ratios_small: list[float] = []
|
||||
ratios_all: list[float] = []
|
||||
|
||||
for i, t in enumerate(chron):
|
||||
price = float(t["price"])
|
||||
if price <= 0:
|
||||
continue
|
||||
leg_id = int(t.get("leg_id", 0))
|
||||
action = t.get("action", "")
|
||||
|
||||
if action == "buy":
|
||||
w = float(t.get("weight", 1.0))
|
||||
rem = sum(
|
||||
float(chron[j].get("weight", 1.0))
|
||||
for j in range(i, len(chron))
|
||||
if int(chron[j].get("leg_id", 0)) == leg_id
|
||||
and chron[j].get("action") == "buy"
|
||||
)
|
||||
opt = optimal_weight_share(w, rem) if rem > 0 else 1.0
|
||||
total_asset, _, _ = portfolio_totals(cash, qty, price)
|
||||
amount = float(t.get("amount_krw") or 0)
|
||||
if total_asset > 0 and amount > 0 and opt > 0:
|
||||
implied = amount / (total_asset * opt)
|
||||
ratios_all.append(implied)
|
||||
if leg_id in large_legs:
|
||||
ratios_large.append(implied)
|
||||
else:
|
||||
ratios_small.append(implied)
|
||||
if amount > 0:
|
||||
fee = amount * fee_rate
|
||||
cash -= amount + fee
|
||||
qty += amount / price
|
||||
elif action == "sell" and qty > 0:
|
||||
gross = float(t.get("amount_krw") or qty * price)
|
||||
cash += gross * (1.0 - fee_rate)
|
||||
qty = 0.0
|
||||
|
||||
def _stats(vals: list[float]) -> dict[str, float]:
|
||||
if not vals:
|
||||
return {}
|
||||
s = sorted(vals)
|
||||
n = len(s)
|
||||
return {
|
||||
"count": n,
|
||||
"mean": round(sum(s) / n, 4),
|
||||
"median": round(s[n // 2], 4),
|
||||
"p25": round(s[max(0, n // 4)], 4),
|
||||
"p75": round(s[min(n - 1, 3 * n // 4)], 4),
|
||||
}
|
||||
|
||||
st_all = _stats(ratios_all)
|
||||
st_large = _stats(ratios_large)
|
||||
st_small = _stats(ratios_small)
|
||||
|
||||
rec_large = st_large.get("median", GT_BUY_PCT_LARGE_LEG)
|
||||
rec_small = st_small.get("median", GT_BUY_PCT_SMALL_LEG)
|
||||
if not rec_large or rec_large <= 0:
|
||||
rec_large = GT_BUY_PCT_LARGE_LEG
|
||||
if not rec_small or rec_small <= 0:
|
||||
rec_small = GT_BUY_PCT_SMALL_LEG
|
||||
|
||||
return {
|
||||
"large_leg_ids": sorted(large_legs),
|
||||
"large_leg_count": len(large_legs),
|
||||
"config_pct_large": GT_BUY_PCT_LARGE_LEG,
|
||||
"config_pct_small": GT_BUY_PCT_SMALL_LEG,
|
||||
"observed_implied_scale": {
|
||||
"all": st_all,
|
||||
"large_leg": st_large,
|
||||
"small_leg": st_small,
|
||||
},
|
||||
"recommended_pct_large_leg": round(rec_large, 4),
|
||||
"recommended_pct_small_leg": round(rec_small, 4),
|
||||
"note": (
|
||||
"implied_scale = amount / (pre_buy_total_asset × weight_share); "
|
||||
"시뮬 tier는 GT 분석 median 사용"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def gt_tier_scale_from_analysis(
|
||||
leg_id: int,
|
||||
large_legs: set[int],
|
||||
analysis: dict[str, Any] | None = None,
|
||||
) -> float:
|
||||
"""
|
||||
GT 분석 권장값 또는 config tier scale.
|
||||
|
||||
Args:
|
||||
leg_id: leg 번호.
|
||||
large_legs: 상위 leg.
|
||||
analysis: analyze_gt_buy_allocation 결과.
|
||||
|
||||
Returns:
|
||||
총자산 대비 매수 스케일 (0~1).
|
||||
"""
|
||||
if analysis and analysis.get("observed_implied_scale", {}).get("all"):
|
||||
if leg_id in large_legs:
|
||||
return float(analysis.get("recommended_pct_large_leg", GT_BUY_PCT_LARGE_LEG))
|
||||
return float(analysis.get("recommended_pct_small_leg", GT_BUY_PCT_SMALL_LEG))
|
||||
return leg_asset_pct_scale(leg_id, large_legs)
|
||||
Reference in New Issue
Block a user