Remove deprecated training scripts and Systemd service files

- Deleted `install_cron.sh`, `setup_schedule.sh`, and `train_daily.sh` as part of the transition to a new scheduling mechanism.
- Removed associated Systemd service and timer files for daily model training.
- Updated `live_regime_strategy.py` and `main.py` to reflect changes in model training and scheduling logic.
- Adjusted `regime_strategy.py` to align with new target calculation methods and updated optimal parameters.
- Enhanced `regime_detection.py` to incorporate path-dependent labeling for target calculations.
This commit is contained in:
2026-01-18 14:35:46 +08:00
parent b5550f4ff4
commit 582a43cd4a
10 changed files with 285 additions and 638 deletions

View File

@@ -6,6 +6,7 @@ Uses a pre-trained ML model or trains on historical data.
"""
import logging
import pickle
import time
from pathlib import Path
from typing import Optional
@@ -39,8 +40,9 @@ class LiveRegimeStrategy:
self.paths = path_config
self.model: Optional[RandomForestClassifier] = None
self.feature_cols: Optional[list] = None
self.horizon: int = 102 # Default horizon
self.horizon: int = 54 # Default horizon
self._last_model_load_time: float = 0.0
self._last_train_time: float = 0.0
self._load_or_train_model()
def reload_model_if_changed(self) -> None:
@@ -72,6 +74,13 @@ class LiveRegimeStrategy:
logger.info(f"Loaded model from {self.paths.model_path} (horizon={self.horizon})")
else:
logger.info(f"Loaded model from {self.paths.model_path} (default horizon={self.horizon})")
# Load timestamp if available
if 'timestamp' in saved:
self._last_train_time = saved['timestamp']
else:
self._last_train_time = self._last_model_load_time
return
except Exception as e:
logger.warning(f"Could not load model: {e}")
@@ -88,12 +97,20 @@ class LiveRegimeStrategy:
pickle.dump({
'model': self.model,
'feature_cols': self.feature_cols,
'metrics': {'horizon': self.horizon} # Save horizon
'metrics': {'horizon': self.horizon}, # Save horizon
'timestamp': time.time()
}, f)
logger.info(f"Saved model to {self.paths.model_path}")
except Exception as e:
logger.error(f"Could not save model: {e}")
def check_retrain(self, features: pd.DataFrame) -> None:
"""Check if model needs retraining (older than 24h)."""
if time.time() - self._last_train_time > 24 * 3600:
logger.info("Model is older than 24h. Retraining...")
self.train_model(features)
self._last_train_time = time.time()
def train_model(self, features: pd.DataFrame) -> None:
"""
Train the Random Forest model on historical data.
@@ -106,18 +123,61 @@ class LiveRegimeStrategy:
z_thresh = self.config.z_entry_threshold
horizon = self.horizon
profit_target = 0.005 # 0.5% profit threshold
stop_loss_pct = self.config.stop_loss_pct
# Define targets
future_min = features['spread'].rolling(window=horizon).min().shift(-horizon)
future_max = features['spread'].rolling(window=horizon).max().shift(-horizon)
# Calculate targets path-dependently
spread = features['spread'].values
z_score = features['z_score'].values
n = len(spread)
target_short = features['spread'] * (1 - profit_target)
target_long = features['spread'] * (1 + profit_target)
targets = np.zeros(n, dtype=int)
success_short = (features['z_score'] > z_thresh) & (future_min < target_short)
success_long = (features['z_score'] < -z_thresh) & (future_max > target_long)
candidates = np.where((z_score > z_thresh) | (z_score < -z_thresh))[0]
targets = np.select([success_short, success_long], [1, 1], default=0)
for i in candidates:
if i + horizon >= n:
continue
entry_price = spread[i]
future_prices = spread[i+1 : i+1+horizon]
if z_score[i] > z_thresh: # Short
target_price = entry_price * (1 - profit_target)
stop_price = entry_price * (1 + stop_loss_pct)
hit_tp = future_prices <= target_price
hit_sl = future_prices >= stop_price
if not np.any(hit_tp):
targets[i] = 0
elif not np.any(hit_sl):
targets[i] = 1
else:
first_tp_idx = np.argmax(hit_tp)
first_sl_idx = np.argmax(hit_sl)
if first_tp_idx < first_sl_idx:
targets[i] = 1
else:
targets[i] = 0
else: # Long
target_price = entry_price * (1 + profit_target)
stop_price = entry_price * (1 - stop_loss_pct)
hit_tp = future_prices >= target_price
hit_sl = future_prices <= stop_price
if not np.any(hit_tp):
targets[i] = 0
elif not np.any(hit_sl):
targets[i] = 1
else:
first_tp_idx = np.argmax(hit_tp)
first_sl_idx = np.argmax(hit_sl)
if first_tp_idx < first_sl_idx:
targets[i] = 1
else:
targets[i] = 0
# Exclude non-feature columns
exclude = ['spread', 'btc_close', 'eth_close', 'eth_volume']
@@ -127,8 +187,10 @@ class LiveRegimeStrategy:
X = features[self.feature_cols].fillna(0)
X = X.replace([np.inf, -np.inf], 0)
# Remove rows with invalid targets
valid_mask = ~np.isnan(targets) & future_min.notna().values & future_max.notna().values
# Use rows where we had enough data to look ahead
valid_mask = np.zeros(n, dtype=bool)
valid_mask[:n-horizon] = True
X_clean = X[valid_mask]
y_clean = targets[valid_mask]
@@ -152,7 +214,8 @@ class LiveRegimeStrategy:
def generate_signal(
self,
features: pd.DataFrame,
current_funding: dict
current_funding: dict,
position_side: Optional[str] = None
) -> dict:
"""
Generate trading signal from latest features.
@@ -160,10 +223,14 @@ class LiveRegimeStrategy:
Args:
features: DataFrame with calculated features
current_funding: Dictionary with funding rate data
position_side: Current position side ('long', 'short', or None)
Returns:
Signal dictionary with action, side, confidence, etc.
"""
# Check if retraining is needed
self.check_retrain(features)
if self.model is None:
# Train model if not available
if len(features) >= 200:
@@ -233,12 +300,17 @@ class LiveRegimeStrategy:
signal['action'] = 'hold'
signal['reason'] = f'funding_filter_blocked_short (funding={btc_funding:.4f})'
# Check for exit conditions (mean reversion complete)
if signal['action'] == 'hold':
# Z-score crossed back through 0
if abs(z_score) < 0.3:
signal['action'] = 'check_exit'
signal['reason'] = f'z_score_reverted_to_mean ({z_score:.2f})'
# Check for exit conditions (Overshoot Logic)
if signal['action'] == 'hold' and position_side:
# Overshoot Logic
# If Long, exit if Z > 0.5 (Reverted past 0 to +0.5)
if position_side == 'long' and z_score > 0.5:
signal['action'] = 'check_exit'
signal['reason'] = f'overshoot_exit_long (z={z_score:.2f} > 0.5)'
# If Short, exit if Z < -0.5 (Reverted past 0 to -0.5)
elif position_side == 'short' and z_score < -0.5:
signal['action'] = 'check_exit'
signal['reason'] = f'overshoot_exit_short (z={z_score:.2f} < -0.5)'
logger.info(
f"Signal: {signal['action']} {signal['side'] or ''} "