ls증권 히스토리 구독 넣음

This commit is contained in:
Your Name
2026-07-30 18:05:07 +09:00
parent 61bec4bd1d
commit 67eab24603
1593 changed files with 135733 additions and 1232 deletions

View File

@@ -79,6 +79,7 @@ from kis_trader.backtest.backtest_portfolio_common import (
strip_portfolio_keys_from_apply_patch,
session_env_patch,
)
from kis_trader.backtest.optuna_common import attach_daily_stability
from kis_trader.backtest.param_search_cli_common import (
add_portfolio_cli_args,
add_search_filter_cli_args,
@@ -367,9 +368,6 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.85, 1.0],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", ["off"]),
"trail_drop_pct": _parse_csv_floats(f"{p}TRAIL_DROP_PCT", [0.0, 35.0]),
"trail_arm_krw": _parse_csv_floats(f"{p}TRAIL_ARM_KRW", [5000.0]),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW", [150000.0, 200000.0],
),
@@ -469,12 +467,6 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.85, 1.0],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", [
"off",
"5000:40,10000:30,20000:20",
]),
"trail_drop_pct": _parse_csv_floats(f"{p}TRAIL_DROP_PCT", [0.0, 35.0]),
"trail_arm_krw": _parse_csv_floats(f"{p}TRAIL_ARM_KRW", [5000.0, 10000.0]),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW", [100000.0, 150000.0, 200000.0],
),
@@ -576,23 +568,22 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"rsi_threshold": _parse_csv_floats(
f"{p}RSI_THRESHOLD", [75.0, 78.0, 85.0, 90.0],
),
# --- 반전 패턴 ON/OFF (실매 DB와 정합: 망치는 BASE 고정 True, 아래만 스윕) ---
# 빼고 싶으면 해당 줄 주석 처리. 값만 고정: [False] 또는 [True]
# 스윕: [False, True] / env: TAIL_GRID_FINE_PATTERN_*
"pattern_pin": _parse_csv_bools(f"{p}PATTERN_PIN", [False, True]),
"pattern_engulfing": _parse_csv_bools(f"{p}PATTERN_ENGULFING", [True]),
"pattern_piercing": _parse_csv_bools(f"{p}PATTERN_PIERCING", [True]),
# 희귀 패턴 — 실매 기본 False. 과적합·노이즈 많으면 아래 3줄 통째 주석 or [False]만
"pattern_harami": _parse_csv_bools(f"{p}PATTERN_HARAMI", [False, True]),
"pattern_doji": _parse_csv_bools(f"{p}PATTERN_DOJI", [False, True]),
"pattern_morning_star": _parse_csv_bools(
f"{p}PATTERN_MORNING_STAR", [False, True],
),
"max_spread_pct": _parse_csv_floats(f"{p}MAX_SPREAD_PCT", [0.30, 0.45]),
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.85, 1.0],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", [
"off",
"5000:40,10000:30,20000:20",
]),
"trail_drop_pct": _parse_csv_floats(
f"{p}TRAIL_DROP_PCT", [0.0, 35.0],
),
"trail_arm_krw": _parse_csv_floats(
f"{p}TRAIL_ARM_KRW", [5000.0, 10000.0],
),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW", [100000.0, 150000.0, 200000.0],
),
@@ -707,19 +698,6 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.50, 0.70, 0.85, 1.0, 1.2],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", [
"off",
"5000:40,10000:30,20000:20",
"30000:50,100000:35,300000:25",
"50000:60,200000:45,500000:30",
"10000:50,50000:40,200000:30",
]),
"trail_drop_pct": _parse_csv_floats(
f"{p}TRAIL_DROP_PCT", [0.0, 20.0, 25.0, 35.0, 45.0, 55.0],
),
"trail_arm_krw": _parse_csv_floats(
f"{p}TRAIL_ARM_KRW", [0.0, 5000.0, 10000.0, 20000.0, 30000.0],
),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW",
[30000.0, 50000.0, 100000.0, 150000.0, 200000.0, 300000.0, 500000.0],
@@ -851,18 +829,6 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.50, 0.70, 0.85, 1.0, 1.2],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", [
"off",
"5000:40,10000:30,20000:20",
"30000:50,100000:35,300000:25",
"50000:60,200000:45,500000:30",
]),
"trail_drop_pct": _parse_csv_floats(
f"{p}TRAIL_DROP_PCT", [0.0, 20.0, 25.0, 35.0, 45.0, 55.0],
),
"trail_arm_krw": _parse_csv_floats(
f"{p}TRAIL_ARM_KRW", [0.0, 3000.0, 5000.0, 10000.0, 20000.0, 30000.0],
),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW",
[30000.0, 50000.0, 100000.0, 150000.0, 200000.0, 300000.0, 500000.0],
@@ -981,19 +947,6 @@ def _tail_grids(mode: str) -> Dict[str, List[Any]]:
"min_bid_ask_ratio": _parse_csv_floats(
f"{p}MIN_BID_ASK_RATIO", [0.40, 0.50, 0.70, 0.85, 1.0, 1.2, 1.5],
),
"trail_tiers": _parse_semi_strings(f"{p}TRAIL_TIERS", [
"off",
"5000:40,10000:30,20000:20",
"30000:50,100000:35,300000:25",
"50000:60,200000:45,500000:30",
"10000:50,50000:40,200000:30",
]),
"trail_drop_pct": _parse_csv_floats(
f"{p}TRAIL_DROP_PCT", [0.0, 15.0, 20.0, 25.0, 35.0, 45.0, 55.0],
),
"trail_arm_krw": _parse_csv_floats(
f"{p}TRAIL_ARM_KRW", [0.0, 3000.0, 5000.0, 10000.0, 20000.0, 30000.0],
),
"max_loss_krw": _parse_csv_floats(
f"{p}MAX_LOSS_KRW",
[30000.0, 50000.0, 100000.0, 150000.0, 200000.0, 300000.0, 500000.0],
@@ -1029,9 +982,6 @@ TAIL_GRID_AXIS_HINTS_KO: Dict[str, str] = {
"shoulder_min_high": "어깨 발동: 진입가 대비 최소 수익(0.003=0.3%) 후 max_price 추적",
"shoulder_cut_pct": "어깨 폭: 갱신된 max_price 대비 되돌림(0.002=0.2%, 매도선=max×(1-cut))",
"ratchet_tiers": "청산방식: off=단일 어깨컷 / '수익%:컷%,…'=다단 래칫 트레일",
"trail_tiers": "당일손익 다단 트레일 'off'/'수익원:컷%,…' — 벌수록 컷% 타이트(이익 보존), 일단위 표본",
"trail_drop_pct": "당일손익 단일 트레일(고점대비 %, 0=OFF) — tier='off' 일 때만 의미",
"trail_arm_krw": "단일 트레일 발동 최소수익(원) — tier='off' & drop>0 일 때만 의미",
"max_daily": "종목당 일일 매수 안전판(무한루프 방지, 기본 20)",
"symbol_daily_loss_limit_pct": "종목당 당일 실현손익 손실한도(%·양수, slot 대비) — 재진입 차단",
"symbol_daily_loss_limit_krw": "종목당 당일 실현손익 손실한도(원·양수) — KRW·% 중 먼저 닿는 쪽",
@@ -1044,6 +994,9 @@ TAIL_GRID_AXIS_HINTS_KO: Dict[str, str] = {
"pattern_pin": "핀바(긴 아래꼬리 단일봉) 진입 패턴 ON/OFF",
"pattern_engulfing": "장악형(2봉 반전) 진입 패턴 ON/OFF",
"pattern_piercing": "관통형(2봉 반전) 진입 패턴 ON/OFF",
"pattern_harami": "하라미(안쪽형 2봉 반전) 진입 패턴 ON/OFF — fine 스윕",
"pattern_doji": "도지(십자형) 진입 패턴 ON/OFF — fine 스윕",
"pattern_morning_star": "샛별(모닝스타 3봉 반전) 진입 패턴 ON/OFF — fine 스윕",
"max_loss_krw": "1회 최대 금액 손실(원, 트레일 미발동 시)",
"limit_atr_mult": "ATR 지정가 깊이(anchorATR×배수) — limit_atr 전용, 클수록 체결↓",
"entry_mode": "진입: align=다음봉 시가 / limit_atr=지정가(틱 체결 재생)",
@@ -1052,6 +1005,11 @@ TAIL_GRID_AXIS_HINTS_KO: Dict[str, str] = {
"max_daily_change": "급등주 필터(당일 등락률 상한, %)",
"trail_pct": "보조 트레일 폭(고점 대비, 0=OFF). 래칫 ON이면 1단계보다 arm이 커야 효과",
"trail_arm_pct": "보조 트레일 무장: 진입×(1+arm) 도달 후 (0=즉시)",
"whipsaw_enabled": "휩쏘 필터 ON/OFF — 실매 TAIL_WHIPSAW_FILTER_ENABLED (현재 false 포함)",
"whipsaw_subbar_sec": "휩쏘 미니봉(초) — 기본 30 · TPE [15,30,45,60]",
"whipsaw_lookback_sec": "휩쏘 룩백(초) — 기본 90 · TPE [60,90,120,180]",
"whipsaw_dip_pct": "휩쏘 지지이탈 하락폭(분율) — 기본 0.003",
"whipsaw_recovery_tol_pct": "휩쏘 회복 허용(분율) — 기본 0.001",
"max_spread_pct": "호가 스프레드 상한(%) — kiwoom_0d 본체 재계산(6/25~)",
"min_bid_ask_ratio": "총매수/총매도 잔량비 하한(꼬리지지) — kiwoom_0d 본체 재계산(6/25~)",
}
@@ -1088,23 +1046,11 @@ def evaluate_tail_param_combo(
test_params["_ob_max_spread_pct"] = float(combo["max_spread_pct"])
if "min_bid_ask_ratio" in combo and combo.get("min_bid_ask_ratio") is not None:
test_params["_ob_min_bid_ask_ratio"] = float(combo["min_bid_ask_ratio"])
# 당일손익 트레일 익절 — tier(우선) 또는 단일 drop 이 켜졌을 때만 시뮬 게이트 ON.
_tier_combo = str(combo.get("trail_tiers") or "").strip().lower()
_tier_on = _tier_combo not in ("", "off")
if _tier_on:
test_params["_backtest_daily_profit_trail"] = True
test_params.setdefault("daily_profit_mode", "trailing")
test_params["daily_trail_tiers"] = str(combo["trail_tiers"]).strip()
elif float(combo.get("trail_drop_pct") or 0) > 0:
test_params["_backtest_daily_profit_trail"] = True
test_params.setdefault("daily_profit_mode", "trailing")
test_params["daily_trail_drop_pct"] = float(combo["trail_drop_pct"])
if combo.get("trail_arm_krw") is not None:
test_params["daily_trail_arm_krw"] = float(combo["trail_arm_krw"])
else:
_arm_env = os.environ.get("TAIL_GRID_TRAIL_ARM_KRW", "")
if str(_arm_env).strip() != "":
test_params["daily_trail_arm_krw"] = float(_arm_env)
# 당일손익 다단트레일 — 그리드/Optuna 탐색 제외. 시뮬은 DB/env(SHORT_DAILY_PROFIT_*) 고정 추종.
# (과거 combo 키 trail_tiers/drop/arm 이 남아 있어도 무시)
for _k in ("trail_tiers", "trail_drop_pct", "trail_arm_krw",
"daily_trail_tiers", "daily_trail_drop_pct", "daily_trail_arm_krw"):
test_params.pop(_k, None)
if cache_holder:
test_params.update(cache_holder)
if log_verdict_by_code:
@@ -1139,7 +1085,7 @@ def evaluate_tail_param_combo(
):
return None
return {
return attach_daily_stability({
"params": combo,
"apply_cfg": {**base_params, **combo},
"total_trades": total_trades,
@@ -1155,7 +1101,7 @@ def evaluate_tail_param_combo(
"skipped_micro_buys": int(
(meta.get("skip_stats") or {}).get("skipped_micro_buys") or 0
),
}
}, trades)
def evaluate_param_chunk(
@@ -1340,22 +1286,16 @@ def run_search(
base_params["scan_interval_min"] = scan_interval_min
base_params["timeframe"] = tail_tf
from kis_trader.engine.tail_tick_replay import (
tail_backtest_tick_fallback_ohlc as _tail_tick_fallback,
tail_backtest_use_tick_db as _tail_use_tick,
tail_backtest_use_tick_exit as _tail_use_tick_exit,
)
base_params.setdefault("backtest_tick_fallback_ohlc", _tail_tick_fallback(None))
# ── 틱재생(ws_ticks) — 기본 ON (실매 체결 정합) ──────────────────────
# 3분봉 OHLC(open→high→low→close 가정)는 손절보다 익절을 먼저 체결하는
# 낙관 편향을 만든다. 모멘텀·돌파와 동일하게 tail_tick_replay 기본값(ON)을
# 그대로 따르되, params/env(TAIL_BACKTEST_USE_TICK_DB)로 오버라이드 가능.
# 절대규칙: 파람서치는 OHLC 폴백으로 숫자 변조 금지
base_params.setdefault("backtest_use_tick_db", _tail_use_tick(None))
base_params.setdefault("backtest_use_tick_exit", _tail_use_tick_exit(None))
base_params["backtest_tick_fallback_ohlc"] = False
if base_params.get("backtest_use_tick_db") or base_params.get("backtest_use_tick_exit"):
if base_params.get("backtest_tick_fallback_ohlc"):
logger.info("📌 틱재생(ws_ticks): ON + OHLC 폴백 ON")
else:
logger.info("📌 틱재생(ws_ticks): ON — OHLC 폴백 OFF (틱 없는 구간 스킵)")
logger.info("📌 틱재생(ws_ticks): ON — OHLC 폴백 강제 OFF (정합 절대규칙)")
logger.info(
f"📅 데이터 로드: {start} ~ {end} | TF={tail_tf} | "
@@ -1816,28 +1756,7 @@ def _tail_params_to_env_map(p: Dict[str, Any]) -> Dict[str, str]:
env_map["RISK_PCT_PER_TRADE"] = str(p["risk_pct"])
if "kelly_mult" in p:
env_map["KELLY_MULTIPLIER"] = str(p["kelly_mult"])
# 당일손익 트레일 — 백테 trail_tiers 축 → 실매 DAILY_PROFIT_* / SHORT_* 동기
if "trail_tiers" in p:
tier = str(p.get("trail_tiers") or "").strip()
if tier.lower() == "off":
env_map["DAILY_PROFIT_TRAIL_TIERS"] = ""
env_map["SHORT_DAILY_PROFIT_TRAIL_TIERS"] = ""
else:
env_map["DAILY_PROFIT_TRAIL_TIERS"] = tier
env_map["SHORT_DAILY_PROFIT_TRAIL_TIERS"] = tier
env_map["DAILY_PROFIT_MODE"] = "trailing"
env_map["SHORT_DAILY_PROFIT_MODE"] = "trailing"
if "trail_drop_pct" in p and p.get("trail_drop_pct") is not None:
drop = max(0.0, float(p["trail_drop_pct"]))
env_map["DAILY_PROFIT_TRAIL_DROP_PCT"] = str(drop)
env_map["SHORT_DAILY_PROFIT_TRAIL_DROP_PCT"] = str(drop)
if drop > 0:
env_map["DAILY_PROFIT_MODE"] = "trailing"
env_map["SHORT_DAILY_PROFIT_MODE"] = "trailing"
if "trail_arm_krw" in p and p.get("trail_arm_krw") is not None:
arm = max(0.0, float(p["trail_arm_krw"]))
env_map["DAILY_PROFIT_TRAIL_ARM_KRW"] = str(int(arm))
env_map["SHORT_DAILY_PROFIT_TRAIL_ARM_KRW"] = str(int(arm))
# 당일손익 다단트레일 — apply-best 로 DB 덮어쓰기 금지 (운영 UI/DB만 변경).
from kis_trader.engine.orderbook_env import orderbook_params_to_env_patch
env_map.update(orderbook_params_to_env_patch("TAIL", p))
return env_map