feat: Add new files and enhance backtesting functionality

Changes:
- Introduced new files for strategy definitions and study names.
- Enhanced `backtest_web.py` with functions to handle integer display prices and trade data formatting.
- Updated backtesting logic to incorporate end-of-day (EOD) parameters for breakout and momentum strategies.
- Added EOD configuration options in the database and parameter search files.

Impact:
- These changes improve the modularity and usability of the backtesting framework, allowing for better integration of EOD strategies and clearer trade data presentation.
This commit is contained in:
2026-07-06 19:11:34 +09:00
parent 336d637b72
commit 78edb75e01
33 changed files with 1479 additions and 303 deletions

View File

@@ -25,6 +25,7 @@ from kis_trader.engine.momentum_engine import (
effective_tp_pct_from_params,
eval_momentum_buy_at_index,
)
from kis_trader.strategies.base import is_strategy_eod_bar
from kis_trader.engine.indicator_cache import (
attach_indicator_caches_to_params,
get_indicator_cache_from_params,
@@ -244,7 +245,6 @@ def _process_sells_for_scan(
scan_key: str,
*,
params: Dict[str, Any],
force_eod_exit: bool,
ticks_by_code: Optional[Dict[str, Dict[str, List[Dict]]]],
all_trades: List[Dict],
tick_exit_count: int,
@@ -253,6 +253,7 @@ def _process_sells_for_scan(
) -> Tuple[int, int]:
"""스캔 시각까지 틱·OHLC 청산 (실매 루프: 매도 먼저)."""
bar_t = scan_key[:12]
is_eod = is_strategy_eod_bar(bar_t, params, "MOMENTUM")
for code in list(portfolio.keys()):
ctx = ctx_by_code.get(code)
if ctx is None:
@@ -265,9 +266,6 @@ def _process_sells_for_scan(
pos = portfolio[code]
if str(pos.get("entry_time") or "")[:12] == bar_t:
continue
day = bar_t[:8]
is_eod_raw = (idx == len(candles) - 1) or (candles[idx + 1]["candle_time"][:8] != day)
is_eod = is_eod_raw and force_eod_exit and _is_minute_tail_scan(scan_key, scan_sec)
entry_time = str(pos.get("entry_time") or "")
sold = False
@@ -500,7 +498,6 @@ def run_momentum_backtest_portfolio(
"""시각순 포트폴리오 백테스트 — MOMENTUM 전용."""
rsi_period = int(params.get("rsi_period", 3))
min_bars = max(rsi_period + 5, 6)
force_eod_exit = _to_bool(params.get("force_eod_exit"), False)
sl_pct = abs(float(params.get("sl_pct", 0.015)))
tp_pct = effective_tp_pct_from_params(params)
max_stocks = _max_stocks_from_params(params)
@@ -574,7 +571,6 @@ def run_momentum_backtest_portfolio(
tick_exit_count, ohlc_exit_count = _process_sells_for_scan(
portfolio, ctx_by_code, scan_key,
params=params,
force_eod_exit=force_eod_exit,
ticks_by_code=ticks_by_code,
all_trades=all_trades,
tick_exit_count=tick_exit_count,
@@ -680,8 +676,7 @@ def run_momentum_backtest_portfolio(
day = t[:8]
if str(portfolio[code]["entry_time"])[:12] == str(t)[:12]:
continue
is_eod_raw = (idx == len(candles) - 1) or (candles[idx + 1]["candle_time"][:8] != day)
is_eod = is_eod_raw and force_eod_exit
is_eod = is_strategy_eod_bar(t, params, "MOMENTUM")
cur_c_info = {
"open": float(c["open"]),
"high": float(c["high"]),