Files
kis_bot/remove/legacy_root/_verify_shared_e2e_breakout.py
Your Name 6d2a706a48 커밋 1 — 실매 가격 TTL 구멍 (본체)
왜: 체결이 없어도 마지막가는 유지인데, TTL로 None 만들고 매도/EOD를 건너뛰어 8/5 돌파·금요일 leftover가 남음. 호가필터 TTL 구멍과 같은 병.

넣을 파일

신규: kis_trader/engine/live_sell_price.py
kis_trader/strategies/base.py (_ws_last_quote, _resolve_sell_price)
전략: momentum.py scalping.py tail_catch.py breakout.py range_break.py dart_strategy.py updow_strategy.py updown_feed.py us_momentum.py
WS: ws_manager.py kis_ws.py kiwoom_ws.py ls_ws.py kis_ws_overseas.py
kis_trader/web/live_config_schema.py (WS_PRICE_MAX_AGE_SEC 기본 0)
database.py (키 주석 + legacy/ sys.path)
kis_trader/execution/order_manager.py (잔고 있는데 40240000 ghost_purge 금지 — 같은 EOD 사고)
EOD가 min_hold에 안 막히게 손본 momentum_hts_logic.py / scalping_engine.py / tail_engine.py (이 대화에서 손본 부분만 확인 후)
문서: docs/like_mcp.md/db_erd.md code_architecture.md (가격 TTL 문구)
메시지 초안

fix: 매수·매도 현재가를 TTL로 버리지 않음 (마지막 RAM)
횡보·체결 공백을 죽은 캐시로 오인해 None 처리하면 손절·EOD가 스킵된다.
호가필터와 같이 나이는 무시하고 마지막 체결가를 유지한다. EOD는 매수가 폴백.
영향: 실매 O / 백테·옵투나 봉 경로 거의 무관 (엔진 식 변경 아님)

커밋 2 — 루트 정리 (remove/ vs legacy/)
왜: 루트 단독봇·테스트는 지울 보관함으로. 웹·알람이 아직 쓰는 모듈은 remove에 두면 나중에 폴더째 삭제 때 깨짐.

넣을 파일

이동: 미사용 → remove/legacy_root/ (래퍼, ETF/키움 옛봇, 테스트, kiwoom_rest_api 등)
이동: 사용 중 → legacy/ (holding_bot kis_holding_ver1 news_analyzer kis_long_ver1/2)
신규: kis_trader/utils/legacy_root.py legacy/README.md remove/README.md
import 경로: backtest_web.py mm_butler.py mm_remote.py updow_holding_cfg.py dbband_stock_cfg.py param_search_updow*.py dbband_param_search.py param_search_apply_snapshot.py verify_three_paths.py
docs/like_mcp.md/code_architecture.md 수동 노트
메시지 초안

chore: 미사용 루트는 remove/, 웹·알람 구모듈은 legacy/
remove는 나중에 통째 삭제 예정. holding_bot·news_analyzer·kis_long은
ensure_legacy_root로 legacy/만 본다.
빼기: scratch/set_ws_price_max_age_zero.py (일회성)
2026-08-18 00:13:17 +09:00

157 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
BREAKOUT 공유메모리 틱 배선 end-to-end 검증 — 동일 데이터로 dict vs SharedTicksMapping 백테 2회 대조.
거래가 확인된 실 파라미터(merged_params)로 주간 백테를 두 번 돌린다:
A) ticks_by_code = 원본 dict (기존 경로)
B) ticks_by_code = SharedTicksMapping (공유메모리 컬럼 경로)
두 결과의 trades 리스트와 요약통계가 완전히 동일해야 한다(bit-identical).
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from database import TradeDB
from kis_trader.backtest import breakout_backtest_common as bbc
from kis_trader.backtest.param_search_breakout import _load_candles_for_search
from kis_trader.backtest.breakout_backtest_common import resolve_breakout_universe
from kis_trader.backtest.breakout_tick_loader import load_breakout_ticks_by_code
from kis_trader.backtest.shared_ticks import build_shared_ticks
START = "2026-06-28"
END = "2026-07-05"
JSON = "./kis_trader/backtest/results/search_breakout_fast_20260705_031411.json"
def run_backtest(codes_candles, universe, ticks_by_code, engine_params, slot_money,
max_stocks, total_budget, start_key, end_key):
p = dict(engine_params)
meta = {"start_key": start_key, "end_key": end_key}
trades = bbc.run_breakout_backtest_web_aligned(
codes_candles, p, universe,
slot_money=slot_money, fee_rate=0.00015, sell_tax=0.0018,
max_stocks=max_stocks, total_budget_krw=total_budget,
ticks_by_code=ticks_by_code,
orderbook_by_code={}, program_by_code={},
meta_out=meta,
)
stats = bbc.summarize_breakout_trades(trades, total_budget_krw=total_budget, period_days=1)
return trades, stats
def norm_trades(trades):
out = []
for t in trades:
out.append(tuple(sorted(
(k, (round(v, 6) if isinstance(v, float) else v))
for k, v in t.items()
)))
return out
def main():
meta_j = json.load(open(JSON))
r0 = meta_j["top"][0]
engine_params = dict(r0["merged_params"])
slot_money = float(meta_j.get("slot_money") or 300_000)
max_stocks = int(meta_j.get("max_stocks") or 20)
total_budget = float(meta_j.get("total_budget_krw") or 6_000_000)
lookback_min = int(engine_params.get("lookback_min", 1))
vol_window = int(engine_params.get("vol_window", 7))
start_key = START.replace("-", "") + "0000"
end_key = END.replace("-", "") + "2359"
print("⏳ 캔들 로드...")
codes_candles = _load_candles_for_search(
START, END, lookback_min,
fixed={"lookback_min": lookback_min, "vol_window": vol_window},
)
print(f" 종목 {len(codes_candles):,}")
print("⏳ 유니버스 로드...")
universe, src, n_bins, _iv = resolve_breakout_universe(
START.replace("-", ""), END.replace("-", ""),
use_saved_history=True, strategy_id="BREAKOUT",
)
print(f" 유니버스 슬롯 {len(universe or {}):,} (src={src})")
print("⏳ ws_ticks 로드...")
db = TradeDB()
try:
ticks_dict, tick_rows = load_breakout_ticks_by_code(
db, start_key, end_key, set(codes_candles.keys()),
)
finally:
db.close()
print(f"{tick_rows:,}건 / 종목 {len(ticks_dict):,}")
# 거래를 넓게 커버하는 변형들 (진입/체결/휩쏘 경로 커버).
_base_loose = {
"vol_mult": 1.0, "min_turnover_1m_pct": 0.0, "cooldown_min": 0,
"max_daily": 100, "prev_chg_min": -50.0, "prev_chg_max": 50.0,
"time_start_hm": 900, "time_end_hm": 1520, "min_price": 0,
}
variants = [
("baseline(top)", {}),
("loose", {"vol_mult": 1.0, "min_turnover_1m_pct": 0.0, "cooldown_min": 0,
"max_daily": 50}),
("loose_wide", dict(_base_loose)),
("loose_slip", {**_base_loose, "intrabar_slippage_pct": 0.3}),
("loose_lb3", {**_base_loose, "lookback_min": 3}),
("tight_sl", {"vol_mult": 1.0, "sl_mode": "atr", "atr_sl_mult": 1.0,
"trail_pct": 0.5}),
("wide", {"vol_mult": 1.0, "shoulder_cut_pct": 0.5, "trail_pct": 2.0}),
]
print("▶ 공유메모리 store 빌드...")
store = build_shared_ticks(ticks_dict)
if store is None:
print("❌ build_shared_ticks None — 검증 불가")
return 1
all_ok = True
total_trades_checked = 0
try:
for name, override in variants:
ep = dict(engine_params)
ep.update(override)
trades_a, stats_a = run_backtest(codes_candles, universe, ticks_dict, ep,
slot_money, max_stocks, total_budget, start_key, end_key)
shared_map = store.attach_mapping()
trades_b, stats_b = run_backtest(codes_candles, universe, shared_map, ep,
slot_money, max_stocks, total_budget, start_key, end_key)
na, nb = norm_trades(trades_a), norm_trades(trades_b)
ok = True
if len(na) != len(nb):
ok = False
detail = f"거래수 {len(na)} vs {len(nb)}"
else:
diff = [i for i in range(len(na)) if na[i] != nb[i]]
if diff:
ok = False
detail = f"내용불일치 {len(diff)}건 (idx {diff[0]})"
print(" dict :", dict(na[diff[0]]))
print(" shared:", dict(nb[diff[0]]))
else:
detail = "동일"
for k in ("total_trades", "total_pnl", "win_rate", "pf", "mdd"):
if stats_a.get(k) != stats_b.get(k):
ok = False
detail += f" | {k} {stats_a.get(k)}{stats_b.get(k)}"
total_trades_checked += len(na)
all_ok = all_ok and ok
print(f" {'' if ok else ''} {name:16s} 거래 {len(na):3d} · 손익 {stats_a['total_pnl']:+,.0f} · {detail}")
finally:
store.unlink()
print(f"\n대조한 총 거래 {total_trades_checked}건 (변형 {len(variants)}개)")
print("🎉 bit-identical 통과" if all_ok else "🚨 불일치 발생")
return 0 if all_ok else 2
if __name__ == "__main__":
sys.exit(main())