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:
317
scripts/_diag_scalp_live_bt_20260716.py
Normal file
317
scripts/_diag_scalp_live_bt_20260716.py
Normal file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SCALP 2026-07-16 실매↔웹BT 진입 괴리 진단 (adhoc)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from database import TradeDB
|
||||
from kis_trader.backtest.scalping_backtest_common import resolve_scalp_universe
|
||||
from kis_trader.engine.scalping_engine import (
|
||||
_apply_buy_state_filters,
|
||||
_eval_scalp_buy_at_index,
|
||||
get_scalping_defaults_from_db,
|
||||
)
|
||||
|
||||
|
||||
def _z(code: Any) -> str:
|
||||
s = str(code or "").strip()
|
||||
return s.zfill(6) if s.isdigit() else s
|
||||
|
||||
|
||||
def _hm_from_buy(buy_date: str) -> Tuple[str, str]:
|
||||
"""'2026-07-16 09:39:03' → day=20260716, entry_key≈202607160939"""
|
||||
raw = str(buy_date or "").strip().replace("-", "").replace(":", "").replace(" ", "")
|
||||
day = raw[:8]
|
||||
hm = raw[8:12] if len(raw) >= 12 else ""
|
||||
return day, day + hm
|
||||
|
||||
|
||||
def load_candles(db: TradeDB, code: str, day: str) -> List[Dict]:
|
||||
rows = db.conn.execute(
|
||||
"""SELECT candle_time, open, high, low, close, volume, is_confirmed
|
||||
FROM ws_candles
|
||||
WHERE code=%s AND timeframe=1 AND candle_time LIKE %s
|
||||
ORDER BY candle_time""",
|
||||
(code, day + "%"),
|
||||
).fetchall()
|
||||
out = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
d["candle_time"] = str(d["candle_time"])
|
||||
out.append(d)
|
||||
return out
|
||||
|
||||
|
||||
def find_index(candles: List[Dict], key12: str) -> Optional[int]:
|
||||
k = str(key12)[:12]
|
||||
for i, c in enumerate(candles):
|
||||
if str(c["candle_time"])[:12] == k:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def nearest_index(candles: List[Dict], key12: str) -> Optional[int]:
|
||||
k = str(key12)[:12]
|
||||
best = None
|
||||
best_d = 10**9
|
||||
for i, c in enumerate(candles):
|
||||
ct = str(c["candle_time"])[:12]
|
||||
if len(ct) < 12:
|
||||
continue
|
||||
try:
|
||||
d = abs(int(ct) - int(k))
|
||||
except ValueError:
|
||||
continue
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = i
|
||||
return best
|
||||
|
||||
|
||||
def eval_at(candles: List[Dict], i: int, params: Dict) -> Tuple[str, str]:
|
||||
if i is None or i < 1:
|
||||
return "no_idx", "봉인덱스 없음"
|
||||
state = {"daily_cnt": 0, "last_exit_dt": None}
|
||||
st = _apply_buy_state_filters(candles, i, params, state)
|
||||
if st[2] is None:
|
||||
return "state", str(st[0] or st[1] or "state_reject")
|
||||
rej, msg, sig = _eval_scalp_buy_at_index(candles, i, params)
|
||||
if rej:
|
||||
return str(rej), str(msg or "")
|
||||
if not sig:
|
||||
return "no_sig", "신호없음"
|
||||
rsi = sig.get("rsi")
|
||||
return "PASS", "rsi=%s mode=%s" % (rsi, sig.get("entry_mode"))
|
||||
|
||||
|
||||
def slot_has(univ: Dict[str, List[str]], slot: str, code: str) -> bool:
|
||||
if not univ:
|
||||
return False
|
||||
return code in (univ.get(slot) or [])
|
||||
|
||||
|
||||
def first_slots(univ: Dict[str, List[str]], code: str, day: str, limit: int = 8) -> List[str]:
|
||||
out = []
|
||||
for sk in sorted(univ.keys()):
|
||||
if not sk.startswith(day):
|
||||
continue
|
||||
if code in (univ.get(sk) or []):
|
||||
out.append(sk)
|
||||
if len(out) >= limit:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
db = TradeDB()
|
||||
live_rows = db.conn.execute(
|
||||
"""SELECT code, name, buy_date, sell_date, buy_price, sell_reason, realized_pnl
|
||||
FROM trade_history WHERE strategy=%s AND buy_date LIKE %s
|
||||
ORDER BY buy_date""",
|
||||
("SCALP", "2026-07-16%"),
|
||||
).fetchall()
|
||||
active_rows = db.conn.execute(
|
||||
"""SELECT code, name, buy_date, avg_buy_price
|
||||
FROM active_trades WHERE strategy=%s AND buy_date LIKE %s""",
|
||||
("SCALP", "2026-07-16%"),
|
||||
).fetchall()
|
||||
|
||||
# 사용자 웹 BT 9건 (스크린샷)
|
||||
bt_user = [
|
||||
("001130", "2026-07-16 14:55:00"),
|
||||
("067830", "2026-07-16 14:25:00"),
|
||||
("035000", "2026-07-16 09:55:00"),
|
||||
("047770", "2026-07-16 14:27:00"),
|
||||
("226400", "2026-07-16 12:36:00"),
|
||||
("330350", "2026-07-16 12:42:00"),
|
||||
("460930", "2026-07-16 09:21:00"),
|
||||
("007540", "2026-07-16 09:36:00"),
|
||||
("439090", "2026-07-16 09:52:00"),
|
||||
]
|
||||
bt_codes = {_z(c) for c, _ in bt_user}
|
||||
live_codes = {_z(r["code"]) for r in list(live_rows) + list(active_rows)}
|
||||
|
||||
params = get_scalping_defaults_from_db()
|
||||
params.update({
|
||||
"rsi_period": 3,
|
||||
"rsi_oversold": 23.0,
|
||||
"rsi_overbought": 75.0,
|
||||
"sl_pct": 0.035,
|
||||
"tp_pct": 0.03,
|
||||
"tp_max_pct": 0.04,
|
||||
"drop_rate": 0.01,
|
||||
"vol_mult": 1.5,
|
||||
"use_defense_filters": False,
|
||||
"use_macd_cross": False,
|
||||
"skip_hts_scan_dupes": False,
|
||||
"require_reversal_candle": False,
|
||||
"min_price": 6000.0,
|
||||
"high_chase_thr": 0.99,
|
||||
"max_daily_chg": 50.0,
|
||||
"cooldown_min": 5,
|
||||
"time_start_hm": 900,
|
||||
"time_end_hm": 1530,
|
||||
"max_daily": 100,
|
||||
})
|
||||
|
||||
univ, usrc, nslots, _ = resolve_scalp_universe(
|
||||
"2026-07-16", "2026-07-16", use_saved_history=True, strategy_id="SCALP",
|
||||
)
|
||||
univ = univ or {}
|
||||
print("UNIVERSE", usrc, "slots", nslots)
|
||||
|
||||
print("\n" + "=" * 72)
|
||||
print("A) 실매 ONLY — 왜 BT가 못 샀나 (매수시각 기준 신호봉=진입직전봉)")
|
||||
print("=" * 72)
|
||||
|
||||
for r in live_rows:
|
||||
code = _z(r["code"])
|
||||
if code in bt_codes:
|
||||
tag = "BOTH"
|
||||
else:
|
||||
tag = "LIVE_ONLY"
|
||||
day, entry_key = _hm_from_buy(r["buy_date"])
|
||||
candles = load_candles(db, code, day)
|
||||
# ALIGN: 진입봉=entry_key, 신호봉=직전 확정봉
|
||||
entry_i = find_index(candles, entry_key)
|
||||
if entry_i is None:
|
||||
entry_i = nearest_index(candles, entry_key)
|
||||
signal_i = (entry_i - 1) if entry_i is not None and entry_i >= 1 else None
|
||||
|
||||
slots = first_slots(univ, code, day, 5)
|
||||
in_entry_slot = slot_has(univ, entry_key, code) if entry_key else False
|
||||
sig_key = str(candles[signal_i]["candle_time"])[:12] if signal_i is not None else ""
|
||||
in_sig_slot = slot_has(univ, sig_key, code) if sig_key else False
|
||||
|
||||
status, detail = ("no_candle", "분봉0")
|
||||
if signal_i is not None:
|
||||
status, detail = eval_at(candles, signal_i, params)
|
||||
# vol detail if reject
|
||||
vol_info = ""
|
||||
if signal_i is not None and candles:
|
||||
c = candles[signal_i]
|
||||
vols = [float(x.get("volume") or 0) for x in candles]
|
||||
win = max(1, min(20, signal_i))
|
||||
avg = sum(vols[signal_i - win : signal_i]) / win if win else 0
|
||||
vol = vols[signal_i]
|
||||
vol_info = "vol=%.0f avg20=%.0f need>=%.0f" % (
|
||||
vol, avg, avg * float(params["vol_mult"]),
|
||||
)
|
||||
|
||||
print(
|
||||
f"\n[{tag}] {code} {r['name']} live_buy={r['buy_date']} @{r['buy_price']}"
|
||||
)
|
||||
print(f" candles={len(candles)} entry_key={entry_key} signal_i={signal_i} sig_key={sig_key}")
|
||||
print(f" universe: in_entry_slot={in_entry_slot} in_sig_slot={in_sig_slot} first_slots={slots}")
|
||||
print(f" TRIGGER@signal: {status} | {detail} | {vol_info}")
|
||||
|
||||
# also scan morning for first PASS in BT conditions
|
||||
first_pass = None
|
||||
for i in range(1, len(candles)):
|
||||
st, det = eval_at(candles, i, params)
|
||||
if st == "PASS":
|
||||
ck = str(candles[i]["candle_time"])[:12]
|
||||
if slot_has(univ, ck, code) or not univ:
|
||||
first_pass = (ck, det)
|
||||
break
|
||||
print(f" first PASS+univ day: {first_pass}")
|
||||
|
||||
print("\n" + "=" * 72)
|
||||
print("B) BT ONLY — 실매는 왜 안 샀나 (BT 매수시각 기준)")
|
||||
print("=" * 72)
|
||||
for code, buy_t in bt_user:
|
||||
code = _z(code)
|
||||
if code in live_codes:
|
||||
continue
|
||||
day, entry_key = _hm_from_buy(buy_t)
|
||||
candles = load_candles(db, code, day)
|
||||
entry_i = find_index(candles, entry_key)
|
||||
signal_i = (entry_i - 1) if entry_i is not None and entry_i >= 1 else None
|
||||
sig_key = str(candles[signal_i]["candle_time"])[:12] if signal_i is not None else ""
|
||||
status, detail = eval_at(candles, signal_i, params) if signal_i is not None else ("no", "")
|
||||
slots = first_slots(univ, code, day, 5)
|
||||
# history presence count
|
||||
n_hist = db.conn.execute(
|
||||
"SELECT COUNT(*) n FROM target_candidates_history WHERE strategy_id=%s AND code=%s AND slot_key LIKE %s",
|
||||
("SCALP", code, day + "%"),
|
||||
).fetchone()["n"]
|
||||
print(f"\n[BT_ONLY] {code} bt_buy={buy_t}")
|
||||
print(f" hist_rows={n_hist} first_slots={slots}")
|
||||
print(f" TRIGGER@BT_signal {sig_key}: {status} | {detail}")
|
||||
print(f" in_sig_slot={slot_has(univ, sig_key, code)} in_entry_slot={slot_has(univ, entry_key, code)}")
|
||||
|
||||
print("\n" + "=" * 72)
|
||||
print("C) BOTH — 시각 차이")
|
||||
print("=" * 72)
|
||||
live_by = {_z(r["code"]): r for r in live_rows}
|
||||
for code, buy_t in bt_user:
|
||||
code = _z(code)
|
||||
if code not in live_by:
|
||||
continue
|
||||
lr = live_by[code]
|
||||
print(f" {code}: LIVE {lr['buy_date']} @{lr['buy_price']} | BT {buy_t}")
|
||||
|
||||
# vol_mult ON/OFF sensitivity for LIVE_ONLY
|
||||
print("\n" + "=" * 72)
|
||||
print("D) LIVE_ONLY — vol_mult=0 이면 PASS 되나?")
|
||||
print("=" * 72)
|
||||
p0 = dict(params)
|
||||
p0["vol_mult"] = 0.0
|
||||
for r in live_rows:
|
||||
code = _z(r["code"])
|
||||
if code in bt_codes:
|
||||
continue
|
||||
day, entry_key = _hm_from_buy(r["buy_date"])
|
||||
candles = load_candles(db, code, day)
|
||||
entry_i = find_index(candles, entry_key) or nearest_index(candles, entry_key)
|
||||
signal_i = (entry_i - 1) if entry_i and entry_i >= 1 else None
|
||||
s1, d1 = eval_at(candles, signal_i, params) if signal_i is not None else ("?", "")
|
||||
s0, d0 = eval_at(candles, signal_i, p0) if signal_i is not None else ("?", "")
|
||||
print(f" {code}: vol1.5={s1}({d1[:40]}) | vol0={s0}({d0[:40]})")
|
||||
|
||||
# min_price with defense OFF should not block — confirm
|
||||
print("\n" + "=" * 72)
|
||||
print("E) 슬롯 경쟁 가설 — 실매 매수 시각에 BT 후보가 몇 개?")
|
||||
print("=" * 72)
|
||||
# for each live buy minute, count how many codes PASS in that signal slot among univ
|
||||
for r in live_rows[:5]:
|
||||
day, entry_key = _hm_from_buy(r["buy_date"])
|
||||
# signal approx entry-1min
|
||||
try:
|
||||
sig_num = int(entry_key) - 1
|
||||
sig_key = str(sig_num)
|
||||
except Exception:
|
||||
sig_key = entry_key
|
||||
# fix minute borrow
|
||||
if entry_key.endswith("00"):
|
||||
# 0900 -> 0859 not valid; use find
|
||||
pass
|
||||
hh = int(entry_key[8:10])
|
||||
mm = int(entry_key[10:12])
|
||||
if mm == 0:
|
||||
hh -= 1
|
||||
mm = 59
|
||||
else:
|
||||
mm -= 1
|
||||
sig_key = "%s%02d%02d" % (day, hh, mm)
|
||||
codes_in = list(univ.get(sig_key) or [])
|
||||
passes = []
|
||||
for c in codes_in[:80]:
|
||||
candles = load_candles(db, _z(c), day)
|
||||
si = find_index(candles, sig_key)
|
||||
if si is None:
|
||||
continue
|
||||
st, det = eval_at(candles, si, params)
|
||||
if st == "PASS":
|
||||
passes.append((_z(c), det))
|
||||
print(
|
||||
f" live={_z(r['code'])} @{r['buy_date']} sig={sig_key} "
|
||||
f"univ={len(codes_in)} PASS={len(passes)} sample={passes[:6]}"
|
||||
)
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
94
scripts/run_optuna_3strat_apply_20260720.sh
Executable file
94
scripts/run_optuna_3strat_apply_20260720.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# 모멘텀·돌파·스캘핑 Optuna 순차 + --apply-best (2026-07-20 1일)
|
||||
# 동시 실행 금지(RAM). 장전 적용용.
|
||||
#
|
||||
# nohup bash scripts/run_optuna_3strat_apply_20260720.sh >> logs/optuna_3strat_apply_0720_master.log 2>&1 &
|
||||
# tail -f logs/optuna_3strat_apply_0720_master.log
|
||||
|
||||
set -euo pipefail
|
||||
cd /home/hoon/kis_bot
|
||||
mkdir -p logs kis_trader/backtest/results
|
||||
|
||||
START="${START:-2026-07-20}"
|
||||
END="${END:-2026-07-20}"
|
||||
MODE="${MODE:-fine}"
|
||||
TRIALS="${TRIALS:-200}"
|
||||
MIN_TRADES="${MIN_TRADES:-1}"
|
||||
STRATEGIES="${STRATEGIES:-momentum breakout scalp}"
|
||||
TS0="$(date +%Y%m%d_%H%M%S)"
|
||||
MASTER="logs/optuna_3strat_apply_${START//-/}_${END//-/}_${TS0}_master.log"
|
||||
|
||||
{
|
||||
echo "======== Optuna 3전략 순차+apply-best 시작 $(date -Is) ========"
|
||||
echo "START=$START END=$END MODE=$MODE TRIALS=$TRIALS"
|
||||
echo "STRATEGIES=$STRATEGIES apply-best=ON orderbook=off n_jobs=1"
|
||||
echo "master_log=$MASTER"
|
||||
free -h | sed -n '1,2p'
|
||||
} | tee -a "$MASTER"
|
||||
echo "$MASTER" > logs/optuna_3strat_apply_latest_master.logpath
|
||||
|
||||
run_one() {
|
||||
local strat="$1"
|
||||
local ts study log sort_by
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
study="${strat}_${MODE}_apply_${START//-/}_${ts}"
|
||||
log="logs/optuna_seq_${strat}_${MODE}_apply_${ts}.log"
|
||||
sort_by="pnl"
|
||||
if [[ "$strat" == "momentum" || "$strat" == "scalp" ]]; then
|
||||
sort_by="score"
|
||||
fi
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "-------- $(date -Is) START $strat study=$study --------"
|
||||
echo "LOG=$log"
|
||||
} | tee -a "$MASTER"
|
||||
echo "$log" > "logs/optuna_seq_${strat}_latest.logpath"
|
||||
echo "$study" > "logs/optuna_seq_${strat}_latest.study"
|
||||
|
||||
set +e
|
||||
python3 -u kis_trader/backtest/param_search_optuna.py \
|
||||
--strategy "$strat" \
|
||||
--mode "$MODE" \
|
||||
--start "$START" \
|
||||
--end "$END" \
|
||||
--trials "$TRIALS" \
|
||||
--min_trades "$MIN_TRADES" \
|
||||
--sort-by "$sort_by" \
|
||||
--orderbook-filter off \
|
||||
--no-progress \
|
||||
--n-jobs 1 \
|
||||
--study-name "$study" \
|
||||
--apply-best \
|
||||
> "$log" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
|
||||
{
|
||||
echo "-------- $(date -Is) END $strat rc=$rc --------"
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
echo "⚠️ $strat 실패(rc=$rc) — 다음 전략 계속. tail: $log"
|
||||
else
|
||||
echo "✅ $strat 완료(+apply-best 시도). log=$log"
|
||||
grep -E "apply-best|DB 적용|env_config|스킵|best|순익|PnL" "$log" | tail -20 || true
|
||||
fi
|
||||
free -h | sed -n '2p'
|
||||
} | tee -a "$MASTER"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
for s in $STRATEGIES; do
|
||||
run_one "$s"
|
||||
done
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "======== 전부 종료 $(date -Is) ========"
|
||||
echo "master=$MASTER"
|
||||
for s in $STRATEGIES; do
|
||||
echo " $s logpath=$(cat logs/optuna_seq_${s}_latest.logpath 2>/dev/null || echo '?')"
|
||||
echo " $s study=$(cat logs/optuna_seq_${s}_latest.study 2>/dev/null || echo '?')"
|
||||
done
|
||||
echo "※ apply-best: 총손익≤0 이면 코드가 DB 적용 스킵할 수 있음 — 각 로그 grep apply"
|
||||
} | tee -a "$MASTER"
|
||||
100
scripts/run_optuna_4strat_seq_20260715_16.sh
Executable file
100
scripts/run_optuna_4strat_seq_20260715_16.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# 4전략 Optuna 순차 실행 (동시 X — RAM 13G + 틱 2일 로딩 시 병렬은 OOM/스왑 위험)
|
||||
# 기간: 2026-07-15 ~ 2026-07-16 (거래일) · apply-best 없음 · 호가 OFF
|
||||
#
|
||||
# 사용:
|
||||
# nohup bash scripts/run_optuna_4strat_seq_20260715_16.sh >> logs/optuna_4strat_seq_master.log 2>&1 &
|
||||
# tail -f logs/optuna_4strat_seq_master.log
|
||||
# # 전략별: tail -f logs/optuna_seq_<strategy>_*.log
|
||||
#
|
||||
# 환경변수 오버라이드 예:
|
||||
# MODE=coarse TRIALS=100 STRATEGIES="tail scalp" bash scripts/run_optuna_4strat_seq_20260715_16.sh
|
||||
|
||||
set -euo pipefail
|
||||
cd /home/hoon/kis_bot
|
||||
mkdir -p logs kis_trader/backtest/results
|
||||
|
||||
START="${START:-2026-07-15}"
|
||||
END="${END:-2026-07-16}"
|
||||
MODE="${MODE:-fine}"
|
||||
TRIALS="${TRIALS:-200}"
|
||||
MIN_TRADES="${MIN_TRADES:-1}"
|
||||
# 공백 구분: tail momentum breakout scalp
|
||||
STRATEGIES="${STRATEGIES:-tail momentum breakout scalp}"
|
||||
TS0="$(date +%Y%m%d_%H%M%S)"
|
||||
MASTER="logs/optuna_4strat_seq_${START}_${END}_${TS0}_master.log"
|
||||
|
||||
{
|
||||
echo "======== Optuna 4전략 순차 시작 $(date -Is) ========"
|
||||
echo "START=$START END=$END MODE=$MODE TRIALS=$TRIALS"
|
||||
echo "STRATEGIES=$STRATEGIES"
|
||||
echo "apply-best=OFF orderbook=off n_jobs=1"
|
||||
echo "master_log=$MASTER"
|
||||
free -h | sed -n '1,2p'
|
||||
df -h / | tail -1
|
||||
} | tee -a "$MASTER"
|
||||
echo "$MASTER" > logs/optuna_4strat_seq_latest_master.logpath
|
||||
|
||||
run_one() {
|
||||
local strat="$1"
|
||||
local ts study log sort_by
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
study="${strat}_${MODE}_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_seq_${strat}_${MODE}_${ts}.log"
|
||||
sort_by="pnl"
|
||||
if [[ "$strat" == "momentum" || "$strat" == "scalp" ]]; then
|
||||
sort_by="score"
|
||||
fi
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "-------- $(date -Is) START $strat study=$study --------"
|
||||
echo "LOG=$log"
|
||||
} | tee -a "$MASTER"
|
||||
echo "$log" > "logs/optuna_seq_${strat}_latest.logpath"
|
||||
echo "$study" > "logs/optuna_seq_${strat}_latest.study"
|
||||
|
||||
# --apply-best 없음 (기본 미적용)
|
||||
set +e
|
||||
python3 -u kis_trader/backtest/param_search_optuna.py \
|
||||
--strategy "$strat" \
|
||||
--mode "$MODE" \
|
||||
--start "$START" \
|
||||
--end "$END" \
|
||||
--trials "$TRIALS" \
|
||||
--min_trades "$MIN_TRADES" \
|
||||
--sort-by "$sort_by" \
|
||||
--orderbook-filter off \
|
||||
--no-progress \
|
||||
--n-jobs 1 \
|
||||
--study-name "$study" \
|
||||
> "$log" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
|
||||
{
|
||||
echo "-------- $(date -Is) END $strat rc=$rc --------"
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
echo "⚠️ $strat 실패(rc=$rc) — 다음 전략 계속. tail: $log"
|
||||
else
|
||||
echo "✅ $strat 완료. log=$log"
|
||||
fi
|
||||
free -h | sed -n '2p'
|
||||
} | tee -a "$MASTER"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
for s in $STRATEGIES; do
|
||||
run_one "$s"
|
||||
done
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "======== 전부 종료 $(date -Is) ========"
|
||||
echo "master=$MASTER"
|
||||
for s in $STRATEGIES; do
|
||||
echo " $s logpath=$(cat logs/optuna_seq_${s}_latest.logpath 2>/dev/null || echo '?')"
|
||||
echo " $s study=$(cat logs/optuna_seq_${s}_latest.study 2>/dev/null || echo '?')"
|
||||
done
|
||||
} | tee -a "$MASTER"
|
||||
@@ -106,6 +106,37 @@ def main() -> None:
|
||||
})
|
||||
assert agg2._confirmed[key][0]["volume"] == 800
|
||||
|
||||
# 진행 중 분봉은 REST/merge confirmed 에 넣지 않음 (장초 직전봉% 왜곡 방지)
|
||||
import datetime as _dt
|
||||
agg3 = CandleAggregator(db=None, timeframes=[1])
|
||||
code2 = "333050"
|
||||
frozen = _dt.datetime(2026, 7, 16, 9, 0, 34)
|
||||
# 전일 + 미완성 당일 09:00 을 넣으려 할 때 → 09:00 만 skip
|
||||
n = agg3.merge_confirmed_bars(
|
||||
code2, 1,
|
||||
[
|
||||
bar("202607151530", 5280, 5280, 5280, 5280, 960, "rest"),
|
||||
bar("202607160900", 5220, 5250, 5200, 5220, 10, "rest"), # 진행분
|
||||
],
|
||||
log_tag="smoke_skip_open",
|
||||
skip_incomplete_bucket=True,
|
||||
now=frozen,
|
||||
)
|
||||
assert n == 1
|
||||
buf3 = agg3._confirmed[(code2, 1)]
|
||||
assert len(buf3) == 1 and buf3[0]["candle_time"] == "202607151530"
|
||||
# 이미 들어간 진행분 purge
|
||||
agg3._confirmed[(code2, 1)].append(
|
||||
bar("202607160900", 5220, 5250, 5200, 5220, 10, "rest")
|
||||
)
|
||||
agg3.merge_confirmed_bars(
|
||||
code2, 1, [], log_tag="smoke_purge", skip_incomplete_bucket=True, now=frozen,
|
||||
)
|
||||
assert all(
|
||||
str(c["candle_time"])[:12] < "202607160900"
|
||||
for c in agg3._confirmed[(code2, 1)]
|
||||
)
|
||||
|
||||
print("SMOKE_OK candle_upsert_rollup")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user