JonusNattapong commited on
Commit
750d1a1
·
verified ·
1 Parent(s): 7769a00

Upload code/market_regime_detector.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. code/market_regime_detector.py +435 -0
code/market_regime_detector.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Market Regime Detection System
4
+ Identifies trending vs ranging markets and provides regime-adaptive trading parameters
5
+ """
6
+
7
+ import pandas as pd
8
+ import numpy as np
9
+ from enum import Enum
10
+ from typing import Dict, List, Tuple, Optional
11
+ import warnings
12
+ warnings.filterwarnings('ignore')
13
+
14
+ class MarketRegime(Enum):
15
+ """Market regime classifications"""
16
+ STRONG_BULL = "strong_bull"
17
+ BULL_TREND = "bull_trend"
18
+ BEAR_TREND = "bear_trend"
19
+ STRONG_BEAR = "strong_bear"
20
+ RANGING = "ranging"
21
+ HIGH_VOLATILITY = "high_volatility"
22
+ LOW_VOLATILITY = "low_volatility"
23
+
24
+ class RegimeParameters:
25
+ """Trading parameters optimized for each market regime"""
26
+
27
+ def __init__(self):
28
+ # Base parameters for each regime
29
+ self.regime_params = {
30
+ MarketRegime.STRONG_BULL: {
31
+ 'profit_targets': [0.015, 0.03, 0.06, 0.12], # Higher targets in strong trends
32
+ 'trailing_stop_pct': 0.03, # Tighter stops to capture momentum
33
+ 'position_size_multiplier': 1.5, # Larger positions in strong trends
34
+ 'max_holding_time': 48, # Longer holding in trends
35
+ 'breakeven_trigger': 0.02, # Later breakeven
36
+ 'min_confidence_threshold': 0.25, # Lower threshold for strong trends
37
+ },
38
+ MarketRegime.BULL_TREND: {
39
+ 'profit_targets': [0.012, 0.025, 0.05, 0.10],
40
+ 'trailing_stop_pct': 0.025,
41
+ 'position_size_multiplier': 1.2,
42
+ 'max_holding_time': 36,
43
+ 'breakeven_trigger': 0.018,
44
+ 'min_confidence_threshold': 0.3,
45
+ },
46
+ MarketRegime.BEAR_TREND: {
47
+ 'profit_targets': [0.012, 0.025, 0.05, 0.10],
48
+ 'trailing_stop_pct': 0.025,
49
+ 'position_size_multiplier': 1.2,
50
+ 'max_holding_time': 36,
51
+ 'breakeven_trigger': 0.018,
52
+ 'min_confidence_threshold': 0.3,
53
+ },
54
+ MarketRegime.STRONG_BEAR: {
55
+ 'profit_targets': [0.015, 0.03, 0.06, 0.12],
56
+ 'trailing_stop_pct': 0.03,
57
+ 'position_size_multiplier': 1.5,
58
+ 'max_holding_time': 48,
59
+ 'breakeven_trigger': 0.02,
60
+ 'min_confidence_threshold': 0.25,
61
+ },
62
+ MarketRegime.RANGING: {
63
+ 'profit_targets': [0.008, 0.015, 0.03, 0.06], # Lower targets in ranging markets
64
+ 'trailing_stop_pct': 0.02, # Tighter stops to protect against whipsaws
65
+ 'position_size_multiplier': 0.7, # Smaller positions in ranging markets
66
+ 'max_holding_time': 12, # Shorter holding periods
67
+ 'breakeven_trigger': 0.012, # Earlier breakeven
68
+ 'min_confidence_threshold': 0.4, # Higher threshold for ranging markets
69
+ },
70
+ MarketRegime.HIGH_VOLATILITY: {
71
+ 'profit_targets': [0.02, 0.04, 0.08, 0.15], # Higher targets for volatility
72
+ 'trailing_stop_pct': 0.04, # Wider stops for volatility
73
+ 'position_size_multiplier': 0.6, # Smaller positions in high volatility
74
+ 'max_holding_time': 8, # Very short holding periods
75
+ 'breakeven_trigger': 0.025, # Later breakeven for volatility
76
+ 'min_confidence_threshold': 0.5, # Much higher threshold
77
+ },
78
+ MarketRegime.LOW_VOLATILITY: {
79
+ 'profit_targets': [0.005, 0.01, 0.02, 0.04], # Lower targets in low volatility
80
+ 'trailing_stop_pct': 0.015, # Tighter stops
81
+ 'position_size_multiplier': 1.0, # Normal positions
82
+ 'max_holding_time': 24, # Normal holding periods
83
+ 'breakeven_trigger': 0.008, # Earlier breakeven
84
+ 'min_confidence_threshold': 0.35, # Moderate threshold
85
+ }
86
+ }
87
+
88
+ def get_parameters(self, regime: MarketRegime) -> Dict:
89
+ """Get trading parameters for a specific regime"""
90
+ return self.regime_params.get(regime, self.regime_params[MarketRegime.RANGING])
91
+
92
+ class MarketRegimeDetector:
93
+ """
94
+ Advanced market regime detection using multiple indicators
95
+ """
96
+
97
+ def __init__(self, lookback_periods: List[int] = [20, 50, 100]):
98
+ self.lookback_periods = lookback_periods
99
+ self.regime_history = []
100
+ self.parameters = RegimeParameters()
101
+
102
+ def detect_regime(self, price_data: pd.DataFrame, current_idx: int = -1) -> Tuple[MarketRegime, Dict]:
103
+ """
104
+ Detect current market regime using comprehensive analysis
105
+
106
+ Args:
107
+ price_data: DataFrame with OHLC and technical indicators
108
+ current_idx: Current index in the data (default: latest)
109
+
110
+ Returns:
111
+ Tuple of (detected_regime, regime_parameters)
112
+ """
113
+ if len(price_data) < max(self.lookback_periods):
114
+ return MarketRegime.RANGING, self.parameters.get_parameters(MarketRegime.RANGING)
115
+
116
+ # Extract current window
117
+ if current_idx == -1:
118
+ current_idx = len(price_data) - 1
119
+
120
+ # Calculate regime indicators
121
+ trend_strength = self._calculate_trend_strength(price_data, current_idx)
122
+ volatility = self._calculate_volatility(price_data, current_idx)
123
+ momentum = self._calculate_momentum(price_data, current_idx)
124
+ volume_trend = self._calculate_volume_trend(price_data, current_idx)
125
+
126
+ # Determine primary regime
127
+ regime = self._classify_regime(trend_strength, volatility, momentum, volume_trend)
128
+
129
+ # Get regime-specific parameters
130
+ regime_params = self.parameters.get_parameters(regime)
131
+
132
+ # Store regime history
133
+ self.regime_history.append({
134
+ 'timestamp': price_data.index[current_idx] if hasattr(price_data.index, '__getitem__') else current_idx,
135
+ 'regime': regime,
136
+ 'trend_strength': trend_strength,
137
+ 'volatility': volatility,
138
+ 'momentum': momentum
139
+ })
140
+
141
+ return regime, regime_params
142
+
143
+ def _calculate_trend_strength(self, data: pd.DataFrame, idx: int) -> float:
144
+ """Calculate trend strength using ADX-like indicator"""
145
+ if 'High' not in data.columns or 'Low' not in data.columns or 'Close' not in data.columns:
146
+ # Fallback for data without OHLC
147
+ if 'Close' in data.columns:
148
+ prices = data['Close'].iloc[max(0, idx-50):idx+1]
149
+ if len(prices) > 10:
150
+ # Simple trend strength based on slope
151
+ x = np.arange(len(prices))
152
+ slope = np.polyfit(x, prices.values, 1)[0]
153
+ return abs(slope) / prices.std() # Normalized slope
154
+ return 0.0
155
+
156
+ # Calculate True Range
157
+ high = data['High'].iloc[max(0, idx-50):idx+1]
158
+ low = data['Low'].iloc[max(0, idx-50):idx+1]
159
+ close = data['Close'].iloc[max(0, idx-50):idx+1]
160
+
161
+ tr = np.maximum(high - low,
162
+ np.maximum(abs(high - close.shift(1)),
163
+ abs(low - close.shift(1))))
164
+
165
+ # Calculate Directional Movement
166
+ dm_plus = np.where((high - high.shift(1)) > (low.shift(1) - low),
167
+ np.maximum(high - high.shift(1), 0), 0)
168
+ dm_minus = np.where((low.shift(1) - low) > (high - high.shift(1)),
169
+ np.maximum(low.shift(1) - low, 0), 0)
170
+
171
+ # Convert to pandas Series for rolling operations
172
+ dm_plus_series = pd.Series(dm_plus, index=high.index)
173
+ dm_minus_series = pd.Series(dm_minus, index=high.index)
174
+ tr_series = pd.Series(tr, index=high.index)
175
+
176
+ # Smooth with Wilder's smoothing
177
+ atr = tr_series.rolling(14).mean()
178
+ di_plus = 100 * (dm_plus_series.rolling(14).mean() / atr)
179
+ di_minus = 100 * (dm_minus_series.rolling(14).mean() / atr)
180
+
181
+ # ADX (trend strength)
182
+ dx = 100 * abs(di_plus - di_minus) / (di_plus + di_minus)
183
+ adx = dx.rolling(14).mean()
184
+
185
+ return adx.iloc[-1] if not adx.empty else 0.0
186
+
187
+ def _calculate_volatility(self, data: pd.DataFrame, idx: int) -> float:
188
+ """Calculate normalized volatility"""
189
+ if 'Close' not in data.columns:
190
+ return 0.0
191
+
192
+ prices = data['Close'].iloc[max(0, idx-50):idx+1]
193
+ if len(prices) < 10:
194
+ return 0.0
195
+
196
+ # Calculate multiple volatility measures
197
+ returns = prices.pct_change().dropna()
198
+
199
+ # Bollinger Band width (normalized)
200
+ sma = prices.rolling(20).mean()
201
+ std = prices.rolling(20).std()
202
+ bb_width = 2 * std / sma
203
+ bb_volatility = bb_width.iloc[-1] if not bb_width.empty else 0
204
+
205
+ # ATR if available
206
+ if 'High' in data.columns and 'Low' in data.columns:
207
+ high = data['High'].iloc[max(0, idx-20):idx+1]
208
+ low = data['Low'].iloc[max(0, idx-20):idx+1]
209
+ close = data['Close'].iloc[max(0, idx-20):idx+1]
210
+
211
+ tr = np.maximum(high - low,
212
+ np.maximum(abs(high - close.shift(1)),
213
+ abs(low - close.shift(1))))
214
+ atr = tr.rolling(14).mean()
215
+ atr_volatility = atr.iloc[-1] / prices.iloc[-1] if not atr.empty else 0
216
+ else:
217
+ atr_volatility = 0
218
+
219
+ # Return volatility
220
+ ret_volatility = returns.std() * np.sqrt(252) # Annualized
221
+
222
+ # Combine measures
223
+ combined_volatility = (bb_volatility * 0.4 + atr_volatility * 0.4 + ret_volatility * 0.2)
224
+
225
+ return combined_volatility
226
+
227
+ def _calculate_momentum(self, data: pd.DataFrame, idx: int) -> float:
228
+ """Calculate momentum indicators"""
229
+ if 'Close' not in data.columns:
230
+ return 0.0
231
+
232
+ prices = data['Close'].iloc[max(0, idx-50):idx+1]
233
+ if len(prices) < 20:
234
+ return 0.0
235
+
236
+ # RSI
237
+ delta = prices.diff()
238
+ gain = (delta.where(delta > 0, 0)).rolling(14).mean()
239
+ loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
240
+ rs = gain / loss
241
+ rsi = 100 - (100 / (1 + rs))
242
+ rsi_value = rsi.iloc[-1] if not rsi.empty else 50
243
+
244
+ # MACD momentum
245
+ ema12 = prices.ewm(span=12).mean()
246
+ ema26 = prices.ewm(span=26).mean()
247
+ macd = ema12 - ema26
248
+ macd_signal = macd.ewm(span=9).mean()
249
+ macd_momentum = (macd - macd_signal).iloc[-1] if not macd.empty else 0
250
+
251
+ # Normalize RSI to momentum score (-1 to 1)
252
+ rsi_momentum = (rsi_value - 50) / 50
253
+
254
+ # Combine momentum indicators
255
+ combined_momentum = (rsi_momentum * 0.6 + np.sign(macd_momentum) * 0.4)
256
+
257
+ return combined_momentum
258
+
259
+ def _calculate_volume_trend(self, data: pd.DataFrame, idx: int) -> float:
260
+ """Calculate volume trend if volume data is available"""
261
+ if 'Volume' not in data.columns:
262
+ return 0.0
263
+
264
+ volume = data['Volume'].iloc[max(0, idx-50):idx+1]
265
+ if len(volume) < 20:
266
+ return 0.0
267
+
268
+ # Volume trend
269
+ volume_sma = volume.rolling(20).mean()
270
+ volume_trend = (volume.iloc[-1] - volume_sma.iloc[-1]) / volume_sma.iloc[-1]
271
+
272
+ return volume_trend
273
+
274
+ def _classify_regime(self, trend_strength: float, volatility: float,
275
+ momentum: float, volume_trend: float) -> MarketRegime:
276
+ """
277
+ Classify market regime based on calculated indicators
278
+ """
279
+
280
+ # Volatility thresholds
281
+ HIGH_VOL_THRESHOLD = 0.05
282
+ LOW_VOL_THRESHOLD = 0.015
283
+
284
+ # Trend strength thresholds
285
+ STRONG_TREND_THRESHOLD = 25
286
+ MODERATE_TREND_THRESHOLD = 20
287
+
288
+ # Momentum thresholds
289
+ BULL_MOMENTUM_THRESHOLD = 0.3
290
+ BEAR_MOMENTUM_THRESHOLD = -0.3
291
+
292
+ # High volatility regime
293
+ if volatility > HIGH_VOL_THRESHOLD:
294
+ return MarketRegime.HIGH_VOLATILITY
295
+
296
+ # Low volatility regime
297
+ if volatility < LOW_VOL_THRESHOLD:
298
+ return MarketRegime.LOW_VOLATILITY
299
+
300
+ # Strong trending regimes
301
+ if trend_strength > STRONG_TREND_THRESHOLD:
302
+ if momentum > BULL_MOMENTUM_THRESHOLD:
303
+ return MarketRegime.STRONG_BULL
304
+ elif momentum < BEAR_MOMENTUM_THRESHOLD:
305
+ return MarketRegime.STRONG_BEAR
306
+
307
+ # Moderate trending regimes
308
+ if trend_strength > MODERATE_TREND_THRESHOLD:
309
+ if momentum > 0.1:
310
+ return MarketRegime.BULL_TREND
311
+ elif momentum < -0.1:
312
+ return MarketRegime.BEAR_TREND
313
+
314
+ # Default to ranging market
315
+ return MarketRegime.RANGING
316
+
317
+ def get_regime_statistics(self) -> Dict:
318
+ """Get statistics about regime detection performance"""
319
+ if not self.regime_history:
320
+ return {}
321
+
322
+ df = pd.DataFrame(self.regime_history)
323
+
324
+ stats = {
325
+ 'total_observations': len(df),
326
+ 'regime_distribution': df['regime'].value_counts().to_dict(),
327
+ 'avg_trend_strength': df['trend_strength'].mean(),
328
+ 'avg_volatility': df['volatility'].mean(),
329
+ 'avg_momentum': df['momentum'].mean(),
330
+ 'regime_transitions': self._calculate_regime_transitions(df)
331
+ }
332
+
333
+ return stats
334
+
335
+ def _calculate_regime_transitions(self, df: pd.DataFrame) -> Dict:
336
+ """Calculate regime transition frequencies"""
337
+ transitions = {}
338
+ regimes = df['regime'].values
339
+
340
+ for i in range(1, len(regimes)):
341
+ from_regime = regimes[i-1]
342
+ to_regime = regimes[i]
343
+ key = f"{from_regime.value}_to_{to_regime.value}"
344
+ transitions[key] = transitions.get(key, 0) + 1
345
+
346
+ return transitions
347
+
348
+ def get_optimal_parameters_for_regime(self, regime: MarketRegime) -> Dict:
349
+ """Get optimal trading parameters for a specific regime"""
350
+ return self.parameters.get_parameters(regime)
351
+
352
+ def create_sample_regime_analysis():
353
+ """Create a sample analysis showing regime detection in action"""
354
+ print("🧠 MARKET REGIME DETECTION SYSTEM")
355
+ print("=" * 50)
356
+
357
+ # Create sample price data
358
+ np.random.seed(42)
359
+ n_points = 500
360
+
361
+ # Generate realistic price data with different regimes
362
+ base_price = 2000
363
+ prices = [base_price]
364
+
365
+ for i in range(1, n_points):
366
+ # Different volatility regimes
367
+ if i < 100: # Low volatility ranging
368
+ volatility = 0.005
369
+ trend = 0.0001
370
+ elif i < 200: # High volatility
371
+ volatility = 0.02
372
+ trend = 0.0005
373
+ elif i < 350: # Strong bull trend
374
+ volatility = 0.015
375
+ trend = 0.002
376
+ else: # Bear trend
377
+ volatility = 0.012
378
+ trend = -0.0015
379
+
380
+ # Add momentum component
381
+ momentum = 0.1 * (prices[-1] - prices[-2]) / prices[-2] if len(prices) > 1 else 0
382
+
383
+ # Generate price movement
384
+ price_change = trend + np.random.normal(0, volatility) + momentum
385
+ new_price = prices[-1] * (1 + price_change)
386
+ prices.append(max(1800, min(2200, new_price)))
387
+
388
+ # Create DataFrame
389
+ data = pd.DataFrame({
390
+ 'Close': prices,
391
+ 'High': [p * (1 + np.random.uniform(0, 0.005)) for p in prices],
392
+ 'Low': [p * (1 - np.random.uniform(0, 0.005)) for p in prices],
393
+ 'Volume': np.random.randint(1000, 10000, n_points)
394
+ })
395
+
396
+ # Initialize regime detector
397
+ detector = MarketRegimeDetector()
398
+
399
+ # Analyze regimes
400
+ regimes = []
401
+ regime_params = []
402
+
403
+ print("\n🔍 Analyzing market regimes...")
404
+ for i in range(50, len(data)): # Start after minimum lookback
405
+ regime, params = detector.detect_regime(data, i)
406
+ regimes.append(regime)
407
+ regime_params.append(params)
408
+
409
+ if i % 100 == 0:
410
+ print(f"Processed {i}/{len(data)} data points...")
411
+
412
+ # Show regime distribution
413
+ regime_counts = pd.Series([r.value for r in regimes]).value_counts()
414
+ print("\n📊 Regime Distribution:")
415
+ for regime, count in regime_counts.items():
416
+ pct = count / len(regimes) * 100
417
+ print(f" {regime}: {count} ({pct:.1f}%)")
418
+
419
+ # Show example parameters for each regime
420
+ print("\n🎯 Regime-Specific Parameters:")
421
+ unique_regimes = set(regimes)
422
+ for regime in unique_regimes:
423
+ params = detector.get_optimal_parameters_for_regime(regime)
424
+ print(f"\n{regime.value.upper()}:")
425
+ print(f" Profit Targets: {[f'{t*100:.1f}%' for t in params['profit_targets']]}")
426
+ print(f" Trailing Stop: {params['trailing_stop_pct']*100:.1f}%")
427
+ print(f" Position Size Multiplier: {params['position_size_multiplier']:.1f}x")
428
+ print(f" Max Holding Time: {params['max_holding_time']} hours")
429
+ print(f" Breakeven Trigger: {params['breakeven_trigger']*100:.1f}%")
430
+
431
+ print("\n✅ Market regime detection system ready!")
432
+ print("This will significantly improve trading performance by adapting to market conditions.")
433
+
434
+ if __name__ == "__main__":
435
+ create_sample_regime_analysis()