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,6 +7,9 @@ kis_trader/engine/daily_profit_halt.py — 일일 익절 목표 달성 시 신
목표 판정: 원(KRW) · 운용한도 대비 %(PCT) **둘 중 하나라도** 달성 시 트리거.
분모(%) : ``DAILY_PROFIT_TARGET_BUDGET_KRW`` 또는 ON 전략 ``*_TOTAL_BUDGET_KRW`` 합.
**활성화(``*_TARGET_ENABLED``)** ON + 목표 설정 → 달성 시 **무조건** 신규매수 차단.
**신규매수 중단(``*_HALT_NEW_BUYS``)** = 목표와 무관한 **수동** 매수 잠금(별도 스위치).
매도(손절·익절)는 계속 — **신규 매수만** 차단.
모든 임계값 env/DB — 하드코딩 금지.
"""
@@ -73,13 +76,23 @@ def _mode(common_key: str, strategy_key: str, default: str = "fixed") -> str:
return (g or default).lower()
def _halt_new_buys_flag(common_key: str, strategy_key: str, default: bool = False) -> bool:
"""수동 신규매수 중단 — 전략 서브값 우선 → 마스터 → 기본 OFF."""
raw = str(get_env_from_db(strategy_key, "")).strip()
if raw != "":
return get_env_bool(strategy_key, default)
return get_env_bool(common_key, default)
def load_global_profit_target() -> Dict[str, Any]:
return {
"enabled": get_env_bool("DAILY_PROFIT_TARGET_ENABLED", False),
"krw": max(0.0, float(get_env_float("DAILY_PROFIT_TARGET_KRW", 0.0))),
"pct": max(0.0, float(get_env_float("DAILY_PROFIT_TARGET_PCT", 0.0))),
"budget_krw": max(0.0, float(get_env_float("DAILY_PROFIT_TARGET_BUDGET_KRW", 0.0))),
"halt_new_buys": get_env_bool("DAILY_PROFIT_HALT_NEW_BUYS", True),
"halt_new_buys": _halt_new_buys_flag(
"DAILY_PROFIT_HALT_NEW_BUYS", "DAILY_PROFIT_HALT_NEW_BUYS", False,
),
"notify_mm": get_env_bool("DAILY_PROFIT_NOTIFY_MM", True),
# 트레일링 익절 (당일 손익 고점 추적) — fixed 기본이라 미설정 시 동작 불변
"mode": _mode("DAILY_PROFIT_MODE", "DAILY_PROFIT_MODE", "fixed"),
@@ -110,7 +123,11 @@ def load_strategy_profit_target(strategy_id: str) -> Dict[str, Any]:
0.0,
),
"budget_env": _STRATEGY_BUDGET_ENV.get(pfx, f"{pfx}_TOTAL_BUDGET_KRW"),
"halt_new_buys": get_env_bool(f"{pfx}_DAILY_PROFIT_HALT_NEW_BUYS", True),
"halt_new_buys": _halt_new_buys_flag(
"DAILY_PROFIT_HALT_NEW_BUYS",
f"{pfx}_DAILY_PROFIT_HALT_NEW_BUYS",
False,
),
# 트레일링 익절 — 전략 서브값 우선 → 마스터 폴백 (fixed 기본)
"mode": _mode("DAILY_PROFIT_MODE", f"{pfx}_DAILY_PROFIT_MODE", "fixed"),
# 다단계 트레일 tier — 전략 서브값 우선 → 마스터 폴백 (비면 단일 drop_pct)
@@ -290,6 +307,31 @@ def _format_hit_detail(
return " · ".join(parts)
def describe_profit_guard_startup(cfg: Dict[str, Any], *, scope: str = "마스터") -> str:
"""기동 로그용 — 설정이 매수에 미치는 영향을 한 줄로."""
if cfg.get("halt_new_buys"):
return (
f"⛔ [일일익절·{scope}] 수동 신규매수 중단 ON — "
f"목표·손익과 무관하게 신규매수 차단 (매도·손절 유지)"
)
if not cfg.get("enabled"):
return (
f" [일일익절·{scope}] 손익 감시 OFF — "
f"목표 달성 시에도 신규매수 차단 없음"
)
if not _guard_active(cfg):
return (
f" [일일익절·{scope}] 손익 감시 ON 이지만 목표 미설정 "
f"(금액·%·트레일 없음) — 달성 차단 없음"
)
krw = float(cfg.get("krw") or 0)
pct = float(cfg.get("pct") or 0)
return (
f"🎯 [일일익절·{scope}] 손익 감시 ON — 목표 {krw:,.0f}원 / {pct:.2f}% "
f"· 달성 시 신규매수 차단 (매도·손절 유지)"
)
class DailyProfitHaltGuard:
"""
Orchestrator 가 주입 — ``buy_allowed(strategy_id)`` 로 신규 매수 차단 여부 판단.
@@ -329,7 +371,17 @@ class DailyProfitHaltGuard:
sid = _strategy_prefix(strategy_id)
gcfg = load_global_profit_target()
if gcfg.get("halt_new_buys", True) and _guard_active(gcfg):
# 1) 수동 신규매수 중단 (목표·손익과 무관)
if gcfg.get("halt_new_buys"):
self._throttled_log(
sid,
"⛔ [신규매수중단·총합] 수동 중단 ON → 신규매수 차단",
)
return False, "탈락-신규매수중단(총합)"
# 2) 일일익절 — 활성화 ON이면 목표 달성 시 무조건 신규매수 차단
if _guard_active(gcfg):
gpnl, gcnt = self._global_pnl_fn(today)
gbudget = resolve_global_operating_budget_krw(self._active_strategies_fn())
gpeak = self._update_peak(f"global:{today}", today, gpnl)
@@ -341,7 +393,15 @@ class DailyProfitHaltGuard:
return False, "탈락-일일익절(총합)"
scfg = load_strategy_profit_target(sid)
if scfg.get("halt_new_buys", True) and _guard_active(scfg):
if scfg.get("halt_new_buys"):
self._throttled_log(
sid,
f"⛔ [신규매수중단·{sid}] 수동 중단 ON → 해당전략 신규매수 차단",
)
return False, f"탈락-신규매수중단({sid})"
if _guard_active(scfg):
spnl, scnt = self._strategy_pnl_fn(today, sid)
sbudget = resolve_strategy_budget_krw(sid)
speak = self._update_peak(f"{sid}:{today}", today, spnl)