stock-entry-exit-technical-signals.md
· 1.5 KiB · Markdown
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.
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
· 12 KiB · Python
Raw
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')
class TechnicalAnalyzer:
def __init__(self, data):
"""
Initialize with price data DataFrame
Expected columns: ['date', 'open', 'high', 'low', 'close', 'volume']
"""
self.data = data.copy()
self.signals = pd.DataFrame()
def calculate_sma(self, period):
"""Simple Moving Average"""
return self.data['close'].rolling(window=period).mean()
def calculate_ema(self, period):
"""Exponential Moving Average"""
return self.data['close'].ewm(span=period).mean()
def calculate_rsi(self, period=14):
"""Relative Strength Index"""
delta = self.data['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
def calculate_macd(self, fast=12, slow=26, signal=9):
"""MACD Indicator"""
ema_fast = self.calculate_ema(fast)
ema_slow = self.calculate_ema(slow)
macd_line = ema_fast - ema_slow
signal_line = macd_line.ewm(span=signal).mean()
histogram = macd_line - signal_line
return macd_line, signal_line, histogram
def calculate_bollinger_bands(self, period=20, std_dev=2):
"""Bollinger Bands"""
sma = self.calculate_sma(period)
std = self.data['close'].rolling(window=period).std()
upper_band = sma + (std * std_dev)
lower_band = sma - (std * std_dev)
return upper_band, sma, lower_band
def calculate_atr(self, period=14):
"""Average True Range"""
high_low = self.data['high'] - self.data['low']
high_close = np.abs(self.data['high'] - self.data['close'].shift())
low_close = np.abs(self.data['low'] - self.data['close'].shift())
ranges = pd.concat([high_low, high_close, low_close], axis=1)
true_range = np.max(ranges, axis=1)
atr = true_range.rolling(window=period).mean()
return atr
def calculate_volume_indicators(self):
"""Volume-based indicators"""
# Volume Moving Average
vol_sma_20 = self.data['volume'].rolling(window=20).mean()
vol_ratio = self.data['volume'] / vol_sma_20
# On Balance Volume (OBV)
obv = (np.sign(self.data['close'].diff()) * self.data['volume']).fillna(0).cumsum()
return vol_ratio, obv
def generate_all_indicators(self):
"""Calculate all technical indicators"""
# Moving Averages
self.data['sma_20'] = self.calculate_sma(20)
self.data['sma_50'] = self.calculate_sma(50)
self.data['ema_12'] = self.calculate_ema(12)
self.data['ema_26'] = self.calculate_ema(26)
# RSI
self.data['rsi'] = self.calculate_rsi()
# MACD
macd, signal, histogram = self.calculate_macd()
self.data['macd'] = macd
self.data['macd_signal'] = signal
self.data['macd_histogram'] = histogram
# Bollinger Bands
bb_upper, bb_middle, bb_lower = self.calculate_bollinger_bands()
self.data['bb_upper'] = bb_upper
self.data['bb_middle'] = bb_middle
self.data['bb_lower'] = bb_lower
# ATR
self.data['atr'] = self.calculate_atr()
# Volume indicators
vol_ratio, obv = self.calculate_volume_indicators()
self.data['vol_ratio'] = vol_ratio
self.data['obv'] = obv
return self.data
def identify_entry_signals(self):
"""Identify potential entry points"""
signals = []
for i in range(1, len(self.data)):
entry_score = 0
reasons = []
current = self.data.iloc[i]
previous = self.data.iloc[i-1]
# Moving Average Crossover (Golden Cross)
if (current['sma_20'] > current['sma_50'] and
previous['sma_20'] <= previous['sma_50']):
entry_score += 2
reasons.append("SMA Golden Cross")
# Price above both MAs
if current['close'] > current['sma_20'] > current['sma_50']:
entry_score += 1
reasons.append("Price above MAs")
# RSI oversold recovery
if previous['rsi'] < 30 and current['rsi'] > 30:
entry_score += 2
reasons.append("RSI oversold recovery")
# MACD bullish crossover
if (current['macd'] > current['macd_signal'] and
previous['macd'] <= previous['macd_signal']):
entry_score += 2
reasons.append("MACD bullish crossover")
# Bollinger Band bounce
if previous['close'] <= previous['bb_lower'] and current['close'] > previous['bb_lower']:
entry_score += 1
reasons.append("BB lower band bounce")
# Volume confirmation
if current['vol_ratio'] > 1.5: # 50% above average
entry_score += 1
reasons.append("High volume")
# Strong overall conditions
if (current['rsi'] > 40 and current['rsi'] < 70 and
current['macd'] > 0):
entry_score += 1
reasons.append("Favorable momentum")
if entry_score >= 3: # Minimum threshold for entry
signals.append({
'date': current['date'],
'type': 'ENTRY',
'price': current['close'],
'score': entry_score,
'reasons': reasons
})
return signals
def identify_exit_signals(self):
"""Identify potential exit points"""
signals = []
for i in range(1, len(self.data)):
exit_score = 0
reasons = []
current = self.data.iloc[i]
previous = self.data.iloc[i-1]
# Moving Average bearish cross
if (current['sma_20'] < current['sma_50'] and
previous['sma_20'] >= previous['sma_50']):
exit_score += 2
reasons.append("SMA Death Cross")
# Price below key MA
if current['close'] < current['sma_20']:
exit_score += 1
reasons.append("Price below SMA20")
# RSI overbought
if current['rsi'] > 70:
exit_score += 1
reasons.append("RSI overbought")
# RSI bearish divergence (simplified)
if previous['rsi'] > 70 and current['rsi'] < 70:
exit_score += 2
reasons.append("RSI overbought exit")
# MACD bearish crossover
if (current['macd'] < current['macd_signal'] and
previous['macd'] >= previous['macd_signal']):
exit_score += 2
reasons.append("MACD bearish crossover")
# Bollinger Band upper touch
if current['close'] >= current['bb_upper']:
exit_score += 1
reasons.append("BB upper band resistance")
# Volume spike (could indicate distribution)
if current['vol_ratio'] > 3.0:
exit_score += 1
reasons.append("Extreme volume spike")
if exit_score >= 3: # Minimum threshold for exit
signals.append({
'date': current['date'],
'type': 'EXIT',
'price': current['close'],
'score': exit_score,
'reasons': reasons
})
return signals
def analyze_stock(self):
"""Complete analysis workflow"""
# Generate all indicators
self.generate_all_indicators()
# Get entry and exit signals
entry_signals = self.identify_entry_signals()
exit_signals = self.identify_exit_signals()
# Combine all signals
all_signals = entry_signals + exit_signals
all_signals = sorted(all_signals, key=lambda x: x['date'])
return all_signals, self.data
# Example usage and demo data generation
def generate_sample_data(days=252):
"""Generate sample stock data for demonstration"""
np.random.seed(42) # For reproducible results
start_date = datetime.now() - timedelta(days=days)
dates = [start_date + timedelta(days=i) for i in range(days)]
# Generate realistic price movement
returns = np.random.normal(0.001, 0.02, days) # Daily returns
price = 100 # Starting price
prices = [price]
for ret in returns[1:]:
price *= (1 + ret)
prices.append(price)
# Generate OHLC data
data = []
for i, (date, close) in enumerate(zip(dates, prices)):
high = close * (1 + abs(np.random.normal(0, 0.015)))
low = close * (1 - abs(np.random.normal(0, 0.015)))
open_price = low + (high - low) * np.random.random()
volume = int(np.random.normal(1000000, 300000))
data.append({
'date': date,
'open': open_price,
'high': high,
'low': low,
'close': close,
'volume': max(volume, 100000) # Ensure positive volume
})
return pd.DataFrame(data)
# Demo execution
if __name__ == "__main__":
# Generate sample data
print("Generating sample stock data...")
sample_data = generate_sample_data(180) # 6 months of data
# Initialize analyzer
analyzer = TechnicalAnalyzer(sample_data)
# Run complete analysis
print("Analyzing technical indicators...")
signals, enhanced_data = analyzer.analyze_stock()
# Display results
print(f"\n=== TECHNICAL ANALYSIS RESULTS ===")
print(f"Analysis period: {len(sample_data)} days")
print(f"Total signals found: {len(signals)}")
# Show recent indicators
print(f"\n=== LATEST INDICATOR VALUES ===")
latest = enhanced_data.iloc[-1]
print(f"Price: ${latest['close']:.2f}")
print(f"RSI: {latest['rsi']:.2f}")
print(f"MACD: {latest['macd']:.4f}")
print(f"Volume Ratio: {latest['vol_ratio']:.2f}x")
print(f"20-day SMA: ${latest['sma_20']:.2f}")
print(f"50-day SMA: ${latest['sma_50']:.2f}")
# Show recent signals
print(f"\n=== RECENT SIGNALS ===")
recent_signals = [s for s in signals if s['date'] >= (datetime.now() - timedelta(days=30))]
if recent_signals:
for signal in recent_signals[-5:]: # Last 5 signals
print(f"\n{signal['type']} Signal:")
print(f" Date: {signal['date'].strftime('%Y-%m-%d')}")
print(f" Price: ${signal['price']:.2f}")
print(f" Score: {signal['score']}")
print(f" Reasons: {', '.join(signal['reasons'])}")
else:
print("No recent signals found.")
print(f"\n=== USAGE NOTES ===")
print("1. Replace sample data with real market data from your preferred source")
print("2. Adjust indicator parameters based on your trading style")
print("3. Modify signal thresholds based on backtesting results")
print("4. Always combine with risk management and position sizing")
print("5. Consider market conditions and fundamental analysis")
| 1 | import pandas as pd |
| 2 | import numpy as np |
| 3 | from datetime import datetime, timedelta |
| 4 | import warnings |
| 5 | warnings.filterwarnings('ignore') |
| 6 | |
| 7 | class 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 |
| 239 | def 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 |
| 275 | if __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") |