Files
kis_bot/scripts/verify_live_portfolio_align.py
2026-07-30 18:05:07 +09:00

126 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""실매 ↔ 백테 포트폴리오·유니버스 슬롯 정합 검증."""
from __future__ import annotations
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from datetime import datetime as dt
from typing import Dict, List
from kis_trader.utils.live_portfolio_common import (
filter_candidates_by_history_universe,
live_portfolio_budget_full,
live_universe_slot_align_enabled,
portfolio_strategy_key,
resolve_live_buy_qty,
resolve_live_total_budget_krw,
slot_key_from_dt,
)
class _FakeDB:
def __init__(self, rows: List[Dict]):
self._rows = rows
def get_universe_at(
self, *, strategy_id: str, at_time: str, history_source: str = "kiwoom",
) -> List[Dict]:
eligible = [
r for r in self._rows
if r["event_time"] <= at_time
and r.get("strategy_id", strategy_id) == strategy_id
]
if not eligible:
return []
et = max(r["event_time"] for r in eligible)
return [
{"code": r["code"], "name": r["name"]}
for r in eligible if r["event_time"] == et
]
def _ok(msg: str) -> None:
print(f" OK {msg}")
def _fail(msg: str) -> None:
print(f" FAIL {msg}")
raise SystemExit(1)
def main() -> None:
print("=== live_portfolio_common 단위 ===")
when = dt(2026, 7, 2, 9, 31, 25)
sk = slot_key_from_dt(when, 1)
if sk != "202607020931":
_fail(f"slot_key {sk}")
_ok(f"slot_key={sk}")
if portfolio_strategy_key("SHORT") != "TAIL":
_fail("SHORT→TAIL mapping")
_ok("SHORT→TAIL")
tb = resolve_live_total_budget_krw("BREAKOUT", max_stocks=20, slot_money=300_000)
if tb <= 0:
_fail(f"total_budget={tb}")
_ok(f"BREAKOUT total_budget={tb:,.0f}")
holdings = {"005930": {"buy_price": 70000, "qty": 5}}
full = live_portfolio_budget_full(holdings, "BREAKOUT", 300_000, 20)
_ok(f"budget_full={full}")
qty, invest, rej = resolve_live_buy_qty(
70000, {}, "BREAKOUT", 300_000, max_stocks=20, invest_cap=300_000,
)
if qty < 1 or rej:
_fail(f"buy_qty qty={qty} rej={rej}")
_ok(f"buy_qty qty={qty} invest={invest:,.0f}")
db = _FakeDB([
{"event_time": "2026-07-02 09:30:00", "code": "005930", "name": "삼성"},
{"event_time": "2026-07-02 09:30:00", "code": "000660", "name": "SK"},
{"event_time": "2026-07-02 09:31:10", "code": "005930", "name": "삼성"},
])
cands = [
{"code": "005930", "name": "삼성"},
{"code": "035720", "name": "카카오"},
]
out, dropped = filter_candidates_by_history_universe(
cands, db, "BREAKOUT", when=when,
)
if len(out) != 1 or out[0]["code"] != "005930" or dropped != 1:
_fail(f"universe filter out={out} dropped={dropped}")
_ok("universe history ∩ live candidates")
print("=== 전략 import ===")
from kis_trader.strategies import ( # noqa: WPS433
BreakoutStrategy,
DbBandStrategy,
MomentumStrategy,
RangeBreakStrategy,
ScalpingStrategy,
TailCatchStrategy,
)
for cls in (
BreakoutStrategy, ScalpingStrategy, MomentumStrategy,
RangeBreakStrategy, TailCatchStrategy, DbBandStrategy,
):
_ok(cls.__name__)
print("=== 슬롯 정합 플래그 ===")
for sid in ("BREAKOUT", "SCALP", "MOMENTUM", "RANGE_BREAK", "SHORT", "UPDOW", "DBBAND"):
en = live_universe_slot_align_enabled(sid)
print(f" {sid}: universe_slot={en}")
print("\n✅ verify_live_portfolio_align 전부 통과")
if __name__ == "__main__":
try:
main()
except SystemExit:
sys.exit(1)