왜: 체결이 없어도 마지막가는 유지인데, 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 (일회성)
63 lines
2.7 KiB
Python
63 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""웹 API vs diag_momentum_backtest CLI 동일조건 비교 (1위 · 5일 · 필터OFF)."""
|
|
import json, urllib.parse, urllib.request, time, sys
|
|
|
|
START, END = "2026-06-22", "2026-06-26"
|
|
# 1위 파라미터 (search_momentum_fast_20260627_015211.json rank=1)
|
|
QS = {
|
|
"start": START, "end": END,
|
|
"sl_pct": "1.5", "tp_pct": "2.0", "tp_max_pct": "2.0",
|
|
"slot_money": "300000", "slots": "20", "total_budget_krw": "6000000",
|
|
"cooldown_min": "1", "shoulder_min_high": "0.5", "shoulder_cut_pct": "0.15",
|
|
"trail_pct": "0.5", "trail_arm_pct": "0", "max_hold_bars": "0",
|
|
"ratchet_tiers": "0.5:0.1,1.0:0.3",
|
|
"time_start": "830", "time_end": "1530", "max_daily": "10",
|
|
"high_chase_thr": "0.99", "max_daily_chg": "50", "min_price": "1000",
|
|
"max_loss_krw": "200000", "min_margin": "0.2",
|
|
"use_defense_filters": "1",
|
|
"ob_filter": "0", "pg_filter": "0",
|
|
"max_spread_pct": "0.45",
|
|
"use_high_chase_filter": "0", "use_daily_range_filter": "0",
|
|
"use_ema_filter": "0", "use_rsi_max_filter": "0",
|
|
"pattern_breakout": "1", "pattern_pullback": "0",
|
|
"chase_lookback_min": "8", "pullback_lookback_min": "10",
|
|
"pullback_min_pct": "0.3", "pullback_max_pct": "3.0",
|
|
"setup_vol_max_mult": "0.8", "setup_bear_bars_min": "1",
|
|
"ema_fast_period": "9", "ema_slow_period": "21",
|
|
"mom_rsi_min": "50", "mom_rsi_max": "80",
|
|
"mom_vol_mult": "3", "mom_vol_win": "5",
|
|
"mom_time_end": "1220",
|
|
"mom_max_from_open_pct": "30", "mom_min_from_open_pct": "-999",
|
|
"universe": "history",
|
|
}
|
|
|
|
url = "http://127.0.0.1:5050/api/backtest/momentum?" + urllib.parse.urlencode(QS)
|
|
print(f"🌐 웹 API 호출: {START}~{END} 필터OFF ...")
|
|
t0 = time.time()
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=300) as r:
|
|
d = json.loads(r.read().decode())
|
|
except Exception as e:
|
|
print(f"❌ 웹 API 실패: {e}")
|
|
sys.exit(1)
|
|
el = time.time() - t0
|
|
s = d.get("summary") or {}
|
|
print(f"🌐 웹 API ({el:.0f}초): 손익 {s.get('total_pnl',0):+,.0f}원 | "
|
|
f"승률 {s.get('win_rate',0):.1f}% | 거래 {s.get('total_trades',0)} | "
|
|
f"PF {s.get('profit_factor',0):.2f}")
|
|
daily = {}
|
|
for t in d.get("trades") or []:
|
|
day = str(t.get("buy_time") or t.get("entry_time") or "")[:8]
|
|
x = daily.setdefault(day, [0, 0.0])
|
|
x[0] += 1
|
|
x[1] += float(t.get("pnl") or 0)
|
|
for day in sorted(daily):
|
|
n, p = daily[day]
|
|
print(f" {day}: {n:3d}거래 {p:+,.0f}원")
|
|
skip = (d.get("summary") or {}).get("skip_stats") or {}
|
|
if skip:
|
|
print(f" skip_stats: {skip}")
|
|
with open("logs/compare_web_api.json", "w", encoding="utf-8") as f:
|
|
json.dump({"summary": s, "daily": daily, "elapsed_sec": el}, f, ensure_ascii=False, indent=2)
|
|
print(" → logs/compare_web_api.json 저장")
|