ckharche commited on
Commit
bf1c8d9
·
verified ·
1 Parent(s): 8e1b1dc

Upload indicators.py

Browse files
Files changed (1) hide show
  1. trade_analysis/indicators.py +375 -0
trade_analysis/indicators.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # trade_analysis/indicators.py
2
+ """
3
+ Technical indicators module - Enhanced for momentum trading
4
+ Works with your enhanced_api.py and other modules
5
+ """
6
+
7
+ import pandas as pd
8
+ import numpy as np
9
+ import warnings
10
+ warnings.filterwarnings('ignore')
11
+
12
+ def enrich_with_indicators(df: pd.DataFrame, interval_str: str) -> pd.DataFrame:
13
+ """
14
+ Enrich dataframe with technical indicators
15
+ Compatible with your data.py output format
16
+ """
17
+ if df.empty or 'Close' not in df.columns:
18
+ return pd.DataFrame()
19
+
20
+ print(f"Enriching data for interval: {interval_str}, {len(df)} rows.")
21
+ df_enriched = df.copy()
22
+
23
+ # Ensure we have all OHLCV columns
24
+ required_cols = ['Open', 'High', 'Low', 'Close', 'Volume']
25
+ for col in required_cols:
26
+ if col not in df_enriched.columns:
27
+ # Handle case variations (Close vs close)
28
+ lower_col = col.lower()
29
+ if lower_col in df_enriched.columns:
30
+ df_enriched[col] = df_enriched[lower_col]
31
+ else:
32
+ df_enriched[col] = np.nan
33
+
34
+ try:
35
+ # Try using pandas_ta if available
36
+ import pandas_ta as ta
37
+
38
+ # 1. ADX (Length 9) - Momentum strength
39
+ try:
40
+ adx_results = ta.adx(df_enriched['High'], df_enriched['Low'],
41
+ df_enriched['Close'], length=9)
42
+ if adx_results is not None and not adx_results.empty:
43
+ df_enriched['ADX_9'] = adx_results.iloc[:, 0]
44
+ else:
45
+ df_enriched['ADX_9'] = 25.0 # Default neutral
46
+ except:
47
+ df_enriched['ADX_9'] = 25.0
48
+
49
+ # 2. RSI (Length 14) - Momentum oscillator
50
+ try:
51
+ rsi = ta.rsi(df_enriched['Close'], length=14)
52
+ df_enriched['RSI_14'] = rsi if rsi is not None else 50.0
53
+ except:
54
+ df_enriched['RSI_14'] = calculate_rsi_manual(df_enriched['Close'], 14)
55
+
56
+ # 3. MACD - Trend following
57
+ try:
58
+ macd = ta.macd(df_enriched['Close'], fast=12, slow=26, signal=9)
59
+ if macd is not None and not macd.empty:
60
+ # Find the histogram column
61
+ for col in macd.columns:
62
+ if 'h' in col.lower() or 'hist' in col.lower():
63
+ df_enriched['MACDh_12_26_9'] = macd[col]
64
+ break
65
+ else:
66
+ df_enriched['MACDh_12_26_9'] = 0
67
+ else:
68
+ df_enriched['MACDh_12_26_9'] = 0
69
+ except:
70
+ df_enriched['MACDh_12_26_9'] = 0
71
+
72
+ # 4. EMA (Length 9) - Fast moving average
73
+ try:
74
+ ema = ta.ema(df_enriched['Close'], length=9)
75
+ df_enriched['EMA_9'] = ema if ema is not None else df_enriched['Close'].ewm(span=9).mean()
76
+ except:
77
+ df_enriched['EMA_9'] = df_enriched['Close'].ewm(span=9, adjust=False).mean()
78
+
79
+ # 5. ATR (Length 14) - Volatility
80
+ try:
81
+ atr = ta.atr(df_enriched['High'], df_enriched['Low'],
82
+ df_enriched['Close'], length=14)
83
+ df_enriched['ATR_14'] = atr if atr is not None else calculate_atr_manual(df_enriched, 14)
84
+ except:
85
+ df_enriched['ATR_14'] = calculate_atr_manual(df_enriched, 14)
86
+
87
+ # 6. VWAP - Only for intraday
88
+ if interval_str in ['15m', '5m', '1m', 'hourly']:
89
+ try:
90
+ vwap = ta.vwap(df_enriched['High'], df_enriched['Low'],
91
+ df_enriched['Close'], df_enriched['Volume'])
92
+ df_enriched['VWAP'] = vwap if vwap is not None else df_enriched['Close']
93
+ except:
94
+ df_enriched['VWAP'] = calculate_vwap_manual(df_enriched)
95
+
96
+ except ImportError:
97
+ print("pandas_ta not available, using manual calculations")
98
+ # Fallback to manual calculations
99
+ df_enriched['RSI_14'] = calculate_rsi_manual(df_enriched['Close'], 14)
100
+ df_enriched['ADX_9'] = 25.0 # Default
101
+ df_enriched['MACDh_12_26_9'] = calculate_macd_histogram_manual(df_enriched['Close'])
102
+ df_enriched['EMA_9'] = df_enriched['Close'].ewm(span=9, adjust=False).mean()
103
+ df_enriched['ATR_14'] = calculate_atr_manual(df_enriched, 14)
104
+ if interval_str in ['15m', '5m', '1m', 'hourly']:
105
+ df_enriched['VWAP'] = calculate_vwap_manual(df_enriched)
106
+
107
+ # Custom momentum indicators for your strategy
108
+
109
+ # 7. Volume analysis
110
+ df_enriched['volume_ma_20'] = df_enriched['Volume'].rolling(20).mean()
111
+ df_enriched['volume_spike'] = df_enriched['Volume'] > (df_enriched['volume_ma_20'] * 2.0)
112
+ df_enriched['volume_exhaustion'] = (
113
+ df_enriched['Volume'].rolling(5).mean() < (df_enriched['volume_ma_20'] * 0.8)
114
+ )
115
+
116
+ # 8. Price momentum
117
+ df_enriched['returns'] = df_enriched['Close'].pct_change()
118
+ df_enriched['momentum_5'] = df_enriched['Close'] / df_enriched['Close'].shift(5) - 1
119
+ df_enriched['momentum_10'] = df_enriched['Close'] / df_enriched['Close'].shift(10) - 1
120
+
121
+ # 9. Volatility
122
+ df_enriched['volatility'] = df_enriched['returns'].rolling(20).std() * np.sqrt(252)
123
+
124
+ # 10. High-Low ratio (for gap detection)
125
+ df_enriched['high_low_ratio'] = (
126
+ (df_enriched['High'] - df_enriched['Low']) / df_enriched['Close']
127
+ )
128
+
129
+ # 11. Support/Resistance levels
130
+ df_enriched['resistance'] = df_enriched['High'].rolling(20).max()
131
+ df_enriched['support'] = df_enriched['Low'].rolling(20).min()
132
+
133
+ # 12. Trend strength
134
+ sma_20 = df_enriched['Close'].rolling(20).mean()
135
+ sma_50 = df_enriched['Close'].rolling(50).mean()
136
+ df_enriched['trend_strength'] = (sma_20 - sma_50) / sma_50 * 100
137
+
138
+ # Clean up
139
+ df_enriched.fillna(method='bfill', inplace=True)
140
+ df_enriched.fillna(method='ffill', inplace=True)
141
+ df_enriched.fillna(0, inplace=True)
142
+
143
+ return df_enriched
144
+
145
+ def identify_current_setup(df: pd.DataFrame, timeframe_str: str) -> dict:
146
+ """
147
+ Identify current market setup for trading decisions
148
+ Returns dict compatible with enhanced_api.py expectations
149
+ """
150
+ if df.empty or len(df) < 2:
151
+ return {
152
+ "timeframe": timeframe_str,
153
+ "direction": "neutral",
154
+ "adx": 0,
155
+ "rsi": 50,
156
+ "gap_risk": "unknown",
157
+ "volume_spike": False,
158
+ "volume_exhaustion": False,
159
+ "error": "Insufficient data"
160
+ }
161
+
162
+ # Get latest values
163
+ latest = df.iloc[-1]
164
+ prev = df.iloc[-2] if len(df) > 1 else latest
165
+
166
+ # Determine direction
167
+ if latest.get('Close', 0) > prev.get('Close', 0):
168
+ direction = "up"
169
+ elif latest.get('Close', 0) < prev.get('Close', 0):
170
+ direction = "down"
171
+ else:
172
+ direction = "neutral"
173
+
174
+ # Gap risk assessment
175
+ atr_val = latest.get('ATR_14', 0)
176
+ close_val = latest.get('Close', 0)
177
+ gap_risk = "low"
178
+
179
+ if close_val > 0 and atr_val > 0:
180
+ atr_percentage = atr_val / close_val
181
+ if atr_percentage > 0.02:
182
+ gap_risk = "high"
183
+ elif atr_percentage > 0.01:
184
+ gap_risk = "moderate"
185
+
186
+ # Momentum assessment
187
+ rsi = latest.get('RSI_14', 50)
188
+ momentum_5 = latest.get('momentum_5', 0)
189
+ momentum_10 = latest.get('momentum_10', 0)
190
+
191
+ # Trend assessment
192
+ trend = "neutral"
193
+ if latest.get('EMA_9', 0) > latest.get('Close', 0):
194
+ trend = "bearish"
195
+ elif latest.get('EMA_9', 0) < latest.get('Close', 0):
196
+ trend = "bullish"
197
+
198
+ # Volume analysis
199
+ volume_spike = bool(latest.get('volume_spike', False))
200
+ volume_exhaustion = bool(latest.get('volume_exhaustion', False))
201
+
202
+ # Support/Resistance proximity
203
+ close = latest.get('Close', 0)
204
+ resistance = latest.get('resistance', close * 1.02)
205
+ support = latest.get('support', close * 0.98)
206
+
207
+ near_resistance = (resistance - close) / close < 0.005 # Within 0.5%
208
+ near_support = (close - support) / close < 0.005
209
+
210
+ # Build setup dictionary
211
+ setup = {
212
+ "timeframe": timeframe_str,
213
+ "direction": direction,
214
+ "adx": round(latest.get('ADX_9', 0), 2),
215
+ "rsi": round(rsi, 2),
216
+ "gap_risk": gap_risk,
217
+ "volume_spike": volume_spike,
218
+ "volume_exhaustion": volume_exhaustion,
219
+ "trend": trend,
220
+ "momentum_5": round(momentum_5 * 100, 2), # As percentage
221
+ "momentum_10": round(momentum_10 * 100, 2),
222
+ "volatility": round(latest.get('volatility', 0), 4),
223
+ "near_resistance": near_resistance,
224
+ "near_support": near_support,
225
+ "macd_histogram": round(latest.get('MACDh_12_26_9', 0), 4)
226
+ }
227
+
228
+ # Add timeframe-specific signals
229
+ if timeframe_str == "15m":
230
+ setup["scalp_ready"] = (
231
+ volume_spike and
232
+ abs(momentum_5) > 0.005 and
233
+ 30 < rsi < 70
234
+ )
235
+ elif timeframe_str == "hourly":
236
+ setup["swing_ready"] = (
237
+ trend != "neutral" and
238
+ not volume_exhaustion and
239
+ 20 < rsi < 80
240
+ )
241
+ elif timeframe_str == "daily":
242
+ setup["position_ready"] = (
243
+ latest.get('ADX_9', 0) > 25 and
244
+ trend != "neutral"
245
+ )
246
+
247
+ return setup
248
+
249
+ # Manual calculation functions (fallbacks)
250
+
251
+ def calculate_rsi_manual(close_prices: pd.Series, period: int = 14) -> pd.Series:
252
+ """Manual RSI calculation"""
253
+ delta = close_prices.diff()
254
+ gain = delta.where(delta > 0, 0)
255
+ loss = -delta.where(delta < 0, 0)
256
+
257
+ avg_gain = gain.rolling(window=period).mean()
258
+ avg_loss = loss.rolling(window=period).mean()
259
+
260
+ rs = avg_gain / avg_loss
261
+ rsi = 100 - (100 / (1 + rs))
262
+
263
+ return rsi.fillna(50)
264
+
265
+ def calculate_atr_manual(df: pd.DataFrame, period: int = 14) -> pd.Series:
266
+ """Manual ATR calculation"""
267
+ high = df['High']
268
+ low = df['Low']
269
+ close = df['Close']
270
+
271
+ tr1 = high - low
272
+ tr2 = abs(high - close.shift())
273
+ tr3 = abs(low - close.shift())
274
+
275
+ tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
276
+ atr = tr.rolling(window=period).mean()
277
+
278
+ return atr.fillna(0)
279
+
280
+ def calculate_macd_histogram_manual(close_prices: pd.Series) -> pd.Series:
281
+ """Manual MACD histogram calculation"""
282
+ ema_12 = close_prices.ewm(span=12, adjust=False).mean()
283
+ ema_26 = close_prices.ewm(span=26, adjust=False).mean()
284
+ macd_line = ema_12 - ema_26
285
+ signal_line = macd_line.ewm(span=9, adjust=False).mean()
286
+ histogram = macd_line - signal_line
287
+
288
+ return histogram.fillna(0)
289
+
290
+ def calculate_vwap_manual(df: pd.DataFrame) -> pd.Series:
291
+ """Manual VWAP calculation"""
292
+ typical_price = (df['High'] + df['Low'] + df['Close']) / 3
293
+ cumulative_tpv = (typical_price * df['Volume']).cumsum()
294
+ cumulative_volume = df['Volume'].cumsum()
295
+ vwap = cumulative_tpv / cumulative_volume
296
+
297
+ return vwap.fillna(df['Close'])
298
+
299
+ # Additional helper functions for agent.py and other modules
300
+
301
+ def get_momentum_signals(df: pd.DataFrame) -> dict:
302
+ """
303
+ Get momentum signals for the agent
304
+ Used by agent.py for quick decisions
305
+ """
306
+ if df.empty or len(df) < 20:
307
+ return {"signal": "NEUTRAL", "strength": 0}
308
+
309
+ latest = df.iloc[-1]
310
+
311
+ # Check multiple momentum conditions
312
+ rsi = latest.get('RSI_14', 50)
313
+ momentum_5 = latest.get('momentum_5', 0)
314
+ volume_spike = latest.get('volume_spike', False)
315
+ macd_hist = latest.get('MACDh_12_26_9', 0)
316
+
317
+ # Bullish signals
318
+ if (rsi > 55 and momentum_5 > 0.01 and volume_spike and macd_hist > 0):
319
+ return {"signal": "BULLISH", "strength": 0.8}
320
+ elif (rsi > 50 and momentum_5 > 0.005):
321
+ return {"signal": "BULLISH", "strength": 0.6}
322
+
323
+ # Bearish signals
324
+ elif (rsi < 45 and momentum_5 < -0.01 and volume_spike and macd_hist < 0):
325
+ return {"signal": "BEARISH", "strength": 0.8}
326
+ elif (rsi < 50 and momentum_5 < -0.005):
327
+ return {"signal": "BEARISH", "strength": 0.6}
328
+
329
+ # Neutral
330
+ else:
331
+ return {"signal": "NEUTRAL", "strength": 0.3}
332
+
333
+ def calculate_entry_signals(df: pd.DataFrame, timeframe: str) -> dict:
334
+ """
335
+ Calculate specific entry signals for different timeframes
336
+ Used by enhanced_api.py for options strategies
337
+ """
338
+ if df.empty:
339
+ return {"entry": False, "confidence": 0}
340
+
341
+ setup = identify_current_setup(df, timeframe)
342
+
343
+ # Timeframe-specific entry logic
344
+ if timeframe in ["1m", "5m"]:
345
+ # Scalping entries
346
+ entry = (
347
+ setup.get('volume_spike', False) and
348
+ abs(setup.get('momentum_5', 0)) > 0.5 and
349
+ 30 < setup.get('rsi', 50) < 70
350
+ )
351
+ confidence = 70 if entry else 30
352
+
353
+ elif timeframe == "15m":
354
+ # Momentum entries
355
+ entry = (
356
+ setup.get('direction') != 'neutral' and
357
+ setup.get('adx', 0) > 25 and
358
+ not setup.get('volume_exhaustion', False)
359
+ )
360
+ confidence = 75 if entry else 40
361
+
362
+ else: # Daily/Hourly
363
+ # Swing entries
364
+ entry = (
365
+ setup.get('trend') != 'neutral' and
366
+ 20 < setup.get('rsi', 50) < 80 and
367
+ setup.get('adx', 0) > 20
368
+ )
369
+ confidence = 80 if entry else 35
370
+
371
+ return {
372
+ "entry": entry,
373
+ "confidence": confidence,
374
+ "setup_details": setup
375
+ }