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

@@ -7,7 +7,10 @@ param_search_apply_snapshot.py — 파라미터 탐색 결과 JSON → insert_en
MOMENTUM search_momentum_*.json top[].merged_params
SCALP search_*.json (rsi_oversold) top[].db_snapshot 우선, 없으면 merged에서 생성
BREAKOUT search_breakout_*.json top[].merged_params
BREAKOUT optuna_breakout_*.json results[N-1] (Optuna — top 없음)
MOMENTUM optuna_momentum_*.json results[N-1] (Optuna)
TAIL search_tail_*.json results[N-1].params (정렬된 순서)
TAIL optuna_tail_*.json results[N-1] (Optuna)
UPDOW updow_param_*.json top[N-1].apply_cfg + tf
사용 예:
@@ -44,6 +47,17 @@ from kis_trader.backtest.backtest_portfolio_common import ( # noqa: E402
)
def _ranked_items(data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Grid ``top[]`` 또는 Optuna ``results[]`` — rank 1 = index 0."""
top = data.get("top")
if isinstance(top, list) and top:
return top
results = data.get("results")
if isinstance(results, list) and results:
return results
return []
def _env_bool_10(v: Any) -> str:
if isinstance(v, bool):
return "1" if v else "0"
@@ -61,10 +75,12 @@ def _detect_strategy(data: Dict[str, Any], path: str) -> str:
return "UPDOW"
if base.startswith("search_tail_") or base.startswith("tail_search_"):
return "TAIL"
if base.startswith("search_breakout_"):
if base.startswith("search_breakout_") or base.startswith("optuna_breakout_"):
return "BREAKOUT"
if base.startswith("search_momentum_"):
if base.startswith("search_momentum_") or base.startswith("optuna_momentum_"):
return "MOMENTUM"
if base.startswith("optuna_tail_"):
return "TAIL"
if isinstance(data.get("code"), str) and len(str(data.get("code")).strip()) == 6:
if "tf" in data and isinstance(data.get("top"), list):
@@ -546,16 +562,18 @@ def main(argv: Optional[List[str]] = None) -> int:
print("⚠️ JSON 에 code 없음 → updow_stock_config 건너뜀")
return 0
# ── top[] 기반 (MOMENTUM / SCALP / BREAKOUT) ─────────────────
top = data.get("top") or []
if not top:
print("❌ JSON 에 top 배열이 없습니다.")
# ── top[] (Grid) 또는 results[] (Optuna) — MOMENTUM / SCALP / BREAKOUT ──
ranked = _ranked_items(data)
if not ranked:
print("❌ JSON 에 top[] 또는 results[] 배열이 없습니다.")
return 8
if rank > len(top):
print(f"❌ rank 범위 초과 (1~{len(top)})")
if rank > len(ranked):
print(f"❌ rank 범위 초과 (1~{len(ranked)})")
return 5
item = top[rank - 1]
item = ranked[rank - 1]
if str(data.get("engine") or "").lower() == "optuna":
print(f" (Optuna results[{rank - 1}], trial=#{item.get('optuna_trial_number')})")
pnl = int(item.get("total_pnl") or 0)
if pnl <= 0 and not args.allow_non_positive_pnl:
print(f"⚠️ total_pnl={pnl} ≤ 0 → 중단. 적용하려면 --allow-non-positive-pnl")