이번에 들어간 내용
한투 호가 = 2번째 앱키 전용 키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger. LS RAM 합집합 후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로. 분봉 쓰레기 → 다음 소스 봉 통째 그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true. 파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개. 같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
"""
|
||||
kis_trader/backtest/optuna_orderbook_recommend.py
|
||||
=================================================
|
||||
Optuna 차트 캔들 최적화(Stage 1)가 완료된 후, 후처리(Stage 2)로 1,000회 고속 호가 탐색을 수행하여
|
||||
전략(모멘텀/돌파 등)별 최적의 진입 호가필터 & 수익구간 호가매도 합의 수치(Consensus)를 도출하고
|
||||
Optuna out_data 및 Apply 패치에 자동으로 결합하는 핵심 모듈입니다.
|
||||
Optuna 차트 캔들 최적화(Stage 1)가 완료된 후, 후처리(Stage 2)로 진입/익절/손절 켜기·끄기 8방(방 안 TPE)을
|
||||
돌린다. 시뮬은 켠 축만 스택(진입→익절호가→손절호가). 실매 엔진은 수정하지 않는다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -319,11 +318,29 @@ def raw_fills_to_ob_trades(
|
||||
|
||||
|
||||
def _ob_axis_n_trials(n_trials: int) -> int:
|
||||
"""레거시: 축 독립 500회. 8방 경로(_ob_combo_n_trials)에서는 미사용."""
|
||||
from kis_trader.utils.env import get_env_int
|
||||
axis = int(get_env_int("OPTUNA_OB_AXIS_TRIALS", 0) or 0)
|
||||
if axis > 0:
|
||||
return max(10, axis)
|
||||
return max(10, int(n_trials or _ob_n_trials(1000)))
|
||||
return max(10, int(n_trials or _ob_n_trials(500)))
|
||||
|
||||
|
||||
def _ob_combo_n_trials(n_on: int) -> int:
|
||||
"""8방 중 켜진 축 개수별 trial. 축당 500×8 금지 — 합이 구 1,500 근방."""
|
||||
from kis_trader.utils.env import get_env_int
|
||||
|
||||
n = int(n_on or 0)
|
||||
if n <= 0:
|
||||
return 0
|
||||
if n == 1:
|
||||
raw = int(get_env_int("OPTUNA_OB_COMBO_TRIALS_SINGLE", 150) or 0)
|
||||
return max(10, raw if raw > 0 else 150)
|
||||
if n == 2:
|
||||
raw = int(get_env_int("OPTUNA_OB_COMBO_TRIALS_DOUBLE", 200) or 0)
|
||||
return max(10, raw if raw > 0 else 200)
|
||||
raw = int(get_env_int("OPTUNA_OB_COMBO_TRIALS_TRIPLE", 250) or 0)
|
||||
return max(10, raw if raw > 0 else 250)
|
||||
|
||||
|
||||
def _ob_orig_stats(trades: List[TradeInfo]) -> Dict[str, Any]:
|
||||
@@ -440,6 +457,238 @@ def _sim_stop_ob(tr: TradeInfo, p: Dict[str, Any]) -> Tuple[float, float, str]:
|
||||
return (tr.actual_pnl, tr.actual_profit_rate, "HOLD_TO_ORIG")
|
||||
|
||||
|
||||
def _entry_sim_p(p: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {
|
||||
"max_spread_pct": p.get("max_spread_pct", p.get("orderbook_max_spread_pct")),
|
||||
"min_bid_ask_ratio": p.get("min_bid_ask_ratio", p.get("orderbook_min_bid_ask_ratio")),
|
||||
"ask_max_mult": p.get("ask_max_mult", p.get("orderbook_entry_ask_max_mult")),
|
||||
}
|
||||
|
||||
|
||||
def _flag_on(p: Dict[str, Any], raw_key: str, enabled_key: str) -> bool:
|
||||
if raw_key in p:
|
||||
return bool(p.get(raw_key))
|
||||
return bool(p.get(enabled_key))
|
||||
|
||||
|
||||
def _sim_stacked(tr: TradeInfo, p: Dict[str, Any]) -> Tuple[float, float, str]:
|
||||
"""켜진 축만. 진입 탈락 → 보유 중 익절호가 → 손절호가 (실매 OB 순서)."""
|
||||
use_e = _flag_on(p, "entry_on", "orderbook_filter_enabled")
|
||||
use_x = _flag_on(p, "exit_on", "exit_ob_enabled")
|
||||
use_s = _flag_on(p, "stop_on", "stop_ob_enabled")
|
||||
if use_e:
|
||||
ep = _entry_sim_p(p)
|
||||
if ep.get("max_spread_pct") is None or ep.get("min_bid_ask_ratio") is None:
|
||||
return (0.0, 0.0, "ENTRY_REJECTED")
|
||||
r = _sim_entry(tr, ep)
|
||||
if r[2] == "ENTRY_REJECTED":
|
||||
return r
|
||||
if not (use_x or use_s):
|
||||
return (tr.actual_pnl, tr.actual_profit_rate, "ORIGINAL")
|
||||
|
||||
from kis_trader.engine.momentum_hts_logic import _check_exit_ob_l3, _check_stop_ob
|
||||
|
||||
exit_p = None
|
||||
stop_p = None
|
||||
if use_x:
|
||||
exit_p = {
|
||||
"exit_ob_enabled": True,
|
||||
"exit_ob_ratio_min": float(p.get("exit_ob_ratio_min") or p.get("ob_ratio_min") or 0.4),
|
||||
"exit_ob_ma_window": int(p.get("exit_ob_ma_window") or p.get("ma_window") or 5),
|
||||
"exit_ob_min_profit_pct": float(p.get("exit_ob_min_profit_pct") or p.get("min_profit_pct") or 0.005),
|
||||
"exit_ob_min_hold_bars": int(p.get("exit_ob_min_hold_bars") or p.get("min_hold_bars") or 3),
|
||||
}
|
||||
if use_s:
|
||||
stop_p = {
|
||||
"stop_ob_enabled": True,
|
||||
"stop_ob_ratio_min": float(p.get("stop_ob_ratio_min") or 0.4),
|
||||
"stop_ob_ma_window": int(p.get("stop_ob_ma_window") or 5),
|
||||
"stop_ob_min_loss_pct": float(p.get("stop_ob_min_loss_pct") or 0.003),
|
||||
"stop_ob_min_hold_bars": int(p.get("stop_ob_min_hold_bars") or 2),
|
||||
}
|
||||
history: List[Optional[float]] = []
|
||||
for s in tr.holding_snaps:
|
||||
cur_p, ratio = _snap_or_and_price(s)
|
||||
if cur_p <= 0:
|
||||
history.append(None)
|
||||
continue
|
||||
history.append(ratio)
|
||||
hold_bars = max(0, int((s.t - tr.buy_dt).total_seconds() / 60.0))
|
||||
if exit_p and _check_exit_ob_l3(exit_p, history, tr.buy_price, cur_p, hold_bars):
|
||||
realized = (cur_p - tr.buy_price) * tr.qty
|
||||
rate = ((cur_p - tr.buy_price) / tr.buy_price) * 100.0 if tr.buy_price else 0.0
|
||||
return (realized, rate, "OB_EXIT")
|
||||
if stop_p and _check_stop_ob(stop_p, history, tr.buy_price, cur_p, hold_bars):
|
||||
realized = (cur_p - tr.buy_price) * tr.qty
|
||||
rate = ((cur_p - tr.buy_price) / tr.buy_price) * 100.0 if tr.buy_price else 0.0
|
||||
return (realized, rate, "OB_STOP")
|
||||
return (tr.actual_pnl, tr.actual_profit_rate, "HOLD_TO_ORIG")
|
||||
|
||||
|
||||
def _finalize_combo_params(
|
||||
p: Dict[str, Any],
|
||||
*,
|
||||
use_e: bool,
|
||||
use_x: bool,
|
||||
use_s: bool,
|
||||
) -> Dict[str, Any]:
|
||||
out: Dict[str, Any] = {}
|
||||
if use_e:
|
||||
out["orderbook_filter_enabled"] = True
|
||||
out["orderbook_max_spread_pct"] = p.get("max_spread_pct", p.get("orderbook_max_spread_pct"))
|
||||
out["orderbook_min_bid_ask_ratio"] = p.get("min_bid_ask_ratio", p.get("orderbook_min_bid_ask_ratio"))
|
||||
out["orderbook_entry_ask_max_mult"] = p.get("ask_max_mult", p.get("orderbook_entry_ask_max_mult"))
|
||||
else:
|
||||
out["orderbook_filter_enabled"] = False
|
||||
if use_x:
|
||||
out["exit_ob_enabled"] = True
|
||||
out["exit_ob_min_hold_bars"] = p.get("exit_ob_min_hold_bars")
|
||||
out["exit_ob_ratio_min"] = p.get("exit_ob_ratio_min")
|
||||
out["exit_ob_min_profit_pct"] = p.get("exit_ob_min_profit_pct")
|
||||
out["exit_ob_ma_window"] = p.get("exit_ob_ma_window")
|
||||
else:
|
||||
out["exit_ob_enabled"] = False
|
||||
if use_s:
|
||||
out["stop_ob_enabled"] = True
|
||||
out["stop_ob_min_hold_bars"] = p.get("stop_ob_min_hold_bars")
|
||||
out["stop_ob_ratio_min"] = p.get("stop_ob_ratio_min")
|
||||
out["stop_ob_min_loss_pct"] = p.get("stop_ob_min_loss_pct")
|
||||
out["stop_ob_ma_window"] = p.get("stop_ob_ma_window")
|
||||
else:
|
||||
out["stop_ob_enabled"] = False
|
||||
return out
|
||||
|
||||
|
||||
def _suggest_combo(
|
||||
trial: Any,
|
||||
*,
|
||||
use_e: bool,
|
||||
use_x: bool,
|
||||
use_s: bool,
|
||||
) -> Dict[str, Any]:
|
||||
from kis_trader.utils.env import get_env_float, get_env_int
|
||||
|
||||
p: Dict[str, Any] = {"entry_on": bool(use_e), "exit_on": bool(use_x), "stop_on": bool(use_s)}
|
||||
if use_e:
|
||||
lo_s = float(get_env_float("OPTUNA_OB_ENTRY_SPREAD_MIN", 0.1))
|
||||
hi_s = float(get_env_float("OPTUNA_OB_ENTRY_SPREAD_MAX", 8.0))
|
||||
lo_r = float(get_env_float("OPTUNA_OB_ENTRY_RATIO_MIN", 0.05))
|
||||
hi_r = float(get_env_float("OPTUNA_OB_ENTRY_RATIO_MAX", 1.5))
|
||||
lo_a = float(get_env_float("OPTUNA_OB_ENTRY_ASK_MULT_MIN", 1.0))
|
||||
hi_a = float(get_env_float("OPTUNA_OB_ENTRY_ASK_MULT_MAX", 80.0))
|
||||
if hi_s < lo_s:
|
||||
lo_s, hi_s = hi_s, lo_s
|
||||
if hi_r < lo_r:
|
||||
lo_r, hi_r = hi_r, lo_r
|
||||
if hi_a < lo_a:
|
||||
lo_a, hi_a = hi_a, lo_a
|
||||
p["max_spread_pct"] = trial.suggest_float("max_spread_pct", lo_s, hi_s, step=0.1)
|
||||
p["min_bid_ask_ratio"] = trial.suggest_float("min_bid_ask_ratio", lo_r, hi_r, step=0.05)
|
||||
p["ask_max_mult"] = trial.suggest_float("ask_max_mult", lo_a, hi_a, step=1.0)
|
||||
if use_x:
|
||||
p["exit_ob_enabled"] = True
|
||||
p["exit_ob_min_hold_bars"] = trial.suggest_int(
|
||||
"exit_ob_min_hold_bars",
|
||||
int(get_env_int("OPTUNA_OB_EXIT_HOLD_MIN", 1)),
|
||||
int(get_env_int("OPTUNA_OB_EXIT_HOLD_MAX", 5)),
|
||||
)
|
||||
p["exit_ob_ratio_min"] = trial.suggest_float(
|
||||
"exit_ob_ratio_min",
|
||||
float(get_env_float("OPTUNA_OB_EXIT_RATIO_MIN", 0.2)),
|
||||
float(get_env_float("OPTUNA_OB_EXIT_RATIO_MAX", 0.8)),
|
||||
step=0.05,
|
||||
)
|
||||
p["exit_ob_min_profit_pct"] = trial.suggest_float(
|
||||
"exit_ob_min_profit_pct",
|
||||
float(get_env_float("OPTUNA_OB_EXIT_PROFIT_MIN", 0.003)),
|
||||
float(get_env_float("OPTUNA_OB_EXIT_PROFIT_MAX", 0.02)),
|
||||
step=0.001,
|
||||
)
|
||||
p["exit_ob_ma_window"] = trial.suggest_int(
|
||||
"exit_ob_ma_window",
|
||||
int(get_env_int("OPTUNA_OB_EXIT_MA_MIN", 3)),
|
||||
int(get_env_int("OPTUNA_OB_EXIT_MA_MAX", 10)),
|
||||
)
|
||||
if use_s:
|
||||
p["stop_ob_enabled"] = True
|
||||
p["stop_ob_min_hold_bars"] = trial.suggest_int(
|
||||
"stop_ob_min_hold_bars",
|
||||
int(get_env_int("OPTUNA_OB_STOP_HOLD_MIN", 1)),
|
||||
int(get_env_int("OPTUNA_OB_STOP_HOLD_MAX", 5)),
|
||||
)
|
||||
p["stop_ob_ratio_min"] = trial.suggest_float(
|
||||
"stop_ob_ratio_min",
|
||||
float(get_env_float("OPTUNA_OB_STOP_RATIO_MIN", 0.2)),
|
||||
float(get_env_float("OPTUNA_OB_STOP_RATIO_MAX", 0.8)),
|
||||
step=0.05,
|
||||
)
|
||||
p["stop_ob_min_loss_pct"] = trial.suggest_float(
|
||||
"stop_ob_min_loss_pct",
|
||||
float(get_env_float("OPTUNA_OB_STOP_LOSS_MIN", 0.001)),
|
||||
float(get_env_float("OPTUNA_OB_STOP_LOSS_MAX", 0.02)),
|
||||
step=0.001,
|
||||
)
|
||||
p["stop_ob_ma_window"] = trial.suggest_int(
|
||||
"stop_ob_ma_window",
|
||||
int(get_env_int("OPTUNA_OB_STOP_MA_MIN", 3)),
|
||||
int(get_env_int("OPTUNA_OB_STOP_MA_MAX", 10)),
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def _optimize_combo(
|
||||
trades: List[TradeInfo],
|
||||
*,
|
||||
orig_cnt: int,
|
||||
n_trials: int,
|
||||
lg: logging.Logger,
|
||||
use_e: bool,
|
||||
use_x: bool,
|
||||
use_s: bool,
|
||||
axis_name: str,
|
||||
combo_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
orig = _ob_orig_stats(trades)
|
||||
mask = {"entry": bool(use_e), "exit": bool(use_x), "stop": bool(use_s)}
|
||||
if int(n_trials or 0) <= 0:
|
||||
return {
|
||||
"ok": True,
|
||||
"reason": "base_no_tpe",
|
||||
"params": _finalize_combo_params({}, use_e=False, use_x=False, use_s=False),
|
||||
"recommended_stats": orig,
|
||||
"orig_stats": orig,
|
||||
"n_trials": 0,
|
||||
"combo_id": combo_id,
|
||||
"mask": mask,
|
||||
}
|
||||
|
||||
def suggest(trial: optuna.Trial) -> Dict[str, Any]:
|
||||
return _suggest_combo(trial, use_e=use_e, use_x=use_x, use_s=use_s)
|
||||
|
||||
rec = _run_axis_study(
|
||||
trades=trades,
|
||||
orig_cnt=orig_cnt,
|
||||
n_trials=n_trials,
|
||||
suggest_fn=suggest,
|
||||
sim_fn=_sim_stacked,
|
||||
skip_reject=bool(use_e),
|
||||
lg=lg,
|
||||
axis_name=axis_name,
|
||||
)
|
||||
rec["combo_id"] = combo_id
|
||||
rec["mask"] = mask
|
||||
if rec.get("ok"):
|
||||
rec["params"] = _finalize_combo_params(
|
||||
rec.get("params") or {}, use_e=use_e, use_x=use_x, use_s=use_s,
|
||||
)
|
||||
rows = [_sim_stacked(t, rec["params"]) for t in trades]
|
||||
rec_st = _suite_stats(rows, skip_reject=bool(use_e))
|
||||
rec_st["pnl_diff"] = _krw_int(int(rec_st["pnl"]) - int(orig["pnl"]))
|
||||
rec["recommended_stats"] = rec_st
|
||||
rec["orig_stats"] = orig
|
||||
return rec
|
||||
|
||||
|
||||
def _run_axis_study(
|
||||
*,
|
||||
trades: List[TradeInfo],
|
||||
@@ -464,7 +713,14 @@ def _run_axis_study(
|
||||
return score
|
||||
|
||||
study = optuna.create_study(direction="maximize")
|
||||
study.optimize(obj_func, n_trials=n_trials)
|
||||
from kis_trader.backtest import optuna_post_progress as opp
|
||||
|
||||
def _cb(_study: Any, _trial: Any) -> None:
|
||||
n = len(_study.trials)
|
||||
if n == 1 or n == int(n_trials) or n % 5 == 0:
|
||||
opp.on_ob_axis_trial(n, int(n_trials), axis_name)
|
||||
|
||||
study.optimize(obj_func, n_trials=n_trials, callbacks=[_cb])
|
||||
valid_records.sort(key=lambda x: x["score"], reverse=True)
|
||||
top5 = valid_records[: min(5, len(valid_records))]
|
||||
if not top5:
|
||||
@@ -495,114 +751,23 @@ def _run_axis_study(
|
||||
|
||||
|
||||
def _optimize_entry_axis(trades: List[TradeInfo], *, orig_cnt: int, n_trials: int, lg: logging.Logger) -> Dict[str, Any]:
|
||||
from kis_trader.utils.env import get_env_float
|
||||
|
||||
lo_s = float(get_env_float("OPTUNA_OB_ENTRY_SPREAD_MIN", 0.1))
|
||||
hi_s = float(get_env_float("OPTUNA_OB_ENTRY_SPREAD_MAX", 8.0))
|
||||
lo_r = float(get_env_float("OPTUNA_OB_ENTRY_RATIO_MIN", 0.05))
|
||||
hi_r = float(get_env_float("OPTUNA_OB_ENTRY_RATIO_MAX", 1.5))
|
||||
lo_a = float(get_env_float("OPTUNA_OB_ENTRY_ASK_MULT_MIN", 1.0))
|
||||
hi_a = float(get_env_float("OPTUNA_OB_ENTRY_ASK_MULT_MAX", 80.0))
|
||||
if hi_s < lo_s:
|
||||
lo_s, hi_s = hi_s, lo_s
|
||||
if hi_r < lo_r:
|
||||
lo_r, hi_r = hi_r, lo_r
|
||||
if hi_a < lo_a:
|
||||
lo_a, hi_a = hi_a, lo_a
|
||||
|
||||
def suggest(trial: optuna.Trial) -> Dict[str, Any]:
|
||||
return {
|
||||
"max_spread_pct": trial.suggest_float("max_spread_pct", lo_s, hi_s, step=0.1),
|
||||
"min_bid_ask_ratio": trial.suggest_float("min_bid_ask_ratio", lo_r, hi_r, step=0.05),
|
||||
"ask_max_mult": trial.suggest_float("ask_max_mult", lo_a, hi_a, step=1.0),
|
||||
}
|
||||
|
||||
rec = _run_axis_study(
|
||||
trades=trades, orig_cnt=orig_cnt, n_trials=n_trials,
|
||||
suggest_fn=suggest, sim_fn=_sim_entry, skip_reject=True, lg=lg, axis_name="진입호가",
|
||||
return _optimize_combo(
|
||||
trades, orig_cnt=orig_cnt, n_trials=n_trials, lg=lg,
|
||||
use_e=True, use_x=False, use_s=False, axis_name="100 진입", combo_id="e",
|
||||
)
|
||||
if rec.get("ok"):
|
||||
p = rec["params"]
|
||||
rec["params"] = {
|
||||
"orderbook_filter_enabled": True,
|
||||
"orderbook_max_spread_pct": p.get("max_spread_pct"),
|
||||
"orderbook_min_bid_ask_ratio": p.get("min_bid_ask_ratio"),
|
||||
"orderbook_entry_ask_max_mult": p.get("ask_max_mult"),
|
||||
}
|
||||
return rec
|
||||
|
||||
|
||||
def _optimize_exit_axis(trades: List[TradeInfo], *, orig_cnt: int, n_trials: int, lg: logging.Logger) -> Dict[str, Any]:
|
||||
from kis_trader.utils.env import get_env_float, get_env_int
|
||||
|
||||
def suggest(trial: optuna.Trial) -> Dict[str, Any]:
|
||||
on = trial.suggest_categorical("exit_ob_enabled", [True, False])
|
||||
return {
|
||||
"exit_ob_enabled": on,
|
||||
"exit_ob_min_hold_bars": trial.suggest_int(
|
||||
"exit_ob_min_hold_bars",
|
||||
int(get_env_int("OPTUNA_OB_EXIT_HOLD_MIN", 1)),
|
||||
int(get_env_int("OPTUNA_OB_EXIT_HOLD_MAX", 5)),
|
||||
),
|
||||
"exit_ob_ratio_min": trial.suggest_float(
|
||||
"exit_ob_ratio_min",
|
||||
float(get_env_float("OPTUNA_OB_EXIT_RATIO_MIN", 0.2)),
|
||||
float(get_env_float("OPTUNA_OB_EXIT_RATIO_MAX", 0.8)),
|
||||
step=0.05,
|
||||
),
|
||||
"exit_ob_min_profit_pct": trial.suggest_float(
|
||||
"exit_ob_min_profit_pct",
|
||||
float(get_env_float("OPTUNA_OB_EXIT_PROFIT_MIN", 0.003)),
|
||||
float(get_env_float("OPTUNA_OB_EXIT_PROFIT_MAX", 0.02)),
|
||||
step=0.001,
|
||||
),
|
||||
"exit_ob_ma_window": trial.suggest_int(
|
||||
"exit_ob_ma_window",
|
||||
int(get_env_int("OPTUNA_OB_EXIT_MA_MIN", 3)),
|
||||
int(get_env_int("OPTUNA_OB_EXIT_MA_MAX", 10)),
|
||||
),
|
||||
}
|
||||
|
||||
return _run_axis_study(
|
||||
trades=trades, orig_cnt=orig_cnt, n_trials=n_trials,
|
||||
suggest_fn=suggest, sim_fn=_sim_exit_ob, skip_reject=False, lg=lg, axis_name="익절호가",
|
||||
return _optimize_combo(
|
||||
trades, orig_cnt=orig_cnt, n_trials=n_trials, lg=lg,
|
||||
use_e=False, use_x=True, use_s=False, axis_name="010 익절", combo_id="x",
|
||||
)
|
||||
|
||||
|
||||
def _optimize_stop_axis(trades: List[TradeInfo], *, orig_cnt: int, n_trials: int, lg: logging.Logger) -> Dict[str, Any]:
|
||||
from kis_trader.utils.env import get_env_float, get_env_int
|
||||
|
||||
def suggest(trial: optuna.Trial) -> Dict[str, Any]:
|
||||
on = trial.suggest_categorical("stop_ob_enabled", [True, False])
|
||||
return {
|
||||
"stop_ob_enabled": on,
|
||||
"stop_ob_min_hold_bars": trial.suggest_int(
|
||||
"stop_ob_min_hold_bars",
|
||||
int(get_env_int("OPTUNA_OB_STOP_HOLD_MIN", 1)),
|
||||
int(get_env_int("OPTUNA_OB_STOP_HOLD_MAX", 5)),
|
||||
),
|
||||
"stop_ob_ratio_min": trial.suggest_float(
|
||||
"stop_ob_ratio_min",
|
||||
float(get_env_float("OPTUNA_OB_STOP_RATIO_MIN", 0.2)),
|
||||
float(get_env_float("OPTUNA_OB_STOP_RATIO_MAX", 0.8)),
|
||||
step=0.05,
|
||||
),
|
||||
"stop_ob_min_loss_pct": trial.suggest_float(
|
||||
"stop_ob_min_loss_pct",
|
||||
float(get_env_float("OPTUNA_OB_STOP_LOSS_MIN", 0.001)),
|
||||
float(get_env_float("OPTUNA_OB_STOP_LOSS_MAX", 0.02)),
|
||||
step=0.001,
|
||||
),
|
||||
"stop_ob_ma_window": trial.suggest_int(
|
||||
"stop_ob_ma_window",
|
||||
int(get_env_int("OPTUNA_OB_STOP_MA_MIN", 3)),
|
||||
int(get_env_int("OPTUNA_OB_STOP_MA_MAX", 10)),
|
||||
),
|
||||
}
|
||||
|
||||
return _run_axis_study(
|
||||
trades=trades, orig_cnt=orig_cnt, n_trials=n_trials,
|
||||
suggest_fn=suggest, sim_fn=_sim_stop_ob, skip_reject=False, lg=lg, axis_name="손절호가",
|
||||
return _optimize_combo(
|
||||
trades, orig_cnt=orig_cnt, n_trials=n_trials, lg=lg,
|
||||
use_e=False, use_x=False, use_s=True, axis_name="001 손절", combo_id="s",
|
||||
)
|
||||
|
||||
|
||||
@@ -620,7 +785,7 @@ def recommend_orderbook_parameters(
|
||||
"""호가 후처리. raw_fills 있으면 그 체결만(백테 앵커). 없으면 trade_history(실매 참고행)."""
|
||||
lg = log or logger
|
||||
if int(n_trials or 0) <= 0:
|
||||
n_trials = _ob_n_trials(1000)
|
||||
n_trials = _ob_n_trials(500)
|
||||
strat_upper = strategy.upper()
|
||||
table, source_filter = resolve_orderbook_recommend_table(
|
||||
ob_table=ob_table,
|
||||
@@ -726,30 +891,72 @@ def recommend_orderbook_parameters(
|
||||
|
||||
orig_stats = _ob_orig_stats(trades)
|
||||
orig_cnt = int(orig_stats["count"])
|
||||
axis_n = _ob_axis_n_trials(int(n_trials))
|
||||
can_exit_stop = strat_upper in ("MOMENTUM", "BREAKOUT")
|
||||
|
||||
entry_axis = _optimize_entry_axis(trades, orig_cnt=orig_cnt, n_trials=axis_n, lg=lg)
|
||||
exit_axis: Dict[str, Any]
|
||||
stop_axis: Dict[str, Any]
|
||||
combo_specs: List[Tuple[str, str, bool, bool, bool]] = [
|
||||
("e", "100 진입", True, False, False),
|
||||
]
|
||||
if can_exit_stop:
|
||||
exit_axis = _optimize_exit_axis(trades, orig_cnt=orig_cnt, n_trials=axis_n, lg=lg)
|
||||
stop_axis = _optimize_stop_axis(trades, orig_cnt=orig_cnt, n_trials=axis_n, lg=lg)
|
||||
else:
|
||||
exit_axis = {"ok": False, "reason": "n/a_strategy", "params": {}, "recommended_stats": {}}
|
||||
stop_axis = {"ok": False, "reason": "n/a_strategy", "params": {}, "recommended_stats": {}}
|
||||
combo_specs.extend(
|
||||
[
|
||||
("x", "010 익절", False, True, False),
|
||||
("s", "001 손절", False, False, True),
|
||||
("ex", "110 진입+익절", True, True, False),
|
||||
("es", "101 진입+손절", True, False, True),
|
||||
("xs", "011 익절+손절", False, True, True),
|
||||
("exs", "111 전부", True, True, True),
|
||||
]
|
||||
)
|
||||
from kis_trader.backtest import optuna_post_progress as opp
|
||||
n_ax = len(combo_specs)
|
||||
opp.set_ob_axes(n_ax, 0)
|
||||
combos: Dict[str, Any] = {
|
||||
"base": {
|
||||
"ok": True,
|
||||
"reason": "base_no_tpe",
|
||||
"params": {},
|
||||
"recommended_stats": orig_stats,
|
||||
"orig_stats": orig_stats,
|
||||
"n_trials": 0,
|
||||
"combo_id": "base",
|
||||
"mask": {"entry": False, "exit": False, "stop": False},
|
||||
}
|
||||
}
|
||||
for i, (cid, label, use_e, use_x, use_s) in enumerate(combo_specs):
|
||||
n_on = int(use_e) + int(use_x) + int(use_s)
|
||||
n_tr = _ob_combo_n_trials(n_on)
|
||||
opp.begin_ob_axis(label, i, n_tr)
|
||||
combos[cid] = _optimize_combo(
|
||||
trades,
|
||||
orig_cnt=orig_cnt,
|
||||
n_trials=n_tr,
|
||||
lg=lg,
|
||||
use_e=use_e,
|
||||
use_x=use_x,
|
||||
use_s=use_s,
|
||||
axis_name=label,
|
||||
combo_id=cid,
|
||||
)
|
||||
|
||||
def _pick(*ids: str) -> Dict[str, Any]:
|
||||
for i in ids:
|
||||
c = combos.get(i)
|
||||
if isinstance(c, dict) and c.get("ok"):
|
||||
return c
|
||||
return {"ok": False, "reason": "no_combo", "params": {}, "recommended_stats": orig_stats}
|
||||
|
||||
# 적용 버튼: 진입=100 · 익절까지=110 스택 · 손절까지=111 스택
|
||||
entry_axis = _pick("e")
|
||||
exit_axis = _pick("ex", "x") if can_exit_stop else {"ok": False, "reason": "n/a_strategy", "params": {}, "recommended_stats": {}}
|
||||
stop_axis = _pick("exs", "es", "xs", "s") if can_exit_stop else {"ok": False, "reason": "n/a_strategy", "params": {}, "recommended_stats": {}}
|
||||
|
||||
merged_params: Dict[str, Any] = {}
|
||||
if entry_axis.get("ok"):
|
||||
merged_params.update(entry_axis.get("params") or {})
|
||||
if exit_axis.get("ok"):
|
||||
merged_params.update(exit_axis.get("params") or {})
|
||||
if stop_axis.get("ok"):
|
||||
merged_params.update(stop_axis.get("params") or {})
|
||||
for ax in (entry_axis, exit_axis, stop_axis):
|
||||
if ax.get("ok"):
|
||||
merged_params.update(ax.get("params") or {})
|
||||
|
||||
rec_stats = dict(entry_axis.get("recommended_stats") or orig_stats)
|
||||
rec_stats = dict(stop_axis.get("recommended_stats") or exit_axis.get("recommended_stats") or entry_axis.get("recommended_stats") or orig_stats)
|
||||
lg.info(
|
||||
"⚡ [호가 축분리] 전략=%s 모수=%d entry=%s exit=%s stop=%s",
|
||||
"⚡ [호가 8방] 전략=%s 모수=%d e=%s ex=%s exs=%s",
|
||||
strat_upper,
|
||||
orig_cnt,
|
||||
"ok" if entry_axis.get("ok") else entry_axis.get("reason"),
|
||||
@@ -757,16 +964,17 @@ def recommend_orderbook_parameters(
|
||||
"ok" if stop_axis.get("ok") else stop_axis.get("reason"),
|
||||
)
|
||||
return {
|
||||
"ok": bool(entry_axis.get("ok") or exit_axis.get("ok") or stop_axis.get("ok")),
|
||||
"ok": any(bool((combos.get(cid) or {}).get("ok")) for cid, *_rest in combo_specs),
|
||||
"strategy": strat_upper,
|
||||
"ob_table": table,
|
||||
"n_trials": axis_n,
|
||||
"n_trials": sum(int((combos.get(c[0]) or {}).get("n_trials") or 0) for c in combo_specs),
|
||||
"trade_count": orig_cnt,
|
||||
"orig_stats": orig_stats,
|
||||
"recommended_stats": rec_stats,
|
||||
"entry": entry_axis,
|
||||
"exit": exit_axis,
|
||||
"stop": stop_axis,
|
||||
"combos": combos,
|
||||
"params": merged_params,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user