Naposledy aktivní 2 months ago

Entry and exit signals with a sample (generic) python script

stock-entry-exit-technical-signals.md Raw

For entering positions, I'd focus on a combination of these technical indicators:

For Entry Signals:

  • Moving Average Crossovers - When a shorter-term MA (like 20-day) crosses above a longer-term MA (like 50-day), it can signal upward momentum
  • RSI (Relative Strength Index) - Looking for oversold conditions (RSI below 30) that might indicate a bounce opportunity
  • MACD - When the MACD line crosses above the signal line, especially after being in negative territory
  • Volume confirmation - Ensuring any breakout or signal is accompanied by higher-than-average volume
  • Support and Resistance levels - Entering near strong support levels or after a clean breakout above resistance

For Exit Signals:

  • Trailing stops based on ATR (Average True Range) to let winners run while protecting against major reversals
  • RSI overbought conditions (above 70) combined with bearish divergence
  • Moving average violations - When price closes below a key moving average that previously provided support
  • MACD bearish crossover - When MACD crosses below the signal line
  • Volume patterns - Selling into high volume spikes, especially if accompanied by reversal candlestick patterns
  • Predetermined risk/reward ratios - Taking profits at 2:1 or 3:1 reward-to-risk levels

The key is using multiple indicators together rather than relying on any single one, and always having a clear exit plan before entering any position. Volume confirmation is crucial for validating most technical signals.

technical-analysis.py Raw
1import pandas as pd
2import numpy as np
3from datetime import datetime, timedelta
4import warnings
5warnings.filterwarnings('ignore')
6
7class TechnicalAnalyzer:
8 def __init__(self, data):
9 """
10 Initialize with price data DataFrame
11 Expected columns: ['date', 'open', 'high', 'low', 'close', 'volume']
12 """
13 self.data = data.copy()
14 self.signals = pd.DataFrame()
15
16 def calculate_sma(self, period):
17 """Simple Moving Average"""
18 return self.data['close'].rolling(window=period).mean()
19
20 def calculate_ema(self, period):
21 """Exponential Moving Average"""
22 return self.data['close'].ewm(span=period).mean()
23
24 def calculate_rsi(self, period=14):
25 """Relative Strength Index"""
26 delta = self.data['close'].diff()
27 gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
28 loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
29 rs = gain / loss
30 rsi = 100 - (100 / (1 + rs))
31 return rsi
32
33 def calculate_macd(self, fast=12, slow=26, signal=9):
34 """MACD Indicator"""
35 ema_fast = self.calculate_ema(fast)
36 ema_slow = self.calculate_ema(slow)
37 macd_line = ema_fast - ema_slow
38 signal_line = macd_line.ewm(span=signal).mean()
39 histogram = macd_line - signal_line
40 return macd_line, signal_line, histogram
41
42 def calculate_bollinger_bands(self, period=20, std_dev=2):
43 """Bollinger Bands"""
44 sma = self.calculate_sma(period)
45 std = self.data['close'].rolling(window=period).std()
46 upper_band = sma + (std * std_dev)
47 lower_band = sma - (std * std_dev)
48 return upper_band, sma, lower_band
49
50 def calculate_atr(self, period=14):
51 """Average True Range"""
52 high_low = self.data['high'] - self.data['low']
53 high_close = np.abs(self.data['high'] - self.data['close'].shift())
54 low_close = np.abs(self.data['low'] - self.data['close'].shift())
55 ranges = pd.concat([high_low, high_close, low_close], axis=1)
56 true_range = np.max(ranges, axis=1)
57 atr = true_range.rolling(window=period).mean()
58 return atr
59
60 def calculate_volume_indicators(self):
61 """Volume-based indicators"""
62 # Volume Moving Average
63 vol_sma_20 = self.data['volume'].rolling(window=20).mean()
64 vol_ratio = self.data['volume'] / vol_sma_20
65
66 # On Balance Volume (OBV)
67 obv = (np.sign(self.data['close'].diff()) * self.data['volume']).fillna(0).cumsum()
68
69 return vol_ratio, obv
70
71 def generate_all_indicators(self):
72 """Calculate all technical indicators"""
73 # Moving Averages
74 self.data['sma_20'] = self.calculate_sma(20)
75 self.data['sma_50'] = self.calculate_sma(50)
76 self.data['ema_12'] = self.calculate_ema(12)
77 self.data['ema_26'] = self.calculate_ema(26)
78
79 # RSI
80 self.data['rsi'] = self.calculate_rsi()
81
82 # MACD
83 macd, signal, histogram = self.calculate_macd()
84 self.data['macd'] = macd
85 self.data['macd_signal'] = signal
86 self.data['macd_histogram'] = histogram
87
88 # Bollinger Bands
89 bb_upper, bb_middle, bb_lower = self.calculate_bollinger_bands()
90 self.data['bb_upper'] = bb_upper
91 self.data['bb_middle'] = bb_middle
92 self.data['bb_lower'] = bb_lower
93
94 # ATR
95 self.data['atr'] = self.calculate_atr()
96
97 # Volume indicators
98 vol_ratio, obv = self.calculate_volume_indicators()
99 self.data['vol_ratio'] = vol_ratio
100 self.data['obv'] = obv
101
102 return self.data
103
104 def identify_entry_signals(self):
105 """Identify potential entry points"""
106 signals = []
107
108 for i in range(1, len(self.data)):
109 entry_score = 0
110 reasons = []
111
112 current = self.data.iloc[i]
113 previous = self.data.iloc[i-1]
114
115 # Moving Average Crossover (Golden Cross)
116 if (current['sma_20'] > current['sma_50'] and
117 previous['sma_20'] <= previous['sma_50']):
118 entry_score += 2
119 reasons.append("SMA Golden Cross")
120
121 # Price above both MAs
122 if current['close'] > current['sma_20'] > current['sma_50']:
123 entry_score += 1
124 reasons.append("Price above MAs")
125
126 # RSI oversold recovery
127 if previous['rsi'] < 30 and current['rsi'] > 30:
128 entry_score += 2
129 reasons.append("RSI oversold recovery")
130
131 # MACD bullish crossover
132 if (current['macd'] > current['macd_signal'] and
133 previous['macd'] <= previous['macd_signal']):
134 entry_score += 2
135 reasons.append("MACD bullish crossover")
136
137 # Bollinger Band bounce
138 if previous['close'] <= previous['bb_lower'] and current['close'] > previous['bb_lower']:
139 entry_score += 1
140 reasons.append("BB lower band bounce")
141
142 # Volume confirmation
143 if current['vol_ratio'] > 1.5: # 50% above average
144 entry_score += 1
145 reasons.append("High volume")
146
147 # Strong overall conditions
148 if (current['rsi'] > 40 and current['rsi'] < 70 and
149 current['macd'] > 0):
150 entry_score += 1
151 reasons.append("Favorable momentum")
152
153 if entry_score >= 3: # Minimum threshold for entry
154 signals.append({
155 'date': current['date'],
156 'type': 'ENTRY',
157 'price': current['close'],
158 'score': entry_score,
159 'reasons': reasons
160 })
161
162 return signals
163
164 def identify_exit_signals(self):
165 """Identify potential exit points"""
166 signals = []
167
168 for i in range(1, len(self.data)):
169 exit_score = 0
170 reasons = []
171
172 current = self.data.iloc[i]
173 previous = self.data.iloc[i-1]
174
175 # Moving Average bearish cross
176 if (current['sma_20'] < current['sma_50'] and
177 previous['sma_20'] >= previous['sma_50']):
178 exit_score += 2
179 reasons.append("SMA Death Cross")
180
181 # Price below key MA
182 if current['close'] < current['sma_20']:
183 exit_score += 1
184 reasons.append("Price below SMA20")
185
186 # RSI overbought
187 if current['rsi'] > 70:
188 exit_score += 1
189 reasons.append("RSI overbought")
190
191 # RSI bearish divergence (simplified)
192 if previous['rsi'] > 70 and current['rsi'] < 70:
193 exit_score += 2
194 reasons.append("RSI overbought exit")
195
196 # MACD bearish crossover
197 if (current['macd'] < current['macd_signal'] and
198 previous['macd'] >= previous['macd_signal']):
199 exit_score += 2
200 reasons.append("MACD bearish crossover")
201
202 # Bollinger Band upper touch
203 if current['close'] >= current['bb_upper']:
204 exit_score += 1
205 reasons.append("BB upper band resistance")
206
207 # Volume spike (could indicate distribution)
208 if current['vol_ratio'] > 3.0:
209 exit_score += 1
210 reasons.append("Extreme volume spike")
211
212 if exit_score >= 3: # Minimum threshold for exit
213 signals.append({
214 'date': current['date'],
215 'type': 'EXIT',
216 'price': current['close'],
217 'score': exit_score,
218 'reasons': reasons
219 })
220
221 return signals
222
223 def analyze_stock(self):
224 """Complete analysis workflow"""
225 # Generate all indicators
226 self.generate_all_indicators()
227
228 # Get entry and exit signals
229 entry_signals = self.identify_entry_signals()
230 exit_signals = self.identify_exit_signals()
231
232 # Combine all signals
233 all_signals = entry_signals + exit_signals
234 all_signals = sorted(all_signals, key=lambda x: x['date'])
235
236 return all_signals, self.data
237
238# Example usage and demo data generation
239def generate_sample_data(days=252):
240 """Generate sample stock data for demonstration"""
241 np.random.seed(42) # For reproducible results
242
243 start_date = datetime.now() - timedelta(days=days)
244 dates = [start_date + timedelta(days=i) for i in range(days)]
245
246 # Generate realistic price movement
247 returns = np.random.normal(0.001, 0.02, days) # Daily returns
248 price = 100 # Starting price
249 prices = [price]
250
251 for ret in returns[1:]:
252 price *= (1 + ret)
253 prices.append(price)
254
255 # Generate OHLC data
256 data = []
257 for i, (date, close) in enumerate(zip(dates, prices)):
258 high = close * (1 + abs(np.random.normal(0, 0.015)))
259 low = close * (1 - abs(np.random.normal(0, 0.015)))
260 open_price = low + (high - low) * np.random.random()
261 volume = int(np.random.normal(1000000, 300000))
262
263 data.append({
264 'date': date,
265 'open': open_price,
266 'high': high,
267 'low': low,
268 'close': close,
269 'volume': max(volume, 100000) # Ensure positive volume
270 })
271
272 return pd.DataFrame(data)
273
274# Demo execution
275if __name__ == "__main__":
276 # Generate sample data
277 print("Generating sample stock data...")
278 sample_data = generate_sample_data(180) # 6 months of data
279
280 # Initialize analyzer
281 analyzer = TechnicalAnalyzer(sample_data)
282
283 # Run complete analysis
284 print("Analyzing technical indicators...")
285 signals, enhanced_data = analyzer.analyze_stock()
286
287 # Display results
288 print(f"\n=== TECHNICAL ANALYSIS RESULTS ===")
289 print(f"Analysis period: {len(sample_data)} days")
290 print(f"Total signals found: {len(signals)}")
291
292 # Show recent indicators
293 print(f"\n=== LATEST INDICATOR VALUES ===")
294 latest = enhanced_data.iloc[-1]
295 print(f"Price: ${latest['close']:.2f}")
296 print(f"RSI: {latest['rsi']:.2f}")
297 print(f"MACD: {latest['macd']:.4f}")
298 print(f"Volume Ratio: {latest['vol_ratio']:.2f}x")
299 print(f"20-day SMA: ${latest['sma_20']:.2f}")
300 print(f"50-day SMA: ${latest['sma_50']:.2f}")
301
302 # Show recent signals
303 print(f"\n=== RECENT SIGNALS ===")
304 recent_signals = [s for s in signals if s['date'] >= (datetime.now() - timedelta(days=30))]
305
306 if recent_signals:
307 for signal in recent_signals[-5:]: # Last 5 signals
308 print(f"\n{signal['type']} Signal:")
309 print(f" Date: {signal['date'].strftime('%Y-%m-%d')}")
310 print(f" Price: ${signal['price']:.2f}")
311 print(f" Score: {signal['score']}")
312 print(f" Reasons: {', '.join(signal['reasons'])}")
313 else:
314 print("No recent signals found.")
315
316 print(f"\n=== USAGE NOTES ===")
317 print("1. Replace sample data with real market data from your preferred source")
318 print("2. Adjust indicator parameters based on your trading style")
319 print("3. Modify signal thresholds based on backtesting results")
320 print("4. Always combine with risk management and position sizing")
321 print("5. Consider market conditions and fundamental analysis")