feat: 새로운 안전 규칙 및 최적화 적용을 통한 트레이딩 시스템 개선
변경 사항 (Changes): 구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함. 스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함. 코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함. 시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함. 기대 효과 (Impact): 이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
This commit is contained in:
@@ -62,7 +62,7 @@ from database import TradeDB
|
||||
from kis_trader.backtest import breakout_backtest_common as bbc
|
||||
from kis_trader.backtest.backtest_portfolio_common import (
|
||||
merge_param_search_apply_source,
|
||||
portfolio_env_patch,
|
||||
strip_portfolio_keys_from_apply_patch,
|
||||
session_env_patch,
|
||||
)
|
||||
from kis_trader.backtest.param_search_cli_common import (
|
||||
@@ -97,6 +97,7 @@ from kis_trader.strategies.breakout import (
|
||||
breakout_min_bars_required,
|
||||
breakout_ui_to_engine_params,
|
||||
normalize_breakout_max_loss_krw,
|
||||
resolve_breakout_skip_hts_scan_dupes,
|
||||
)
|
||||
from kis_trader.engine.indicator_cache import attach_indicator_caches_to_params
|
||||
from kis_trader.utils.env import get_env_bool, get_env_from_db, get_env_float, get_env_int # noqa: E402
|
||||
@@ -143,6 +144,19 @@ def _parse_csv_strs(env_key: str, fallback: List[str]) -> List[str]:
|
||||
return out if out else list(fallback)
|
||||
|
||||
|
||||
def _parse_csv_bools(env_key: str, fallback: List[bool]) -> List[bool]:
|
||||
raw = get_env_from_db(env_key, "")
|
||||
if not raw or str(raw).strip() in ("", "None"):
|
||||
return list(fallback)
|
||||
out: List[bool] = []
|
||||
for chunk in str(raw).replace("|", ",").split(","):
|
||||
chunk = chunk.strip().lower()
|
||||
if not chunk:
|
||||
continue
|
||||
out.append(chunk in ("1", "true", "t", "y", "yes", "on"))
|
||||
return out if out else list(fallback)
|
||||
|
||||
|
||||
def _parse_csv_tiers(env_key: str, fallback: List[str]) -> List[str]:
|
||||
"""세미콜론(;) 구분 래칫 티어 후보 → 그리드 문자열 축.
|
||||
|
||||
@@ -188,10 +202,16 @@ def _bo_fixed_defaults() -> Dict[str, Any]:
|
||||
"""
|
||||
db = TradeDB()
|
||||
try:
|
||||
row = db.conn.execute(
|
||||
"SELECT * FROM env_config ORDER BY id DESC LIMIT 1"
|
||||
).fetchone()
|
||||
env = dict(row) if row else {}
|
||||
# config_breakout + env_config 병합 — SKIP_HTS 등이 env_config 단독 SELECT 에 없음
|
||||
if hasattr(db, "get_strategy_config_snapshot"):
|
||||
env = dict(db.get_strategy_config_snapshot("BREAKOUT") or {})
|
||||
merged = db.get_merged_env_snapshot() if hasattr(db, "get_merged_env_snapshot") else {}
|
||||
for k, v in (merged or {}).items():
|
||||
if k not in env or env.get(k) in (None, "", "None"):
|
||||
env[k] = v
|
||||
else:
|
||||
from kis_trader.backtest.backtest_portfolio_common import load_portfolio_env_row
|
||||
env = load_portfolio_env_row(db)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -254,6 +274,10 @@ def _bo_fixed_defaults() -> Dict[str, Any]:
|
||||
"eod_hm": (
|
||||
str(_pick("BREAKOUT_EOD_HM", default="15:15")).strip() or "15:15"
|
||||
),
|
||||
# 가짜돌파 필터 (0=OFF) — DB 컬럼 없으면 0
|
||||
"confirm_margin_pct": float(_pick("BREAKOUT_CONFIRM_MARGIN_PCT", default=0.0)),
|
||||
"body_min_pct": float(_pick("BREAKOUT_BODY_MIN_PCT", default=0.0)),
|
||||
"skip_hts_scan_dupes": resolve_breakout_skip_hts_scan_dupes(env),
|
||||
}
|
||||
|
||||
|
||||
@@ -324,228 +348,403 @@ def _breakout_grids() -> Dict[str, Dict[str, List]]:
|
||||
어깨축은 익절(7~15%) 미도달 시 고점 추적·되돌림 컷 — 스캘핑용 0.3%급과 구분.
|
||||
"""
|
||||
return {
|
||||
# [FAST] TRIGGER 거래량 = 유통주식 1분 회전율(%) 스윕 · 급등주 한도 스윕.
|
||||
# ※ vol_mult(직전봉평균×배수)는 그리드 밖 — FIXED vol_mult=0(OFF). HTS 조건 1.2배는 SCAN 전용.
|
||||
# env: BREAKOUT_GRID_FAST_MIN_TURNOVER_1M_PCT=0.05,0.1,0.15
|
||||
# [FAST] wide(7/15) Top 근방 스모크 — vol2.5·tp3/12·sl4·trail1.5·sh5/0.5·end1530 분지
|
||||
# 실매 앵커: chg25 · vol2/w1 · turn0.1 · price3000 · atr · trail3 · end1300
|
||||
"fast": {
|
||||
# ★ 당일시가 대비 누적 상승 한도(%). HTS 전일대비 등락과 다름.
|
||||
"max_daily_chg": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_MAX_DAILY_CHG", [15.0, 20.0, 25.0, 30.0],
|
||||
"BREAKOUT_GRID_FAST_MAX_DAILY_CHG", [25.0, 40.0],
|
||||
),
|
||||
"vol_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_VOL_MULT", [2.0, 2.5],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_VOL_WINDOW", [1, 10],
|
||||
),
|
||||
# ★ TRIGGER 1차 거래량 — 1분 거래량÷유통주식×100 (ka10001). vol_mult 와 별개.
|
||||
"min_turnover_1m_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_MIN_TURNOVER_1M_PCT", [0.05, 0.1, 0.15],
|
||||
"BREAKOUT_GRID_FAST_MIN_TURNOVER_1M_PCT", [0.1, 0.15],
|
||||
),
|
||||
"prev_chg_min": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_PREV_CHG_MIN", [0.5],
|
||||
),
|
||||
# ★ 과열컷 — 직전봉 등락 상한(%). 너무 달군 봉에 추격 진입 회피(휩쏘 방어).
|
||||
"prev_chg_max": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_PREV_CHG_MAX", [5.0, 10.0, 15.0],
|
||||
"BREAKOUT_GRID_FAST_PREV_CHG_MAX", [8.0, 20.0],
|
||||
),
|
||||
"min_price": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_MIN_PRICE", [3000.0],
|
||||
),
|
||||
# ★ 익절(%) — R:R 탐색축. 7/10/15.
|
||||
"tp_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_TP_PCT", [7.0, 10.0, 15.0],
|
||||
"BREAKOUT_GRID_FAST_TP_PCT", [3.0, 12.0],
|
||||
),
|
||||
# ★ 손절(%) — PF 직격 축. 2/3/4 (멘토 지적: 고정 3% 단일값 → 탐색).
|
||||
"sl_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_SL_PCT", [2.0, 3.0, 4.0],
|
||||
"BREAKOUT_GRID_FAST_SL_PCT", [4.0],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_SHOULDER_SMIN", [1.0, 2.0, 3.0],
|
||||
),
|
||||
# ★ 어깨컷(%) — 익절 미달 구간 고점 되돌림 컷. 0.5/0.8/1.2.
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_SHOULDER_CUT", [0.5, 0.8, 1.2],
|
||||
),
|
||||
# ★ 트레일(%) — 고점 추적 되돌림 폭. arm 은 고정(아래) 유지.
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_TRAIL_PCT", [1.0, 1.5, 2.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_TRAIL_ARM", [1.5],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_LOOKBACK_MIN", [1],
|
||||
),
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_CONFIRM_MARGIN", [0.0],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_BODY_MIN", [0.0],
|
||||
),
|
||||
# ★ 매매종료 시각 비교축 — 골든타임(10:30)만 vs 오전연장(11:30) vs 오후추격(13:00).
|
||||
# "오전장만 좋다" 가설을 데이터로 검증(오후 추격이 PF 를 갉아먹는지).
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_TIME_END_HM", [1030, 1130, 1300],
|
||||
),
|
||||
# ── 래칫(단계식 트레일) 축 ─────────────────────────────────────
|
||||
# ""(OFF·단일 어깨컷, 기준선) vs 2단/3단 티어. fast 는 cap 균등샘플이라
|
||||
# 조합이 늘어도 max-combos 안에서 OFF/ON 이 골고루 섞인다.
|
||||
# ※ 래칫 ON 이면 엔진에서 shoulder_min_high/cut 은 무시(else 분기)된다.
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_FAST_RATCHET_TIERS",
|
||||
["", "2:1.5,5:1.0", "3:1.5,6:1.0,10:0.7"],
|
||||
),
|
||||
# ── ATR 동적 손절 비교축 ─────────────────────────────────────
|
||||
# sl_mode='fixed'(기존 고정 sl_pct%) vs 'atr'(변동성 비례). atr 일 때만
|
||||
# atr_sl_mult 적용(손절%=ATR×배수/진입가, atr_sl_min/max_pct 캡).
|
||||
# fixed 조합에선 atr_sl_mult 가 무의미(중복) → cap 균등샘플이 흡수.
|
||||
"sl_mode": _parse_csv_strs(
|
||||
"BREAKOUT_GRID_FAST_SL_MODE", ["fixed", "atr"],
|
||||
),
|
||||
"atr_sl_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_ATR_SL_MULT", [1.5, 2.0, 2.5],
|
||||
"BREAKOUT_GRID_FAST_ATR_SL_MULT", [2.5],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_SHOULDER_SMIN", [2.0, 5.0],
|
||||
),
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_SHOULDER_CUT", [0.5, 0.8],
|
||||
),
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_TRAIL_PCT", [1.5, 3.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_TRAIL_ARM", [2.0, 3.0],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_LOOKBACK_MIN", [1, 5],
|
||||
),
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_CONFIRM_MARGIN", [0.2, 0.5],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_BODY_MIN", [0.0, 0.3],
|
||||
),
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_TIME_END_HM", [1300, 1530],
|
||||
),
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_FAST_RATCHET_TIERS",
|
||||
["", "5:2,10:1.5", "3:1.5,6:1.0,10:0.7"],
|
||||
),
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_MAX_HOLD_BARS", [60, 120],
|
||||
),
|
||||
# ── 호가필터(2단계 조인트) 축 — 기본 단일값=동작 불변 ──────────
|
||||
# max_spread_pct : 스프레드 상한(%) ↑완화
|
||||
# min_bid_ask_ratio : 매수/매도 잔량비 하한 ↓완화
|
||||
# ask_wall_max_qty : 돌파 저항 근처 매도벽 수량 상한 ↑완화 (돌파 전용)
|
||||
"max_spread_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_MAX_SPREAD_PCT", [0.45],
|
||||
),
|
||||
"min_bid_ask_ratio": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FAST_MIN_BID_ASK_RATIO", [0.85],
|
||||
"BREAKOUT_GRID_FAST_MIN_BID_ASK_RATIO", [0.85, 1.0],
|
||||
),
|
||||
"ask_wall_max_qty": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FAST_ASK_WALL_MAX_QTY", [5000],
|
||||
"BREAKOUT_GRID_FAST_ASK_WALL_MAX_QTY", [3000],
|
||||
),
|
||||
},
|
||||
# 5×4×4×5×4×3×3 ≈ 14,400 (무효 prev_chg 조합 제외)
|
||||
# [COARSE] wide Top 밴드 조금 넓게 (1차 스크리닝)
|
||||
"coarse": {
|
||||
# 급등주(당일시가 대비) — fast 와 동일 축. env BREAKOUT_GRID_COARSE_MAX_DAILY_CHG
|
||||
"max_daily_chg": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_MAX_DAILY_CHG", [15.0, 20.0, 25.0, 30.0],
|
||||
"BREAKOUT_GRID_COARSE_MAX_DAILY_CHG", [25.0, 30.0, 40.0, 50.0],
|
||||
),
|
||||
# 거래량 배수 — HTS 300%(3.0)보다 코드에서 2~5배 탐색 (핵심 축)
|
||||
"vol_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_VOL_MULT", [1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
|
||||
"BREAKOUT_GRID_COARSE_VOL_MULT", [2.0, 2.5, 3.0],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_COARSE_VOL_WINDOW", [1, 5, 10],
|
||||
),
|
||||
"min_turnover_1m_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_MIN_TURNOVER_1M_PCT", [0.1, 0.15, 0.3],
|
||||
),
|
||||
"prev_chg_min": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_PREV_CHG_MIN", [0.5, 1.0, 1.5, 2.0],
|
||||
"BREAKOUT_GRID_COARSE_PREV_CHG_MIN", [0.3, 0.5],
|
||||
),
|
||||
"prev_chg_max": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_PREV_CHG_MAX", [5.0, 8.0, 10.0, 15.0],
|
||||
"BREAKOUT_GRID_COARSE_PREV_CHG_MAX", [8.0, 10.0, 15.0, 20.0],
|
||||
),
|
||||
"min_price": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_MIN_PRICE", [2000.0, 3000.0],
|
||||
),
|
||||
"tp_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_TP_PCT", [3.0, 5.0, 7.0, 10.0, 15.0],
|
||||
"BREAKOUT_GRID_COARSE_TP_PCT", [3.0, 5.0, 10.0, 12.0],
|
||||
),
|
||||
"sl_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_SL_PCT", [1.5, 2.0, 3.0, 4.0],
|
||||
"BREAKOUT_GRID_COARSE_SL_PCT", [3.0, 4.0, 5.0],
|
||||
),
|
||||
"sl_mode": _parse_csv_strs(
|
||||
"BREAKOUT_GRID_COARSE_SL_MODE", ["fixed", "atr"],
|
||||
),
|
||||
"atr_sl_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_ATR_SL_MULT", [2.0, 2.5, 3.0],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_SHOULDER_SMIN", [1.0, 2.0, 3.0],
|
||||
"BREAKOUT_GRID_COARSE_SHOULDER_SMIN", [2.0, 3.0, 5.0],
|
||||
),
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_SHOULDER_CUT", [0.5, 0.8, 1.2],
|
||||
"BREAKOUT_GRID_COARSE_SHOULDER_CUT", [0.5, 0.8, 1.0],
|
||||
),
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_TRAIL_PCT", [1.5, 2.0, 3.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_TRAIL_ARM", [1.5, 2.0, 3.0],
|
||||
),
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_COARSE_RATCHET_TIERS",
|
||||
["", "5:2,10:1.5", "3:1.5,6:1.0,10:0.7", "2:1.5,5:1.0"],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_COARSE_LOOKBACK_MIN", [1, 10],
|
||||
"BREAKOUT_GRID_COARSE_LOOKBACK_MIN", [1, 5],
|
||||
),
|
||||
# 가짜돌파(휩쏘) 필터
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_CONFIRM_MARGIN", [0.0, 0.3],
|
||||
"BREAKOUT_GRID_COARSE_CONFIRM_MARGIN", [0.0, 0.2, 0.5],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_BODY_MIN", [0.0, 0.3],
|
||||
),
|
||||
# 10:30 골든타임 vs 11:30·13:00 (오후 추격 리스크)
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_COARSE_TIME_END_HM", [1030],
|
||||
"BREAKOUT_GRID_COARSE_TIME_END_HM", [1300, 1430, 1530],
|
||||
),
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_COARSE_MAX_HOLD_BARS", [0, 60, 120],
|
||||
),
|
||||
"max_spread_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_MAX_SPREAD_PCT", [0.30, 0.45],
|
||||
),
|
||||
"min_bid_ask_ratio": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_COARSE_MIN_BID_ASK_RATIO", [0.85, 1.0],
|
||||
),
|
||||
"ask_wall_max_qty": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_COARSE_ASK_WALL_MAX_QTY", [3000, 5000],
|
||||
),
|
||||
},
|
||||
# [FINE] 2026-07-15 wide Top 재설계 (best~+52k/6건 · mode~+32k/4건)
|
||||
# 실매 앵커(chg25/vol2/turn0.1/end1300/atr) + Top(#1·최빈) 포함. apply는 확인 후.
|
||||
"fine": {
|
||||
"max_daily_chg": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_MAX_DAILY_CHG", [25.0, 30.0, 40.0, 50.0],
|
||||
),
|
||||
"vol_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_VOL_MULT", [1.0, 1.5, 2.0, 2.5, 3.0, 4.0],
|
||||
"BREAKOUT_GRID_FINE_VOL_MULT", [2.0, 2.5, 3.0],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FINE_VOL_WINDOW", [1, 5, 10],
|
||||
),
|
||||
"min_turnover_1m_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_MIN_TURNOVER_1M_PCT", [0.1, 0.15, 0.3],
|
||||
),
|
||||
"prev_chg_min": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_PREV_CHG_MIN", [0.3, 0.5, 1.0, 1.5, 2.5],
|
||||
"BREAKOUT_GRID_FINE_PREV_CHG_MIN", [0.3, 0.5],
|
||||
),
|
||||
"prev_chg_max": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_PREV_CHG_MAX", [4.0, 6.0, 8.0, 12.0, 15.0],
|
||||
"BREAKOUT_GRID_FINE_PREV_CHG_MAX", [8.0, 10.0, 15.0, 20.0, 25.0],
|
||||
),
|
||||
"min_price": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_MIN_PRICE", [2000.0, 3000.0],
|
||||
),
|
||||
"tp_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_TP_PCT", [2.0, 3.0, 5.0, 7.0, 10.0],
|
||||
"BREAKOUT_GRID_FINE_TP_PCT", [3.0, 5.0, 10.0, 12.0, 15.0],
|
||||
),
|
||||
"sl_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_SL_PCT", [1.0, 1.5, 2.0, 3.0],
|
||||
"BREAKOUT_GRID_FINE_SL_PCT", [3.0, 4.0, 5.0],
|
||||
),
|
||||
"sl_mode": _parse_csv_strs(
|
||||
"BREAKOUT_GRID_FINE_SL_MODE", ["fixed", "atr"],
|
||||
),
|
||||
"atr_sl_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_ATR_SL_MULT", [2.0, 2.5, 3.0],
|
||||
),
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_TRAIL_PCT", [1.0, 1.5, 2.0],
|
||||
"BREAKOUT_GRID_FINE_TRAIL_PCT", [1.5, 2.0, 3.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_TRAIL_ARM", [1.5, 2.0, 3.0],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_SHOULDER_SMIN", [1.0, 2.0, 3.0, 5.0],
|
||||
"BREAKOUT_GRID_FINE_SHOULDER_SMIN", [2.0, 3.0, 5.0],
|
||||
),
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_SHOULDER_CUT", [0.5, 0.8, 1.2, 1.5],
|
||||
"BREAKOUT_GRID_FINE_SHOULDER_CUT", [0.5, 0.8, 1.0],
|
||||
),
|
||||
# 래칫(단계식 트레일) 후보 — ""(OFF·단일 어깨컷) vs 2단/3단 티어.
|
||||
# 래칫 ON 이면 shoulder_* 축은 무시되어 중복 조합이 생기지만 cap 샘플로 흡수됨.
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_FINE_RATCHET_TIERS",
|
||||
["", "2:1.5,5:1.0", "3:1.5,6:1.0,10:0.7"],
|
||||
["", "5:2,10:1.5", "3:1.5,6:1.0,10:0.7", "2:1.5,5:1.0"],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FINE_LOOKBACK_MIN", [1, 5, 10, 20],
|
||||
"BREAKOUT_GRID_FINE_LOOKBACK_MIN", [1, 5],
|
||||
),
|
||||
# 가짜돌파(휩쏘) 필터
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_CONFIRM_MARGIN", [0.0, 0.2, 0.5],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_BODY_MIN", [0.0, 0.3],
|
||||
),
|
||||
# 시간컷(분) — 0=OFF · 옆으로 기는 실패 돌파 정리
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FINE_MAX_HOLD_BARS", [0, 60],
|
||||
"BREAKOUT_GRID_FINE_MAX_HOLD_BARS", [0, 60, 120],
|
||||
),
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FINE_TIME_END_HM", [1030, 1130, 1300],
|
||||
"BREAKOUT_GRID_FINE_TIME_END_HM", [1300, 1430, 1530],
|
||||
),
|
||||
"max_spread_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_MAX_SPREAD_PCT", [0.30, 0.45, 0.60],
|
||||
),
|
||||
"min_bid_ask_ratio": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FINE_MIN_BID_ASK_RATIO", [0.85, 1.0],
|
||||
),
|
||||
"ask_wall_max_qty": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FINE_ASK_WALL_MAX_QTY", [3000, 5000],
|
||||
),
|
||||
},
|
||||
"full": {
|
||||
# [WIDE] 축 스크리닝 — 하루 fine 5건·WR100% 과적합 완화용.
|
||||
# Optuna 소수 trial로 유효 축·구간 먼저 찾고, 이후 fine 재설계.
|
||||
# 실매/직전 fine 앵커: chg25 · vol2/w1 · turn0.1 · price3000 · tp3 · sl4 · atr · trail3 · end1300
|
||||
# ※ SKIP_HTS 는 사용자 지시 없으면 false 유지(그리드에 True 넣지 않음).
|
||||
"wide": {
|
||||
"max_daily_chg": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_MAX_DAILY_CHG",
|
||||
[10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 50.0],
|
||||
),
|
||||
"vol_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_VOL_MULT", [1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0],
|
||||
"BREAKOUT_GRID_WIDE_VOL_MULT",
|
||||
[0.0, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_WIDE_VOL_WINDOW", [1, 3, 5, 10],
|
||||
),
|
||||
"min_turnover_1m_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_MIN_TURNOVER_1M_PCT",
|
||||
[0.03, 0.05, 0.1, 0.15, 0.2, 0.3],
|
||||
),
|
||||
"prev_chg_min": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_PREV_CHG_MIN", [0.3, 0.5, 1.0, 1.5, 2.0, 3.0],
|
||||
"BREAKOUT_GRID_WIDE_PREV_CHG_MIN",
|
||||
[0.3, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0],
|
||||
),
|
||||
"prev_chg_max": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_PREV_CHG_MAX", [4.0, 6.0, 8.0, 10.0, 12.0, 15.0, 20.0],
|
||||
"BREAKOUT_GRID_WIDE_PREV_CHG_MAX",
|
||||
[6.0, 8.0, 10.0, 12.0, 15.0, 20.0, 25.0],
|
||||
),
|
||||
"min_price": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_MIN_PRICE", [1000.0, 2000.0, 3000.0, 5000.0],
|
||||
),
|
||||
"tp_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_TP_PCT", [2.0, 3.0, 5.0, 7.0, 10.0, 15.0, 20.0],
|
||||
"BREAKOUT_GRID_WIDE_TP_PCT",
|
||||
[3.0, 5.0, 7.0, 10.0, 12.0, 15.0, 20.0],
|
||||
),
|
||||
"sl_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_SL_PCT", [1.0, 1.5, 2.0, 3.0, 5.0],
|
||||
"BREAKOUT_GRID_WIDE_SL_PCT", [1.5, 2.0, 3.0, 4.0, 5.0],
|
||||
),
|
||||
"sl_mode": _parse_csv_strs(
|
||||
"BREAKOUT_GRID_WIDE_SL_MODE", ["fixed", "atr"],
|
||||
),
|
||||
"atr_sl_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_ATR_SL_MULT", [1.5, 2.0, 2.5, 3.0],
|
||||
),
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_TRAIL_PCT", [0.0, 1.0, 1.5, 2.0, 3.0, 4.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_TRAIL_ARM", [0.0, 1.0, 1.5, 2.0, 3.0],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_SHOULDER_SMIN", [1.0, 2.0, 3.0, 5.0, 7.0],
|
||||
),
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_SHOULDER_CUT", [0.5, 0.8, 1.0, 1.2, 1.5, 2.0],
|
||||
),
|
||||
# OFF + 늦은 보호 + 기존 fine 선호 주변 (조기만 단독으로 몰지 않음)
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_WIDE_RATCHET_TIERS",
|
||||
["", "5:2,10:1.5", "3:1.5,6:1.0,10:0.7", "2:1.5,5:1.0"],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_WIDE_LOOKBACK_MIN", [1, 5, 10, 20],
|
||||
),
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_CONFIRM_MARGIN", [0.0, 0.2, 0.5, 1.0],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_BODY_MIN", [0.0, 0.3, 0.5],
|
||||
),
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_WIDE_MAX_HOLD_BARS", [0, 30, 60, 120],
|
||||
),
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_WIDE_TIME_END_HM", [1030, 1130, 1300, 1430, 1530],
|
||||
),
|
||||
"max_spread_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_MAX_SPREAD_PCT", [0.30, 0.45, 0.60],
|
||||
),
|
||||
"min_bid_ask_ratio": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_WIDE_MIN_BID_ASK_RATIO", [0.70, 0.85, 1.0],
|
||||
),
|
||||
"ask_wall_max_qty": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_WIDE_ASK_WALL_MAX_QTY", [3000, 5000, 8000],
|
||||
),
|
||||
},
|
||||
# [FULL] fine + 최대 폭 (HTS 밴드 바깥까지 탐색)
|
||||
"full": {
|
||||
"max_daily_chg": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_MAX_DAILY_CHG",
|
||||
[10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0],
|
||||
),
|
||||
"vol_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_VOL_MULT", [0.0, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_VOL_WINDOW", [1, 3, 5, 10],
|
||||
),
|
||||
"min_turnover_1m_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_MIN_TURNOVER_1M_PCT",
|
||||
[0.03, 0.05, 0.1, 0.15, 0.2, 0.3],
|
||||
),
|
||||
"skip_hts_scan_dupes": _parse_csv_bools(
|
||||
"BREAKOUT_GRID_FULL_SKIP_HTS_SCAN_DUPES", [False, True],
|
||||
),
|
||||
"prev_chg_min": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_PREV_CHG_MIN",
|
||||
[0.3, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
|
||||
),
|
||||
"prev_chg_max": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_PREV_CHG_MAX",
|
||||
[6.0, 8.0, 10.0, 12.0, 15.0, 20.0, 25.0],
|
||||
),
|
||||
"min_price": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_MIN_PRICE", [1000.0, 2000.0, 3000.0, 5000.0],
|
||||
),
|
||||
"tp_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_TP_PCT", [3.0, 5.0, 7.0, 10.0, 15.0, 20.0],
|
||||
),
|
||||
"sl_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_SL_PCT", [1.0, 1.5, 2.0, 3.0, 4.0, 5.0],
|
||||
),
|
||||
"sl_mode": _parse_csv_strs(
|
||||
"BREAKOUT_GRID_FULL_SL_MODE", ["fixed", "atr"],
|
||||
),
|
||||
"atr_sl_mult": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_ATR_SL_MULT", [1.0, 1.5, 2.0, 2.5, 3.0],
|
||||
),
|
||||
"trail_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_TRAIL_PCT", [1.0, 1.5, 2.0, 3.0],
|
||||
),
|
||||
"trail_arm_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_TRAIL_ARM", [0.0, 1.0, 1.5, 2.0],
|
||||
),
|
||||
"shoulder_min_high_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_SHOULDER_SMIN", [1.0, 2.0, 3.0, 5.0],
|
||||
"BREAKOUT_GRID_FULL_SHOULDER_SMIN", [1.0, 2.0, 3.0, 5.0, 7.0],
|
||||
),
|
||||
"shoulder_cut_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_SHOULDER_CUT", [0.5, 0.8, 1.2, 1.5],
|
||||
"BREAKOUT_GRID_FULL_SHOULDER_CUT", [0.5, 0.8, 1.0, 1.2, 1.5, 2.0],
|
||||
),
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_MAX_HOLD_BARS", [0, 30, 60],
|
||||
"ratchet_tiers": _parse_csv_tiers(
|
||||
"BREAKOUT_GRID_FULL_RATCHET_TIERS",
|
||||
["", "2:1.5,5:1.0", "3:1.5,6:1.0,10:0.7", "2:2.0,6:1.2,12:0.8"],
|
||||
),
|
||||
"lookback_min": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_LOOKBACK_MIN", [1, 5, 10, 20],
|
||||
"BREAKOUT_GRID_FULL_LOOKBACK_MIN", [1, 5, 10, 20, 30],
|
||||
),
|
||||
"vol_window": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_VOL_WINDOW", [1, 3, 5],
|
||||
),
|
||||
# 가짜돌파(휩쏘) 필터
|
||||
"confirm_margin_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_CONFIRM_MARGIN", [0.0, 0.2, 0.5],
|
||||
"BREAKOUT_GRID_FULL_CONFIRM_MARGIN", [0.0, 0.2, 0.5, 1.0],
|
||||
),
|
||||
"body_min_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_BODY_MIN", [0.0, 0.3, 0.5],
|
||||
),
|
||||
"max_hold_bars": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_MAX_HOLD_BARS", [0, 30, 60, 90],
|
||||
),
|
||||
"time_end_hm": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_TIME_END_HM", [1030, 1130, 1300, 1500],
|
||||
"BREAKOUT_GRID_FULL_TIME_END_HM", [1030, 1130, 1300, 1500, 1530],
|
||||
),
|
||||
"max_spread_pct": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_MAX_SPREAD_PCT", [0.20, 0.30, 0.45, 0.60, 0.80],
|
||||
),
|
||||
"min_bid_ask_ratio": _parse_csv_floats(
|
||||
"BREAKOUT_GRID_FULL_MIN_BID_ASK_RATIO", [0.50, 0.70, 0.85, 1.0],
|
||||
),
|
||||
"ask_wall_max_qty": _parse_csv_ints(
|
||||
"BREAKOUT_GRID_FULL_ASK_WALL_MAX_QTY", [2000, 3000, 5000, 8000, 12000],
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -592,6 +791,7 @@ def _get_breakout_field_map() -> Dict[str, Tuple[str, Any]]:
|
||||
"slot_money": ("BREAKOUT_SLOT_MONEY", lambda v: str(int(float(v)))),
|
||||
"entry_mode": ("BREAKOUT_ENTRY_MODE", lambda v: str(v).strip().lower()),
|
||||
"intrabar_slippage_pct": ("BREAKOUT_INTRABAR_SLIPPAGE_PCT", lambda v: str(float(v))),
|
||||
# skip_hts_scan_dupes: full 그리드 탐색만, apply 제외
|
||||
}
|
||||
|
||||
|
||||
@@ -606,8 +806,9 @@ def _apply_to_db(best_params: dict) -> None:
|
||||
)
|
||||
|
||||
patch = _patch_from_breakout_merged(best_params)
|
||||
patch.update(portfolio_env_patch("BREAKOUT", best_params))
|
||||
patch.update(session_env_patch("BREAKOUT", best_params))
|
||||
# 슬롯·동시보유·총한도 제외 — Optuna/Grid 가 slot×종목수로 한도를 덮지 않음
|
||||
patch = strip_portfolio_keys_from_apply_patch(patch, "BREAKOUT")
|
||||
if not patch:
|
||||
print("DB 적용할 파라미터가 없습니다.")
|
||||
return
|
||||
@@ -682,6 +883,10 @@ def evaluate_breakout_param_combo(
|
||||
engine_params["_ob_ask_wall_max_qty"] = float(ui_params["ask_wall_max_qty"])
|
||||
if ui_params.get("_orderbook_filter_enabled") is not None:
|
||||
engine_params["_orderbook_filter_enabled"] = bool(ui_params["_orderbook_filter_enabled"])
|
||||
if "skip_hts_scan_dupes" in ui_params:
|
||||
engine_params["skip_hts_scan_dupes"] = bool(ui_params["skip_hts_scan_dupes"])
|
||||
elif "skip_hts_scan_dupes" not in engine_params:
|
||||
engine_params["skip_hts_scan_dupes"] = resolve_breakout_skip_hts_scan_dupes()
|
||||
if cache_holder:
|
||||
engine_params.update(cache_holder)
|
||||
engine_params["slot_money"] = float(slot_money)
|
||||
@@ -955,8 +1160,8 @@ def run_search(
|
||||
|
||||
db = TradeDB()
|
||||
try:
|
||||
row = db.conn.execute("SELECT * FROM env_config ORDER BY id DESC LIMIT 1").fetchone()
|
||||
env_row = dict(row) if row else {}
|
||||
from kis_trader.backtest.backtest_portfolio_common import load_portfolio_env_row
|
||||
env_row = load_portfolio_env_row(db)
|
||||
finally:
|
||||
db.close()
|
||||
fee_rate, sell_tax, slot_from_env = bbc.fee_and_slot_from_env(env_row)
|
||||
@@ -1315,13 +1520,13 @@ def run_search(
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
|
||||
from kis_trader.backtest.param_search_dates import resolve_param_search_range
|
||||
week_ago, today = resolve_param_search_range("BREAKOUT", lookback_days=7)
|
||||
|
||||
parser = argparse.ArgumentParser(description="돌파매매 백테스트 파라미터 Grid Search (BreakoutStrategy + run_breakout_backtest)")
|
||||
parser.add_argument("--start", default=week_ago, help="시작일 (YYYY-MM-DD)")
|
||||
parser.add_argument("--end", default=today, help="종료일 (YYYY-MM-DD)")
|
||||
parser.add_argument("--mode", default="fast", choices=["fast", "coarse", "fine", "full"],
|
||||
parser.add_argument("--start", default=week_ago, help="시작일 (YYYY-MM-DD, 거래일 보정)")
|
||||
parser.add_argument("--end", default=today, help="종료일 (YYYY-MM-DD, 주말·휴장이면 이전 장운영일)")
|
||||
parser.add_argument("--mode", default="fast", choices=["fast", "coarse", "fine", "wide", "full"],
|
||||
help="탐색 모드: fast(그리드→균등200·~10–15분) / coarse / fine / full")
|
||||
parser.add_argument(
|
||||
"--max-combos", type=int, default=None, dest="max_combos",
|
||||
|
||||
Reference in New Issue
Block a user