feat: Add DART strategy and related configurations
ㅇ Changes: - Introduced the DART strategy to the trading system, including its configuration and integration into the existing framework. - Updated the database schema to include DART-specific tables for disclosures and watchlists. - Enhanced the backtesting and parameter search functionalities to support the DART strategy. - Implemented new rules for browser verification and API interactions to ensure compliance with the updated DART strategy. Impact: - These additions expand the trading capabilities of the system, allowing for more comprehensive analysis and execution of DART-related strategies, while maintaining system integrity and performance.
This commit is contained in:
@@ -54,10 +54,12 @@ def resolve_scalp_universe(
|
||||
try:
|
||||
from kis_trader.database.db_manager import get_db as _get_ext_db
|
||||
|
||||
debounce_sec = scalp_universe_exit_debounce_sec()
|
||||
history = _get_ext_db().get_universe_by_candle_time(
|
||||
strategy_id=strategy_id,
|
||||
start_ymd=start_ymd,
|
||||
end_ymd=end_ymd,
|
||||
exit_debounce_sec=debounce_sec,
|
||||
)
|
||||
if history:
|
||||
return history, "history", len(history), 1
|
||||
@@ -66,13 +68,79 @@ def resolve_scalp_universe(
|
||||
return None, "all", 0, 1
|
||||
|
||||
|
||||
def scalp_universe_exit_debounce_sec() -> int:
|
||||
"""실매 ``CONDITION_EXIT_GRACE_SEC`` 정합 — 스냅샷 축소 시 N초 유지."""
|
||||
from kis_trader.backtest.universe_timeline import universe_exit_debounce_sec_for_strategy
|
||||
|
||||
return universe_exit_debounce_sec_for_strategy("SCALP")
|
||||
|
||||
|
||||
def scalp_backtest_candle_warmup_bars() -> int:
|
||||
"""백테 RSI 등 warm-up — 실매 봉 버퍼와 같이 기간 시작 전 N봉 prepend."""
|
||||
from kis_trader.utils.env import get_env_int
|
||||
|
||||
return max(0, int(get_env_int("SCALP_BACKTEST_CANDLE_WARMUP_BARS", 50)))
|
||||
|
||||
|
||||
def prepend_scalp_candle_warmup(
|
||||
db,
|
||||
candles_by_code: Dict[str, List[Dict]],
|
||||
period_start_key: str,
|
||||
*,
|
||||
warmup_bars: Optional[int] = None,
|
||||
) -> int:
|
||||
"""
|
||||
``period_start_key``(YYYYMMDDHHMM) 이전 N봉을 종목별로 prepend.
|
||||
RSI 판별용 — 포트폴리오 all_times 는 ``_backtest_period_start_key`` 로 기간만 사용.
|
||||
"""
|
||||
wb = (
|
||||
scalp_backtest_candle_warmup_bars()
|
||||
if warmup_bars is None
|
||||
else max(0, int(warmup_bars))
|
||||
)
|
||||
if wb <= 0 or db is None or not period_start_key:
|
||||
return 0
|
||||
ps = str(period_start_key)[:12]
|
||||
total_prepended = 0
|
||||
for code, rows in list(candles_by_code.items()):
|
||||
if not rows:
|
||||
continue
|
||||
first_period_idx = None
|
||||
for i, r in enumerate(rows):
|
||||
ct = str(r.get("candle_time") or "")
|
||||
if ct >= ps:
|
||||
first_period_idx = i
|
||||
break
|
||||
if first_period_idx is None:
|
||||
continue
|
||||
# 이미 기간 전 봉이 있으면 skip (idempotent)
|
||||
if first_period_idx > 0:
|
||||
continue
|
||||
first_ct = str(rows[first_period_idx].get("candle_time") or "")
|
||||
if not first_ct:
|
||||
continue
|
||||
warm_rows = db.conn.execute(
|
||||
"SELECT candle_time, open, high, low, close, volume "
|
||||
"FROM ws_candles WHERE timeframe=1 AND code=%s "
|
||||
"AND candle_time < %s AND is_confirmed=1 "
|
||||
"ORDER BY candle_time DESC LIMIT %s",
|
||||
[code, first_ct, wb],
|
||||
).fetchall()
|
||||
if not warm_rows:
|
||||
continue
|
||||
prefix = [dict(r) for r in reversed(warm_rows)]
|
||||
candles_by_code[code] = prefix + [dict(r) for r in rows]
|
||||
total_prepended += len(prefix)
|
||||
return total_prepended
|
||||
|
||||
|
||||
def load_scalp_candles_by_code(
|
||||
db,
|
||||
start_key: str,
|
||||
end_key: str,
|
||||
rsi_period: int = 3,
|
||||
) -> Tuple[Dict[str, List[Dict]], int]:
|
||||
"""ws_candles 1분봉 전 종목 로드."""
|
||||
"""ws_candles 1분봉 전 종목 로드 (+ 기간 전 웜업 prepend)."""
|
||||
codes_raw = db.conn.execute(
|
||||
"SELECT DISTINCT code FROM ws_candles WHERE timeframe=1 "
|
||||
"AND candle_time >= %s AND candle_time <= %s ORDER BY code",
|
||||
@@ -97,6 +165,7 @@ def load_scalp_candles_by_code(
|
||||
candles_by_code[code] = [dict(r) for r in rows]
|
||||
total_candles += len(rows)
|
||||
|
||||
prepend_scalp_candle_warmup(db, candles_by_code, str(start_key)[:12])
|
||||
return candles_by_code, total_candles
|
||||
|
||||
|
||||
@@ -137,6 +206,14 @@ def run_scalping_backtest_web_aligned(
|
||||
engine_params.setdefault("scan_interval_min", 1)
|
||||
engine_params.setdefault("portfolio_mode", True)
|
||||
|
||||
# 기간 시작 키 — 웜업 봉이 all_times 에 섞이지 않도록 (돌파·모멘텀과 동일)
|
||||
_sk_w = str((meta_out or {}).get("start_key") or "")[:12]
|
||||
if _sk_w:
|
||||
engine_params["_backtest_period_start_key"] = _sk_w
|
||||
_db_w = (meta_out or {}).get("db")
|
||||
if _db_w is not None and str(mode).strip().lower() != "momentum":
|
||||
prepend_scalp_candle_warmup(_db_w, candles_by_code, _sk_w)
|
||||
|
||||
from kis_trader.backtest.backtest_env_timeline import attach_backtest_env_timeline_to_params
|
||||
attach_backtest_env_timeline_to_params(engine_params, meta_out, "SCALP")
|
||||
|
||||
@@ -194,6 +271,9 @@ def run_scalping_backtest_web_aligned(
|
||||
meta_out["backtest_buy_source"] = "ohlc_fallback"
|
||||
else:
|
||||
meta_out["backtest_buy_source"] = "align"
|
||||
if meta_out is not None:
|
||||
meta_out["universe_exit_debounce_sec"] = scalp_universe_exit_debounce_sec()
|
||||
meta_out["candle_warmup_bars"] = scalp_backtest_candle_warmup_bars()
|
||||
|
||||
attach_scalp_trade_pnl(
|
||||
trades, fee_rate=fee_rate, sell_tax=sell_tax,
|
||||
|
||||
Reference in New Issue
Block a user