GT 총자산 비율 매수·leg 티어 배분과 시뮬/실거래 포지션 사이징을 통합한다.
타점·비중을 gt_model로 일반화하고, amount_krw 시각순 배분·EV/WF·상위 leg 대형 매수를 position_sizing과 시뮬 HTML(고정 ₩/회 비교)에 반영한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,9 +19,18 @@ from config import (
|
||||
LIVE_ORDER_KRW,
|
||||
LIVE_TRADING_ENABLED,
|
||||
SYMBOL,
|
||||
TRADING_FEE_RATE,
|
||||
)
|
||||
from deepcoin.ground_truth.ground_truth import load_ground_truth
|
||||
from deepcoin.matching.live_eval import evaluate_live_rules
|
||||
from deepcoin.matching.load_rules import load_monitor_rules
|
||||
from deepcoin.matching.position_sizing import (
|
||||
compute_buy_amount_krw,
|
||||
live_buy_asset_pct_scale,
|
||||
load_ev_wf_approved_rule_ids,
|
||||
top_leg_ids_by_forward_return,
|
||||
)
|
||||
from deepcoin.paths import resolve_ground_truth_file
|
||||
from deepcoin.ops.alert_message import build_rule_alert_message
|
||||
from deepcoin.ops.monitor import Monitor
|
||||
from deepcoin.paths import LIVE_TRADES_LOG
|
||||
@@ -40,6 +49,17 @@ class LiveTrader(Monitor):
|
||||
self._day_spent_krw: float = 0.0
|
||||
self._day_trades: int = 0
|
||||
self._day_pnl_krw: float = 0.0
|
||||
self._gt_trades: list[dict] = []
|
||||
self._large_legs: set[int] = set()
|
||||
self._approved_rules: set[str] = set()
|
||||
self._load_sizing_context()
|
||||
|
||||
def _load_sizing_context(self) -> None:
|
||||
"""GT leg·EV/WF 통과 규칙 캐시."""
|
||||
gt = load_ground_truth(resolve_ground_truth_file()) or {}
|
||||
self._gt_trades = gt.get("trades") or []
|
||||
self._large_legs = top_leg_ids_by_forward_return(self._gt_trades)
|
||||
self._approved_rules = load_ev_wf_approved_rule_ids()
|
||||
|
||||
def _reset_day_if_needed(self) -> None:
|
||||
"""날짜 변경 시 일별 한도 카운터 초기화."""
|
||||
@@ -61,7 +81,7 @@ class LiveTrader(Monitor):
|
||||
with LIVE_TRADES_LOG.open("a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
||||
|
||||
def _can_trade(self, rule_id: str) -> tuple[bool, str]:
|
||||
def _can_trade(self, rule_id: str, planned_krw: float | None = None) -> tuple[bool, str]:
|
||||
"""
|
||||
일·쿨다운·손실 한도 검사.
|
||||
|
||||
@@ -74,7 +94,8 @@ class LiveTrader(Monitor):
|
||||
self._reset_day_if_needed()
|
||||
if self._day_trades >= LIVE_MAX_TRADES_PER_DAY:
|
||||
return False, "일 최대 거래 수 초과"
|
||||
if self._day_spent_krw + LIVE_ORDER_KRW > LIVE_DAILY_KRW_MAX:
|
||||
need = float(planned_krw if planned_krw is not None else LIVE_ORDER_KRW)
|
||||
if self._day_spent_krw + need > LIVE_DAILY_KRW_MAX:
|
||||
return False, "일 주문 한도 초과"
|
||||
if self._day_pnl_krw <= -abs(LIVE_DAILY_LOSS_LIMIT_KRW):
|
||||
return False, "일 손실 한도 초과"
|
||||
@@ -83,6 +104,46 @@ class LiveTrader(Monitor):
|
||||
return False, f"규칙 쿨다운({LIVE_COOLDOWN_MIN}분)"
|
||||
return True, ""
|
||||
|
||||
def _resolve_buy_amount_krw(self, hit: dict[str, Any]) -> float:
|
||||
"""
|
||||
총자산·현금·EV/WF·leg 티어로 매수 원화 산출.
|
||||
|
||||
Args:
|
||||
hit: evaluate_live_rules 항목.
|
||||
|
||||
Returns:
|
||||
매수 원화.
|
||||
"""
|
||||
rid = hit["rule_id"]
|
||||
if rid not in self._approved_rules:
|
||||
return 0.0
|
||||
price = float(hit["close"])
|
||||
cash = 0.0
|
||||
qty = 0.0
|
||||
try:
|
||||
bal = self.load_balances_dict()
|
||||
sym = bal.get(SYMBOL, {})
|
||||
cash = float(sym.get("available_krw") or sym.get("krw") or 0)
|
||||
qty = float(sym.get("balance") or 0)
|
||||
except Exception:
|
||||
return 0.0
|
||||
scale = live_buy_asset_pct_scale(
|
||||
rid,
|
||||
hit["dt"],
|
||||
self._gt_trades,
|
||||
approved_rules=self._approved_rules,
|
||||
large_legs=self._large_legs,
|
||||
)
|
||||
return compute_buy_amount_krw(
|
||||
cash,
|
||||
qty,
|
||||
price,
|
||||
1.0,
|
||||
1.0,
|
||||
asset_pct_scale=scale,
|
||||
fee_rate=TRADING_FEE_RATE,
|
||||
)
|
||||
|
||||
def _execute_order(self, hit: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
매수·매도 주문 실행 또는 드라이런.
|
||||
@@ -95,7 +156,22 @@ class LiveTrader(Monitor):
|
||||
"""
|
||||
side = hit["side"]
|
||||
price = float(hit["close"])
|
||||
amount_krw = float(LIVE_ORDER_KRW)
|
||||
if side == "buy":
|
||||
amount_krw = self._resolve_buy_amount_krw(hit)
|
||||
if amount_krw <= 0:
|
||||
return {
|
||||
"ts": datetime.now().isoformat(timespec="seconds"),
|
||||
"rule_id": hit["rule_id"],
|
||||
"side": side,
|
||||
"signal_dt": hit["dt"],
|
||||
"price": price,
|
||||
"amount_krw": 0,
|
||||
"live_enabled": LIVE_TRADING_ENABLED,
|
||||
"ok": False,
|
||||
"message": "매수 스킵(EV/WF·leg·현금)",
|
||||
}
|
||||
else:
|
||||
amount_krw = float(LIVE_ORDER_KRW)
|
||||
record: dict[str, Any] = {
|
||||
"ts": datetime.now().isoformat(timespec="seconds"),
|
||||
"rule_id": hit["rule_id"],
|
||||
@@ -163,11 +239,23 @@ class LiveTrader(Monitor):
|
||||
|
||||
for hit in fired:
|
||||
rid = hit["rule_id"]
|
||||
ok, reason = self._can_trade(rid)
|
||||
if hit["side"] == "buy" and hit["rule_id"] not in self._approved_rules:
|
||||
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
||||
print(" skip: EV/WF 미통과 규칙")
|
||||
continue
|
||||
planned = (
|
||||
self._resolve_buy_amount_krw(hit)
|
||||
if hit["side"] == "buy"
|
||||
else float(LIVE_ORDER_KRW)
|
||||
)
|
||||
ok, reason = self._can_trade(rid, planned)
|
||||
print(f" [{hit['side']}] {rid} @ {hit['dt']}")
|
||||
if not ok:
|
||||
print(f" skip: {reason}")
|
||||
continue
|
||||
if hit["side"] == "buy" and planned <= 0:
|
||||
print(" skip: 매수금액 0")
|
||||
continue
|
||||
log = self._execute_order(hit)
|
||||
self._append_log(log)
|
||||
print(f" order: {log['message']} ok={log['ok']}")
|
||||
|
||||
Reference in New Issue
Block a user