거래 빠르게 안티에서 병신만든거 커서로
feat: Implement backtest source management and enhance candle data handling Changes: - Introduced a new function `_apply_backtest_source_env_from_request` to manage the environment variables for candle, tick, and order book sources based on incoming requests. - Added a teardown function `_teardown_backtest_source_env` to ensure that environment variables do not persist between requests, enhancing the stability of the backtesting environment. - Refactored existing code to utilize the new source management functions, improving code readability and maintainability. - Added new utility functions in `bt_candle_source.py` for fetching and managing candle data, ensuring consistency with live trading data sources. Impact: - These changes improve the flexibility and reliability of the backtesting framework, allowing for better management of data sources and reducing the risk of cross-request contamination.
This commit is contained in:
@@ -43,6 +43,17 @@ class TailCatchStrategy(BaseStrategy):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def reload_config(self) -> None:
|
||||
# 루프마다 호출 — 병목 시 [RELOAD_PROF] 로 단계 ms 기록
|
||||
_rp_t0 = time.perf_counter()
|
||||
_rp_last = _rp_t0
|
||||
_rp: Dict[str, float] = {}
|
||||
|
||||
def _rp_mark(stage: str) -> None:
|
||||
nonlocal _rp_last
|
||||
now = time.perf_counter()
|
||||
_rp[stage] = (now - _rp_last) * 1000.0
|
||||
_rp_last = now
|
||||
|
||||
self.min_price = get_env_float("MIN_STOCK_PRICE", 1000.0)
|
||||
self.stop_loss_pct = get_env_float("STOP_LOSS_PCT", -0.04)
|
||||
self.take_profit_pct = get_env_float("TAKE_PROFIT_PCT", 0.05)
|
||||
@@ -50,9 +61,13 @@ class TailCatchStrategy(BaseStrategy):
|
||||
get_env_int("TAIL_SLOT_MONEY", 0)
|
||||
or get_env_int("SLOT_MONEY_DEFAULT", 3_000_000)
|
||||
)
|
||||
_rp_mark("env_basic")
|
||||
if te is not None:
|
||||
try:
|
||||
# ※ get_tail_defaults_from_db → db.get_merged_env_snapshot() 직접
|
||||
# (SCALP 는 get_strategy_env_dict RAM 캐시 경로 · SHORT/MOM 은 매 루프 DB)
|
||||
p = te.get_tail_defaults_from_db(self.db)
|
||||
_rp_mark("tail_defaults")
|
||||
p["live_backtest_align"] = get_env_bool(
|
||||
"SHORT_LIVE_BACKTEST_ALIGN", True,
|
||||
)
|
||||
@@ -61,10 +76,38 @@ class TailCatchStrategy(BaseStrategy):
|
||||
)
|
||||
p["entry_mode"] = short_entry_mode()
|
||||
self._engine_params = p
|
||||
_rp_mark("tail_flags")
|
||||
except Exception as e:
|
||||
self.logger.debug("tail_engine defaults 조회 실패: %s", e)
|
||||
_rp_mark("tail_err")
|
||||
self.eod_enabled = get_env_bool("TAIL_EOD_ENABLED", True)
|
||||
self.eod_hm = get_env_from_db("TAIL_EOD_HM", "15:20")
|
||||
_rp_mark("eod")
|
||||
|
||||
_rp_total = (time.perf_counter() - _rp_t0) * 1000.0
|
||||
# 500ms 이상만 — 장중 수 초 reload 원인 확정용
|
||||
if _rp_total >= 500.0:
|
||||
parts = [f"[RELOAD_PROF] SHORT total={_rp_total:.1f}"]
|
||||
for k, v in sorted(_rp.items(), key=lambda x: -x[1]):
|
||||
parts.append(f"{k}={v:.1f}")
|
||||
line = " ".join(parts)
|
||||
try:
|
||||
self.logger.info("%s", line)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
path = str(get_env_from_db("LOOP_PROFILE_LOG_PATH", "logs/loop_profile.log") or "").strip()
|
||||
if path:
|
||||
import os
|
||||
from datetime import datetime as _dt
|
||||
if not os.path.isabs(path):
|
||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
path = os.path.join(root, path)
|
||||
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
||||
with open(path, "a", encoding="utf-8") as f:
|
||||
f.write(_dt.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + " " + line + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _candidate_filter(self, candidate: Dict) -> bool:
|
||||
"""tail_on 이 True 인 후보만 대상 (SCALP 과 분리)."""
|
||||
@@ -151,19 +194,23 @@ class TailCatchStrategy(BaseStrategy):
|
||||
if self.is_dip_buy_excluded(code):
|
||||
self.logger.info("🔍 [탈락-대형주제외] %s %s: DIP_BUY_EXCLUDE_CODES", name, code)
|
||||
return None
|
||||
_cb = self._cb_prof_start(code)
|
||||
try:
|
||||
if get_env_bool("FORCE_BUY_TEST", False):
|
||||
return self._force_buy_test(code, name)
|
||||
|
||||
min_len = get_env_int("MIN_CANDLE_LEN_TAIL", 14)
|
||||
candles_raw = self.ws.get_candles(code, self.candle_tf, n=50)
|
||||
self._cb_prof_mark(_cb, "candles")
|
||||
if len(candles_raw) < min_len:
|
||||
try:
|
||||
self.ws.fill_gap([code], force=True)
|
||||
except Exception:
|
||||
pass
|
||||
self._cb_prof_mark(_cb, "fill_gap")
|
||||
return None
|
||||
candles = [self._norm_candle(c) for c in candles_raw]
|
||||
self._cb_prof_mark(_cb, "norm")
|
||||
if len(candles) < 10:
|
||||
return None
|
||||
|
||||
@@ -177,7 +224,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
today_trades = self.db.get_trades_by_date(today)
|
||||
today_trades = self._get_today_trades(today)
|
||||
code_trades = [
|
||||
t for t in today_trades
|
||||
if t.get("code") == code and str(t.get("strategy", "")).startswith("SHORT")
|
||||
@@ -189,6 +236,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
except Exception:
|
||||
daily_cnt = 0
|
||||
daily_pnl_krw = 0.0
|
||||
self._cb_prof_mark(_cb, "trades_db")
|
||||
state = {
|
||||
"last_exit_dt": last_exit_dt,
|
||||
"daily_cnt": daily_cnt,
|
||||
@@ -204,6 +252,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
params["_program_code"] = code
|
||||
params["slot_money"] = self.slot_money
|
||||
reject, msg, sig = te.check_buy_signal_live(candles, params, state)
|
||||
self._cb_prof_mark(_cb, "engine")
|
||||
if reject:
|
||||
self.logger.info("🔍 [%s] %s %s: %s", reject, name, code, msg or "")
|
||||
return None
|
||||
@@ -217,6 +266,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
eng = params if params else te.get_tail_defaults_from_db(self.db)
|
||||
atr_period = int(eng.get("atr_period", 14))
|
||||
atr_series = te.compute_atr_series(candles, atr_period)
|
||||
self._cb_prof_mark(_cb, "atr")
|
||||
|
||||
if is_limit_atr_entry(short_entry_mode(eng)):
|
||||
if len(candles) < 2:
|
||||
@@ -246,6 +296,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
qty, rej = self._resolve_buy_qty_live(
|
||||
float(limit_int), hard_cap=hard_cap,
|
||||
)
|
||||
self._cb_prof_mark(_cb, "qty")
|
||||
if rej:
|
||||
self.logger.info(
|
||||
"🔍 [탈락-%s] %s(%s) limit=%s",
|
||||
@@ -285,6 +336,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
_defer = self._defer_mid_enroll_entry(
|
||||
code, _ebk, int(self.candle_tf or 3),
|
||||
)
|
||||
self._cb_prof_mark(_cb, "mid_enroll")
|
||||
if _defer:
|
||||
self.logger.info("🔍 [%s] %s(%s)", _defer, name, code)
|
||||
return None
|
||||
@@ -307,6 +359,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
) or curr_price
|
||||
except Exception:
|
||||
pass
|
||||
self._cb_prof_mark(_cb, "align")
|
||||
if curr_price <= 0 or curr_price < self.min_price:
|
||||
return None
|
||||
|
||||
@@ -316,6 +369,7 @@ class TailCatchStrategy(BaseStrategy):
|
||||
qty, rej = self._resolve_buy_qty_live(
|
||||
curr_price, hard_cap=hard_cap,
|
||||
)
|
||||
self._cb_prof_mark(_cb, "qty")
|
||||
if rej:
|
||||
self.logger.info(
|
||||
"🔍 [탈락-%s] %s(%s) price=%.0f",
|
||||
@@ -369,6 +423,8 @@ class TailCatchStrategy(BaseStrategy):
|
||||
except Exception as e:
|
||||
self.logger.info("🔍 [탈락-예외] %s %s: %s", name, code, e)
|
||||
return None
|
||||
finally:
|
||||
self._cb_prof_finish(_cb)
|
||||
|
||||
def _force_buy_test(self, code: str, name: str) -> Optional[Dict]:
|
||||
wsd = self.ws.get_price(code)
|
||||
|
||||
Reference in New Issue
Block a user