변경 사항 (Changes): 구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함. 스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함. 코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함. 시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함. 기대 효과 (Impact): 이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
159 lines
6.1 KiB
Python
159 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
웹 백테스트 전용 env 타임라인 — 실매·파람서치 경로에는 부착하지 않는다.
|
|
|
|
``backtest_env_timeline=True`` (웹 bt_meta) 일 때만 ``_env_timeline`` 을 params 에
|
|
넣고, 포트폴리오/단일 루프에서 ``apply_env_timeline_at`` 으로 TRIGGER·포트폴리오
|
|
숫자를 시점별 갱신한다.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, Optional
|
|
|
|
from kis_trader.backtest.env_timeline import EnvTimeline, build_env_timeline
|
|
|
|
# params 에 남겨 두는 구조·메타 키 — env 스냅샷이 덮어쓰지 않음
|
|
_PRESERVE_PARAM_KEYS = frozenset({
|
|
"_env_timeline",
|
|
"_env_timeline_evt_key",
|
|
"_universe_timeline",
|
|
"_momentum_universe_timeline",
|
|
"_universe_timeline_meta",
|
|
"backtest_env_timeline",
|
|
"portfolio_mode",
|
|
"scan_interval_min",
|
|
"backtest_universe_scan_at",
|
|
"backtest_vol_fill_cap_pct",
|
|
"strict_fill_verify",
|
|
"limit_fill_slip_pct",
|
|
"fee_rate",
|
|
"sell_tax",
|
|
"slot_money",
|
|
"max_stocks",
|
|
"total_budget_krw",
|
|
"_backtest_period_start_key",
|
|
"_backtest_log_verdict_by_code",
|
|
"_portfolio_skip_stats",
|
|
"share_denom_by_code",
|
|
})
|
|
|
|
|
|
def _truthy(val: Any) -> bool:
|
|
if val is None:
|
|
return False
|
|
s = str(val).strip().lower()
|
|
return s in ("1", "true", "t", "y", "yes", "on")
|
|
|
|
|
|
def backtest_env_timeline_enabled(params: Optional[Dict[str, Any]] = None) -> bool:
|
|
"""웹 백테 env 타임라인 ON 여부 — 기본 False (파람서치·실매 영향 없음)."""
|
|
if params is None:
|
|
return False
|
|
if params.get("backtest_env_timeline") is not None:
|
|
return _truthy(params.get("backtest_env_timeline"))
|
|
return params.get("_env_timeline") is not None
|
|
|
|
|
|
def attach_backtest_env_timeline_to_params(
|
|
engine_params: Dict[str, Any],
|
|
meta_out: Optional[Dict[str, Any]],
|
|
strategy_id: str,
|
|
) -> None:
|
|
"""웹 백테 구간 env 타임라인 부착 — meta_out['backtest_env_timeline']=True 일 때만."""
|
|
if not backtest_env_timeline_enabled(engine_params):
|
|
if meta_out and _truthy(meta_out.get("backtest_env_timeline")):
|
|
engine_params["backtest_env_timeline"] = True
|
|
else:
|
|
return
|
|
db = (meta_out or {}).get("db")
|
|
start_key = str((meta_out or {}).get("start_key") or "")
|
|
end_key = str((meta_out or {}).get("end_key") or "")
|
|
start_ymd = start_key[:8] if len(start_key) >= 8 else ""
|
|
end_ymd = end_key[:8] if len(end_key) >= 8 else ""
|
|
if not start_ymd or not end_ymd:
|
|
return
|
|
tl = build_env_timeline(db, start_ymd, end_ymd)
|
|
if tl is None:
|
|
return
|
|
engine_params["_env_timeline"] = tl
|
|
engine_params["_strategy_id_for_env_tl"] = str(strategy_id or "").strip().upper()
|
|
if meta_out is not None:
|
|
meta_out["backtest_env_timeline"] = True
|
|
meta_out["env_timeline_snapshots"] = tl.snapshot_count
|
|
|
|
|
|
def engine_defaults_from_env_row(
|
|
strategy_id: str,
|
|
env_row: Dict[str, Any],
|
|
) -> Dict[str, Any]:
|
|
"""전략별 get_*_defaults_from_db — env_row 스냅샷만 사용 (DB 재조회 없음).
|
|
|
|
반환값은 반드시 **엔진 비율**(0.02=2%) 이어야 한다.
|
|
웹이 ``ui% → breakout_ui_to_engine_params`` 한 뒤 타임라인이 UI% 를 다시
|
|
덮어쓰면 trail/shoulder 가 100배 커져 청산·슬롯이 붕괴한다 (BREAKOUT 전용 함정).
|
|
"""
|
|
sid = str(strategy_id or "").strip().upper()
|
|
if sid in ("SHORT", "TAIL"):
|
|
from kis_trader.engine.tail_engine import get_tail_defaults_from_db
|
|
return get_tail_defaults_from_db(env_row=env_row)
|
|
if sid == "MOMENTUM":
|
|
from kis_trader.engine.momentum_engine import get_momentum_defaults_from_db
|
|
return get_momentum_defaults_from_db(env_row=env_row)
|
|
if sid == "SCALP":
|
|
from kis_trader.engine.scalping_engine import get_scalping_defaults_from_db
|
|
return get_scalping_defaults_from_db(env_row=env_row)
|
|
if sid == "BREAKOUT":
|
|
from kis_trader.backtest.breakout_backtest_common import get_breakout_defaults_from_env_row
|
|
from kis_trader.strategies.breakout import breakout_ui_to_engine_params
|
|
|
|
# get_breakout_defaults_from_env_row = 웹 폼용 UI% → 엔진 비율로 변환 후 적용
|
|
ui = get_breakout_defaults_from_env_row(env_row)
|
|
eng = breakout_ui_to_engine_params(ui)
|
|
# buy_params 동기화·invest_cap 이 읽는 별칭 (값은 엔진 비율)
|
|
eng["sl_pct"] = float(eng.get("stop_loss_pct", 0.0) or 0.0)
|
|
eng["tp_pct"] = float(eng.get("take_profit_pct", 0.0) or 0.0)
|
|
if eng.get("shoulder_min_high") is not None:
|
|
eng["shoulder_min_high_pct"] = float(eng["shoulder_min_high"])
|
|
# ui_to_engine 이 빼먹는 트리거 스냅샷 플래그
|
|
for k in ("ob_filter_enabled", "pg_filter_enabled", "max_spread_pct"):
|
|
if k in ui:
|
|
eng[k] = ui[k]
|
|
return eng
|
|
if sid == "RANGE_BREAK":
|
|
from kis_trader.engine.range_break_engine import get_range_break_defaults_from_db
|
|
return get_range_break_defaults_from_db(env_row=env_row)
|
|
if sid == "DBBAND":
|
|
from kis_trader.engine.dbband_engine import get_dbband_defaults_from_db
|
|
return get_dbband_defaults_from_db(env_row=env_row)
|
|
return {}
|
|
|
|
|
|
def apply_env_timeline_at(
|
|
params: Dict[str, Any],
|
|
candle_time: str,
|
|
strategy_id: Optional[str] = None,
|
|
) -> bool:
|
|
"""
|
|
봉 시각에 맞는 env 스냅샷을 params 에 반영. 스냅샷 변경 시에만 True.
|
|
|
|
포트폴리오 루프 변수(max_stocks, slot_money 등)는 호출측에서 params 재읽기.
|
|
"""
|
|
tl: Optional[EnvTimeline] = params.get("_env_timeline")
|
|
if tl is None:
|
|
return False
|
|
snap, evt_key = tl.snapshot_at(candle_time)
|
|
if not snap or not evt_key:
|
|
return False
|
|
if evt_key == params.get("_env_timeline_evt_key"):
|
|
return False
|
|
sid = str(
|
|
strategy_id or params.get("_strategy_id_for_env_tl") or ""
|
|
).strip().upper()
|
|
defaults = engine_defaults_from_env_row(sid, snap)
|
|
for k, v in defaults.items():
|
|
if k in _PRESERVE_PARAM_KEYS:
|
|
continue
|
|
params[k] = v
|
|
params["_env_timeline_evt_key"] = evt_key
|
|
return True
|