feat: Enhance trading system with new permanent subscription features and order book management
Changes: - Added a new API endpoint for managing permanent subscriptions, allowing users to enable or disable subscriptions dynamically. - Implemented a function to fill candle data from Kiwoom, ensuring that only relevant data is inserted into the database. - Introduced a mechanism to handle master subscription states, improving the management of subscription statuses. - Updated the database schema to include new fields for managing subscription states and order book filtering. Impact: - These enhancements improve the flexibility and reliability of the trading system, allowing for better management of subscriptions and order book data, while reducing the risk of data inconsistencies. 히스토리 align 제거 븅신같은 초기설계 아예 제거 진입모드에 구멍메움 호가진입을 켜도 호가가 안들어올때 호가 안보고 그냥 사버림
This commit is contained in:
249
kis_trader/backtest/optuna_rerun_postprocess.py
Normal file
249
kis_trader/backtest/optuna_rerun_postprocess.py
Normal file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python3
|
||||
"""구 Optuna JSON에 TopN 후처리(진입/익절/손절/휩쏘)를 다시 붙여 저장.
|
||||
|
||||
실매 엔진 미변경. 캔들/틱은 prepare_* 가 DB 재사용. REST 웜업은 기존 prepare 경로만.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_ROOT))
|
||||
|
||||
logger = logging.getLogger("optuna_rerun_postprocess")
|
||||
|
||||
EvalFn = Callable[[Dict[str, Any]], Optional[Dict[str, Any]]]
|
||||
|
||||
|
||||
def _hist(data: Dict[str, Any]) -> Optional[str]:
|
||||
return (
|
||||
data.get("universe_history_source")
|
||||
or data.get("_universe_history_source")
|
||||
or data.get("history_source")
|
||||
)
|
||||
|
||||
|
||||
def build_replay_evaluate_fn(
|
||||
data: Dict[str, Any],
|
||||
) -> Tuple[Optional[EvalFn], Any]:
|
||||
"""JSON 메타로 실매 Optuna와 같은 evaluate_fn + ctx. 실패 시 (None, None)."""
|
||||
strat = str(data.get("strategy") or "").strip().lower()
|
||||
start = str(data.get("start") or "").strip()
|
||||
end = str(data.get("end") or "").strip()
|
||||
mode = str(data.get("mode") or "tpe").strip().lower() or "tpe"
|
||||
hist = _hist(data)
|
||||
if not start or not end:
|
||||
logger.warning("⚠️ start/end 없음 — 호가 재탐색만(체결 재실행 없음)")
|
||||
return None, None
|
||||
|
||||
if strat in ("momentum", "us_momentum"):
|
||||
from kis_trader.backtest.optuna_momentum import prepare_momentum_search_context
|
||||
from kis_trader.backtest.param_search_momentum import evaluate_momentum_param_combo
|
||||
|
||||
mk = "US" if strat == "us_momentum" else "KR"
|
||||
ctx = prepare_momentum_search_context(
|
||||
start, end, mode,
|
||||
history_source=hist,
|
||||
market=mk,
|
||||
symbol=str(data.get("symbol") or "") or None,
|
||||
orderbook_filter="off",
|
||||
)
|
||||
if ctx is None:
|
||||
return None, None
|
||||
|
||||
def _eval(combo: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return evaluate_momentum_param_combo(
|
||||
combo,
|
||||
base_fixed=ctx.base_fixed,
|
||||
grid_keys=ctx.grid_keys,
|
||||
codes_candles=ctx.codes_candles,
|
||||
min_trades=1,
|
||||
min_win_rate=0.0,
|
||||
min_pf=0.0,
|
||||
universe_by_slot=ctx.universe_by_slot,
|
||||
slot_money=ctx.slot_money,
|
||||
max_stocks=ctx.max_stocks,
|
||||
total_budget_krw=ctx.total_budget_krw,
|
||||
fee_rate=ctx.fee_rate,
|
||||
sell_tax=ctx.sell_tax,
|
||||
period_days=ctx.period_days,
|
||||
cache_holder=ctx.cache_holder,
|
||||
ticks_by_code=ctx.ticks_by_code,
|
||||
orderbook_by_code=ctx.orderbook_by_code,
|
||||
program_by_code=ctx.program_by_code,
|
||||
log_verdict_by_code=ctx.log_verdict_by_code,
|
||||
start_key=ctx.start_key,
|
||||
end_key=ctx.end_key,
|
||||
include_trades=True,
|
||||
)
|
||||
|
||||
return _eval, ctx
|
||||
|
||||
if strat == "breakout":
|
||||
from kis_trader.backtest.optuna_breakout import prepare_breakout_search_context
|
||||
from kis_trader.backtest.param_search_breakout import evaluate_breakout_param_combo
|
||||
|
||||
ctx = prepare_breakout_search_context(
|
||||
start, end, mode, history_source=hist, orderbook_filter="off",
|
||||
)
|
||||
if ctx is None:
|
||||
return None, None
|
||||
|
||||
def _eval_b(combo: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return evaluate_breakout_param_combo(
|
||||
combo,
|
||||
base_fixed=ctx.base_fixed,
|
||||
grid_keys=ctx.grid_keys,
|
||||
codes_candles=ctx.codes_candles,
|
||||
min_trades=1,
|
||||
min_win_rate=0.0,
|
||||
min_pf=0.0,
|
||||
universe_by_slot=ctx.universe_by_slot,
|
||||
slot_money=ctx.slot_money,
|
||||
max_stocks=ctx.max_stocks,
|
||||
total_budget_krw=ctx.total_budget_krw,
|
||||
fee_rate=ctx.fee_rate,
|
||||
sell_tax=ctx.sell_tax,
|
||||
period_days=ctx.period_days,
|
||||
cache_holder=ctx.cache_holder,
|
||||
ticks_by_code=ctx.ticks_by_code,
|
||||
orderbook_by_code=ctx.orderbook_by_code,
|
||||
program_by_code=ctx.program_by_code,
|
||||
log_verdict_by_code=ctx.log_verdict_by_code,
|
||||
share_denom_by_code=ctx.share_denom_by_code,
|
||||
include_trades=True,
|
||||
)
|
||||
|
||||
return _eval_b, ctx
|
||||
|
||||
if strat in ("scalp", "scalping"):
|
||||
from kis_trader.backtest.optuna_scalping import prepare_scalp_search_context
|
||||
from kis_trader.backtest.param_search_scalping import evaluate_scalp_param_combo
|
||||
|
||||
ctx = prepare_scalp_search_context(
|
||||
start, end, mode, history_source=hist, orderbook_filter="off",
|
||||
)
|
||||
if ctx is None:
|
||||
return None, None
|
||||
|
||||
def _eval_s(combo: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return evaluate_scalp_param_combo(
|
||||
combo,
|
||||
base_fixed=ctx.base_fixed,
|
||||
grid_keys=ctx.grid_keys,
|
||||
codes_candles=ctx.codes_candles,
|
||||
min_trades=1,
|
||||
min_win_rate=0.0,
|
||||
min_pf=0.0,
|
||||
universe_by_slot=ctx.universe_by_slot,
|
||||
slot_money=ctx.slot_money,
|
||||
max_stocks=ctx.max_stocks,
|
||||
total_budget_krw=ctx.total_budget_krw,
|
||||
fee_rate=ctx.fee_rate,
|
||||
sell_tax=ctx.sell_tax,
|
||||
period_days=ctx.period_days,
|
||||
cache_holder=ctx.cache_holder,
|
||||
ticks_by_code=ctx.ticks_by_code,
|
||||
orderbook_by_code=ctx.orderbook_by_code,
|
||||
program_by_code=ctx.program_by_code,
|
||||
start_key=ctx.start_key,
|
||||
end_key=ctx.end_key,
|
||||
include_trades=True,
|
||||
)
|
||||
|
||||
return _eval_s, ctx
|
||||
|
||||
if strat in ("tail", "short"):
|
||||
from kis_trader.backtest.param_search_optuna import prepare_tail_search_context
|
||||
from kis_trader.backtest.tail_param_search import evaluate_tail_param_combo
|
||||
|
||||
ctx = prepare_tail_search_context(
|
||||
start, end, mode, history_source=hist, orderbook_filter="off",
|
||||
)
|
||||
if ctx is None:
|
||||
return None, None
|
||||
|
||||
def _eval_t(combo: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return evaluate_tail_param_combo(
|
||||
combo,
|
||||
base_params=ctx.base_params,
|
||||
candles_by_code=ctx.candles_by_code,
|
||||
fee_rate=ctx.fee_rate,
|
||||
sell_tax=ctx.sell_tax,
|
||||
min_trades=1,
|
||||
min_win_rate=0.0,
|
||||
min_pf=0.0,
|
||||
universe_by_slot=ctx.universe_by_slot,
|
||||
slot_money=ctx.slot_money,
|
||||
max_stocks=ctx.max_stocks,
|
||||
total_budget_krw=ctx.total_budget_krw,
|
||||
period_days=ctx.period_days,
|
||||
cache_holder=ctx.cache_holder,
|
||||
ticks_by_code=ctx.ticks_by_code,
|
||||
orderbook_by_code=ctx.orderbook_by_code,
|
||||
program_by_code=ctx.program_by_code,
|
||||
log_verdict_by_code=ctx.log_verdict_by_code,
|
||||
include_trades=True,
|
||||
)
|
||||
|
||||
return _eval_t, ctx
|
||||
|
||||
logger.warning("⚠️ 전략 %s 후처리 재실행 evaluate 미지원", strat)
|
||||
return None, None
|
||||
|
||||
|
||||
def rerun_postprocess_on_json(path: str, *, ob_n_trials: int = 0) -> Dict[str, Any]:
|
||||
p = Path(path)
|
||||
if not p.is_file():
|
||||
raise FileNotFoundError(str(p))
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
eval_fn, ctx = build_replay_evaluate_fn(data)
|
||||
try:
|
||||
from kis_trader.backtest.optuna_postprocess_topn import attach_topn_postprocess
|
||||
|
||||
attach_topn_postprocess(
|
||||
data,
|
||||
evaluate_fn=eval_fn,
|
||||
log=logger,
|
||||
run_ob_whipsaw=True,
|
||||
ob_n_trials=int(ob_n_trials or 0),
|
||||
)
|
||||
tmp = p.with_suffix(".tmp.json")
|
||||
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
tmp.replace(p)
|
||||
topn = data.get("postprocess_topn") or {}
|
||||
n = len(topn.get("postprocess_by_anchor") or [])
|
||||
logger.info("📌 후처리 재저장 %s anchors=%d overfit=%s", p, n, topn.get("apply_overfit_pct"))
|
||||
return {"ok": True, "path": str(p), "anchors": n, "run_ob_whipsaw": True}
|
||||
finally:
|
||||
if ctx is not None:
|
||||
try:
|
||||
from kis_trader.backtest.optuna_common import release_shared_tick_store
|
||||
release_shared_tick_store(ctx, log=logger)
|
||||
except Exception as exc:
|
||||
logger.warning("⚠️ tick store 해제: %s", exc)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
ap = argparse.ArgumentParser(description="Optuna JSON TopN 후처리 재실행")
|
||||
ap.add_argument("--result-json", required=True)
|
||||
ap.add_argument("--ob-axis-trials", type=int, default=0, help="0=DB OPTUNA_OB_* trial 수")
|
||||
args = ap.parse_args()
|
||||
out = rerun_postprocess_on_json(args.result_json, ob_n_trials=int(args.ob_axis_trials or 0))
|
||||
logger.info("OK %s", out)
|
||||
return 0 if out.get("ok") else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user