인과적 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

@@ -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)