hybrid DD tier와 Option C 2차(+1000%) 검증을 추가하고 실거래 사이징을 정합한다.
인과 GT leg 엔진·drawdown tier·train 캘리브레이션, Phase 2 Go/No-Go 및 시뮬 리포트를 반영한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
214
deepcoin/matching/live_sizing.py
Normal file
214
deepcoin/matching/live_sizing.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
실거래 매수 사이징 — 시뮬(sim_tier_enhanced)과 동일 인과 tier·weight 정책.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from config import (
|
||||
GT_SIGNAL_CAUSAL,
|
||||
TRADING_FEE_RATE,
|
||||
)
|
||||
from deepcoin.ground_truth.causal_gt_hybrid import (
|
||||
_attach_drawdown_to_buys,
|
||||
_bar_index_at,
|
||||
_close_series_from_df,
|
||||
_drawdown_pct_at_index,
|
||||
hybrid_tier_scale,
|
||||
)
|
||||
from deepcoin.ground_truth.gt_model import leg_entry_weights, remaining_weight_sum
|
||||
from deepcoin.matching.position_sizing import compute_buy_amount_krw
|
||||
from deepcoin.paths import OPS_STATE_DIR
|
||||
|
||||
LIVE_SIZING_STATE_JSON = OPS_STATE_DIR / "live_sizing_state.json"
|
||||
|
||||
|
||||
class LivePositionState:
|
||||
"""
|
||||
미청산 leg·과거 leg 수익·매수 weight 추적 (시뮬 enrich/causal tier 정합).
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""빈 포지션 상태."""
|
||||
self.current_leg_id: int = 0
|
||||
self.open_buys: list[dict[str, Any]] = []
|
||||
self.completed_leg_ret: dict[int, float] = {}
|
||||
self.leg_cost_krw: float = 0.0
|
||||
self.leg_proceeds_krw: float = 0.0
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path | None = None) -> LivePositionState:
|
||||
"""
|
||||
디스크에서 상태 복원.
|
||||
|
||||
Args:
|
||||
path: JSON 경로. None이면 기본 경로.
|
||||
|
||||
Returns:
|
||||
LivePositionState 인스턴스.
|
||||
"""
|
||||
p = path or LIVE_SIZING_STATE_JSON
|
||||
st = cls()
|
||||
if not p.is_file():
|
||||
return st
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return st
|
||||
st.current_leg_id = int(data.get("current_leg_id") or 0)
|
||||
st.open_buys = list(data.get("open_buys") or [])
|
||||
st.completed_leg_ret = {
|
||||
int(k): float(v) for k, v in (data.get("completed_leg_ret") or {}).items()
|
||||
}
|
||||
st.leg_cost_krw = float(data.get("leg_cost_krw") or 0.0)
|
||||
st.leg_proceeds_krw = float(data.get("leg_proceeds_krw") or 0.0)
|
||||
return st
|
||||
|
||||
def save(self, path: Path | None = None) -> None:
|
||||
"""
|
||||
상태를 디스크에 저장.
|
||||
|
||||
Args:
|
||||
path: JSON 경로. None이면 기본 경로.
|
||||
"""
|
||||
p = path or LIVE_SIZING_STATE_JSON
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
"current_leg_id": self.current_leg_id,
|
||||
"open_buys": self.open_buys,
|
||||
"completed_leg_ret": self.completed_leg_ret,
|
||||
"leg_cost_krw": round(self.leg_cost_krw, 0),
|
||||
"leg_proceeds_krw": round(self.leg_proceeds_krw, 0),
|
||||
}
|
||||
p.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
def _start_new_leg_if_needed(self) -> None:
|
||||
"""포지션 없을 때 새 leg 시작."""
|
||||
if not self.open_buys:
|
||||
self.current_leg_id += 1
|
||||
self.leg_cost_krw = 0.0
|
||||
self.leg_proceeds_krw = 0.0
|
||||
|
||||
def record_buy(self, dt: str, price: float, amount_krw: float, fee: float) -> None:
|
||||
"""
|
||||
체결 매수 기록.
|
||||
|
||||
Args:
|
||||
dt: 체결 시각.
|
||||
price: 체결가.
|
||||
amount_krw: 매수 원화.
|
||||
fee: 수수료.
|
||||
"""
|
||||
self._start_new_leg_if_needed()
|
||||
self.open_buys.append({"dt": dt, "price": price, "amount_krw": amount_krw})
|
||||
self.leg_cost_krw += amount_krw + fee
|
||||
|
||||
def record_sell(self, amount_krw: float, fee: float, *, full_close: bool) -> None:
|
||||
"""
|
||||
체결 매도 기록.
|
||||
|
||||
Args:
|
||||
amount_krw: 매도 원화(총액).
|
||||
fee: 수수료.
|
||||
full_close: leg 전량 청산 여부.
|
||||
"""
|
||||
net = amount_krw - fee
|
||||
self.leg_proceeds_krw += net
|
||||
if full_close and self.leg_cost_krw > 0:
|
||||
ret_pct = (self.leg_proceeds_krw - self.leg_cost_krw) / self.leg_cost_krw * 100.0
|
||||
self.completed_leg_ret[self.current_leg_id] = ret_pct
|
||||
self.open_buys = []
|
||||
self.leg_cost_krw = 0.0
|
||||
self.leg_proceeds_krw = 0.0
|
||||
|
||||
def plan_buy_amount_krw(
|
||||
self,
|
||||
dt: str,
|
||||
price: float,
|
||||
cash: float,
|
||||
qty: float,
|
||||
df: pd.DataFrame | None = None,
|
||||
*,
|
||||
enhanced: bool = True,
|
||||
fee_rate: float = TRADING_FEE_RATE,
|
||||
) -> float:
|
||||
"""
|
||||
시뮬과 동일 tier·weight로 매수 원화 산출.
|
||||
|
||||
Args:
|
||||
dt: 신호 시각.
|
||||
price: 종가.
|
||||
cash: 가용 원화.
|
||||
qty: 보유 수량.
|
||||
df: OHLC (drawdown).
|
||||
enhanced: conviction·medium tier 사용.
|
||||
fee_rate: 수수료율.
|
||||
|
||||
Returns:
|
||||
매수 원화.
|
||||
"""
|
||||
self._start_new_leg_if_needed()
|
||||
prices = [float(b["price"]) for b in self.open_buys] + [price]
|
||||
weights = leg_entry_weights(prices)
|
||||
idx = len(self.open_buys)
|
||||
weight = float(weights[idx])
|
||||
w_sum = float(sum(weights[idx:]))
|
||||
trade: dict[str, Any] = {
|
||||
"dt": dt,
|
||||
"action": "buy",
|
||||
"price": price,
|
||||
"leg_id": self.current_leg_id,
|
||||
"weight": round(weight, 4),
|
||||
}
|
||||
if df is not None and not df.empty:
|
||||
attached = _attach_drawdown_to_buys([trade], df)
|
||||
if attached:
|
||||
trade = attached[0]
|
||||
from deepcoin.ground_truth.hybrid_dd_calibrate import load_hybrid_dd_params
|
||||
|
||||
dd_params = load_hybrid_dd_params()
|
||||
scale = hybrid_tier_scale(
|
||||
trade,
|
||||
completed_leg_ret=self.completed_leg_ret,
|
||||
enhanced=enhanced,
|
||||
dd_large_pct=dd_params.get("dd_large_pct"),
|
||||
dd_medium_pct=dd_params.get("dd_medium_pct"),
|
||||
)
|
||||
return compute_buy_amount_krw(
|
||||
cash,
|
||||
qty,
|
||||
price,
|
||||
weight,
|
||||
w_sum,
|
||||
asset_pct_scale=scale,
|
||||
fee_rate=fee_rate,
|
||||
ignore_weight_split=bool(trade.get("conviction_buy")),
|
||||
)
|
||||
|
||||
|
||||
def drawdown_pct_from_df(df: pd.DataFrame, dt: str) -> float:
|
||||
"""
|
||||
bar 시점 drawdown % (인과적).
|
||||
|
||||
Args:
|
||||
df: DatetimeIndex OHLC.
|
||||
dt: 시각 문자열.
|
||||
|
||||
Returns:
|
||||
drawdown %.
|
||||
"""
|
||||
if df.empty:
|
||||
return 0.0
|
||||
close_s = _close_series_from_df(df)
|
||||
bar_idx = _bar_index_at(df, dt)
|
||||
return _drawdown_pct_at_index(close_s, bar_idx)
|
||||
|
||||
|
||||
def live_sizing_enabled() -> bool:
|
||||
"""실거래 사이징을 시뮬 인과 tier와 정합할지."""
|
||||
return bool(GT_SIGNAL_CAUSAL)
|
||||
Reference in New Issue
Block a user