import pandas as pd import numpy as np import ta import talib from sklearn.ensemble import RandomForestRegressor from sklearn.feature_selection import SelectFromModel from sklearn.preprocessing import LabelEncoder from scipy.stats import ks_2samp from collections import deque from utils import log_and_print # Global variable to track drift history drift_window = deque(maxlen=10) def identify_market_structure(data): """Identifies swing highs/lows and market structure (HH, HL, LH, LL).""" data['swing_high'] = (data['high'].shift(1) < data['high']) & (data['high'].shift(-1) < data['high']) data['swing_low'] = (data['low'].shift(1) > data['low']) & (data['low'].shift(-1) > data['low']) swing_highs = data[data['swing_high']] swing_lows = data[data['swing_low']] data['HH'] = (data['swing_high']) & (data['high'] > swing_highs['high'].shift(1)) data['HL'] = (data['swing_low']) & (data['low'] > swing_lows['low'].shift(1)) data['LH'] = (data['swing_high']) & (data['high'] < swing_highs['high'].shift(1)) data['LL'] = (data['swing_low']) & (data['low'] < swing_lows['low'].shift(1)) # Fill NaN values for col in ['swing_high', 'swing_low', 'HH', 'HL', 'LH', 'LL']: data[col] = data[col].fillna(False) return data def identify_candlestick_patterns(data): """Identifies common candlestick patterns.""" patterns = { 'engulfing': talib.CDLENGULFING, 'doji': talib.CDLDOJI, 'hammer': talib.CDLHAMMER, 'shooting_star': talib.CDLSHOOTINGSTAR } for name, pattern_func in patterns.items(): data[name] = pattern_func(data['open'], data['high'], data['low'], data['close']) return data def identify_trends(data): """Identifies the current trend based on market structure.""" if 'HH' not in data.columns or 'HL' not in data.columns or 'LH' not in data.columns or 'LL' not in data.columns: log_and_print("Warning: Market structure columns (HH, HL, etc.) are missing. Cannot identify trends.", is_error=True) data['trend'] = 'sideways' else: data['trend'] = np.where(data['HH'] & data['HL'], 'uptrend', np.where(data['LH'] & data['LL'], 'downtrend', 'sideways')) return data def detect_breakouts(data, window=20): """Detects breakouts from recent highs and lows.""" data['breakout'] = np.logical_or( np.greater(data['close'], data['high'].rolling(window=window).max().shift(1)), np.less(data['close'], data['low'].rolling(window=window).min().shift(1)) ) return data def add_indicators(data): """Adds a set of technical indicators to the dataframe.""" # Add basic indicators data['RSI'] = ta.momentum.rsi(data['close'], window=14) data['MACD'] = ta.trend.macd(data['close']) data['MACD_signal'] = ta.trend.macd_signal(data['close']) data['Bollinger_high'] = ta.volatility.bollinger_hband(data['close']) data['Bollinger_low'] = ta.volatility.bollinger_lband(data['close']) data['MA20'] = data['close'].rolling(window=20).mean() data['MA50'] = data['close'].rolling(window=50).mean() data['MA200'] = data['close'].rolling(window=200).mean() data['volatility_atr'] = ta.volatility.average_true_range(data['high'], data['low'], data['close']) # Add advanced indicators data['ichimoku_a'] = ta.trend.ichimoku_a(data['high'], data['low']) data['ichimoku_b'] = ta.trend.ichimoku_b(data['high'], data['low']) data['adx'] = ta.trend.adx(data['high'], data['low'], data['close']) if 'tick_volume' in data.columns: data['vwap'] = ta.volume.volume_weighted_average_price(data['high'], data['low'], data['close'], data['tick_volume']) return data def detect_market_regime(data): """Detects market regime (trending or ranging) based on ADX.""" try: adx = data.get('adx', ta.trend.adx(data['high'], data['low'], data['close'])) data['regime'] = np.where(adx > 25, 'trending', 'ranging') except Exception as e: log_and_print(f"Error in detect_market_regime: {e}", is_error=True) data['regime'] = 'ranging' # Default to 'ranging' return data def add_all_features(df): """A master function to add all features to the dataframe.""" df = add_indicators(df) df = identify_market_structure(df) df = identify_candlestick_patterns(df) df = identify_trends(df) df = detect_breakouts(df) df = detect_market_regime(df) df = df.fillna(0) return df def select_features(X, y, max_features=20): """Selects the best features using a RandomForestRegressor.""" estimator = RandomForestRegressor(n_estimators=100, random_state=42) selector = SelectFromModel(estimator=estimator, threshold=-np.inf, max_features=max_features).fit(X, y) selected_features = X.columns[selector.get_support()] log_and_print(f"Selected Features: {list(selected_features)}") return selected_features def encode_categorical(df): """Encodes categorical columns like 'trend' and 'regime'.""" le = LabelEncoder() for col in ['trend', 'regime']: if col in df.columns: df[col] = df[col].astype(str) df[col] = le.fit_transform(df[col]) return df def calculate_future_returns(df): """Calculates future returns for training the model.""" df["future_returns"] = df["close"].pct_change().shift(-1) return df.dropna() def ensure_same_columns(df1, df2): """Ensures two DataFrames have the same columns in the same order.""" missing_in_df1 = set(df2.columns) - set(df1.columns) for c in missing_in_df1: df1[c] = 0 missing_in_df2 = set(df1.columns) - set(df2.columns) for c in missing_in_df2: df2[c] = 0 return df1[df2.columns], df2 def detect_feature_drift(current_df, reference_df): """Detects drift between current data and a reference dataset.""" current_df, reference_df = ensure_same_columns(current_df.copy(), reference_df.copy()) drift_features = [] for column in current_df.columns: if column not in reference_df.columns: continue stat, p_value = ks_2samp(current_df[column].dropna(), reference_df[column].dropna()) if p_value < 0.05: drift_features.append(column) drift_detected = 1 if drift_features else 0 drift_window.append(drift_detected) drift_ratio = sum(drift_window) / len(drift_window) if drift_window else 0 log_and_print(f"Rolling drift ratio: {drift_ratio:.2f}") if drift_ratio > 0.5: log_and_print(f"Significant feature drift detected in: {drift_features}") return drift_features else: return []