fix: address follow-up review findings
- fix(notifier): capture fire-and-forget Future exceptions via done_callback - fix(bot): add _close_event.set() in SYNC path to unblock _close_and_reenter - fix(ml_features): apply z-score to oi_price_spread (oi_z - ret_1_z) matching training - fix(backtester): clean up import ordering after _calc_trade_stats extraction - fix(backtester): correct Sharpe annualization for 24/7 crypto (365d × 96 = 35,040) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,14 +6,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import warnings
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import joblib
|
||||
import lightgbm as lgb
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from loguru import logger
|
||||
|
||||
# 크립토 24/7 시장: 15분봉 × 96봉/일 × 365일 = 35,040
|
||||
_ANNUALIZE_FACTOR = 35_040
|
||||
|
||||
|
||||
def _calc_trade_stats(trades: list[dict], initial_balance: float) -> dict:
|
||||
"""거래 리스트에서 통계 요약을 계산한다. Backtester와 WalkForward 공통 사용."""
|
||||
@@ -43,7 +49,7 @@ def _calc_trade_stats(trades: list[dict], initial_balance: float) -> dict:
|
||||
|
||||
if len(pnls) > 1:
|
||||
pnl_arr = np.array(pnls)
|
||||
sharpe = float(np.mean(pnl_arr) / np.std(pnl_arr) * np.sqrt(24192)) if np.std(pnl_arr) > 0 else 0.0
|
||||
sharpe = float(np.mean(pnl_arr) / np.std(pnl_arr) * np.sqrt(_ANNUALIZE_FACTOR)) if np.std(pnl_arr) > 0 else 0.0
|
||||
else:
|
||||
sharpe = 0.0
|
||||
|
||||
@@ -81,11 +87,6 @@ def _calc_trade_stats(trades: list[dict], initial_balance: float) -> dict:
|
||||
"close_reasons": reasons,
|
||||
}
|
||||
|
||||
import warnings
|
||||
|
||||
import joblib
|
||||
import lightgbm as lgb
|
||||
|
||||
from src.dataset_builder import (
|
||||
_calc_indicators, _calc_signals, _calc_features_vectorized,
|
||||
generate_dataset_vectorized, stratified_undersample,
|
||||
|
||||
@@ -530,6 +530,7 @@ class TradingBot:
|
||||
self.current_trade_side = None
|
||||
self._entry_price = None
|
||||
self._entry_quantity = None
|
||||
self._close_event.set()
|
||||
self._close_handled_by_sync = False
|
||||
continue
|
||||
except Exception as e:
|
||||
|
||||
@@ -320,7 +320,16 @@ def build_features_aligned(
|
||||
else:
|
||||
base["oi_change_ma5"] = np.nan
|
||||
|
||||
base["oi_price_spread"] = float(oi_price_spread) if oi_price_spread is not None else np.nan
|
||||
# oi_price_spread = oi_z - ret_1_z (학습과 동일하게 z-score 적용된 값의 차이)
|
||||
if oi_history and len(oi_history) >= 2 and oi_price_spread is not None:
|
||||
oi_z = base.get("oi_change", np.nan)
|
||||
ret_1_z = base.get("ret_1", 0.0)
|
||||
if not np.isnan(oi_z):
|
||||
base["oi_price_spread"] = oi_z - ret_1_z
|
||||
else:
|
||||
base["oi_price_spread"] = np.nan
|
||||
else:
|
||||
base["oi_price_spread"] = np.nan
|
||||
base["adx"] = adx_z
|
||||
|
||||
return pd.Series(base)
|
||||
|
||||
@@ -17,7 +17,10 @@ class DiscordNotifier:
|
||||
return
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.run_in_executor(None, self._send_sync, content)
|
||||
fut = loop.run_in_executor(None, self._send_sync, content)
|
||||
fut.add_done_callback(
|
||||
lambda f: f.exception() and logger.warning(f"Discord 전송 실패: {f.exception()}")
|
||||
)
|
||||
except RuntimeError:
|
||||
self._send_sync(content)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user