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:
Your Name
2026-08-15 23:01:14 +09:00
parent 4a18ce2697
commit 36a3e2b4a1
94 changed files with 6368 additions and 1639 deletions

View 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())