I'll show you how to structure data and prompts for effective technical analysis with LLMs. Here's a comprehensive approach:
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
def prepare_ta_data(df: pd.DataFrame) -> pd.DataFrame:
"""
Prepare OHLCV data with technical indicators for LLM analysis
"""
# Ensure we have basic columns
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
# Calculate key indicators
# Moving Averages
df['sma_10'] = df['close'].rolling(10).mean()
df['sma_20'] = df['close'].rolling(20).mean()
df['sma_50'] = df['close'].rolling(50).mean()
df['ema_12'] = df['close'].ewm(span=12).mean()
df['ema_26'] = df['close'].ewm(span=26).mean()
# RSI
def calculate_rsi(data, period=14):
delta = data.diff()
gain = (delta.where(delta > 0, 0)).rolling(period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(period).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))
df['rsi'] = calculate_rsi(df['close'])
# MACD
df['macd'] = df['ema_12'] - df['ema_26']
df['macd_signal'] = df['macd'].ewm(span=9).mean()
df['macd_histogram'] = df['macd'] - df['macd_signal']
# Bollinger Bands
bb_period = 20
df['bb_middle'] = df['close'].rolling(bb_period).mean()
bb_std = df['close'].rolling(bb_period).std()
df['bb_upper'] = df['bb_middle'] + (2 * bb_std)
df['bb_lower'] = df['bb_middle'] - (2 * bb_std)
# ATR (Average True Range)
high_low = df['high'] - df['low']
high_close = np.abs(df['high'] - df['close'].shift())
low_close = np.abs(df['low'] - df['close'].shift())
ranges = pd.concat([high_low, high_close, low_close], axis=1)
true_range = ranges.max(axis=1)
df['atr'] = true_range.rolling(14).mean()
# Volume indicators
df['volume_sma'] = df['volume'].rolling(20).mean()
df['volume_ratio'] = df['volume'] / df['volume_sma']
# Price change metrics
df['pct_change'] = df['close'].pct_change()
df['intraday_range'] = (df['high'] - df['low']) / df['close']
return df
def create_analysis_snapshot(df: pd.DataFrame, lookback: int = 20) -> Dict:
"""
Create a snapshot of recent data for LLM analysis
"""
recent = df.tail(lookback).copy()
latest = df.iloc[-1]
snapshot = {
'current_price': latest['close'],
'price_change_pct': (latest['close'] - df.iloc[-lookback]['close']) / df.iloc[-lookback]['close'] * 100,
'current_rsi': latest['rsi'],
'current_volume_ratio': latest['volume_ratio'],
# Trend indicators
'price_vs_sma20': (latest['close'] - latest['sma_20']) / latest['sma_20'] * 100,
'price_vs_sma50': (latest['close'] - latest['sma_50']) / latest['sma_50'] * 100,
# MACD status
'macd_value': latest['macd'],
'macd_signal': latest['macd_signal'],
'macd_histogram': latest['macd_histogram'],
'macd_crossover': check_crossover(df['macd'], df['macd_signal']),
# Bollinger Band position
'bb_position': (latest['close'] - latest['bb_lower']) / (latest['bb_upper'] - latest['bb_lower']),
# Support/Resistance
'recent_high': recent['high'].max(),
'recent_low': recent['low'].min(),
'current_vs_high': (latest['close'] - recent['high'].max()) / recent['high'].max() * 100,
# Volatility
'current_atr': latest['atr'],
'atr_percentage': latest['atr'] / latest['close'] * 100,
}
return snapshot
def check_crossover(series1: pd.Series, series2: pd.Series, lookback: int = 3) -> str:
"""Check if crossover occurred recently"""
recent = pd.DataFrame({'s1': series1.tail(lookback), 's2': series2.tail(lookback)})
if len(recent) < 2:
return 'insufficient_data'
# Check for bullish crossover (series1 crosses above series2)
if recent['s1'].iloc[-1] > recent['s2'].iloc[-1] and recent['s1'].iloc[-2] <= recent['s2'].iloc[-2]:
return 'bullish_crossover'
# Check for bearish crossover
elif recent['s1'].iloc[-1] < recent['s2'].iloc[-1] and recent['s1'].iloc[-2] >= recent['s2'].iloc[-2]:
return 'bearish_crossover'
else:
return 'no_crossover'
def format_for_llm_analysis(df: pd.DataFrame, snapshot: Dict) -> str:
"""
Format technical data into a structured prompt for LLM analysis
"""
recent_data = df.tail(10)
prompt = f"""
TECHNICAL ANALYSIS REQUEST
Asset: [SYMBOL]
Timeframe: Daily
Analysis Date: {df['date'].iloc[-1]}
CURRENT MARKET SNAPSHOT:
- Price: ${snapshot['current_price']:.2f} ({snapshot['price_change_pct']:+.2f}% from 20 days ago)
- Volume Ratio: {snapshot['volume_ratio']:.2f}x average
TREND INDICATORS:
- Price vs SMA(20): {snapshot['price_vs_sma20']:+.2f}%
- Price vs SMA(50): {snapshot['price_vs_sma50']:+.2f}%
- MACD: {snapshot['macd_value']:.4f} (Signal: {snapshot['macd_signal']:.4f})
- MACD Status: {snapshot['macd_crossover']}
MOMENTUM INDICATORS:
- RSI(14): {snapshot['current_rsi']:.2f}
- Bollinger Band Position: {snapshot['bb_position']:.2f} (0=lower, 1=upper)
VOLATILITY:
- ATR: ${snapshot['current_atr']:.2f} ({snapshot['atr_percentage']:.2f}% of price)
- Intraday Range: {recent_data['intraday_range'].mean():.2f}%
RECENT PRICE ACTION (Last 10 days):
{format_price_table(recent_data)}
KEY LEVELS:
- Recent High: ${snapshot['recent_high']:.2f}
- Recent Low: ${snapshot['recent_low']:.2f}
- Current vs High: {snapshot['current_vs_high']:+.2f}%
Please provide:
1. Overall trend assessment (bullish/bearish/neutral)
2. Key support and resistance levels
3. Momentum analysis
4. Potential entry/exit signals
5. Risk factors to consider
"""
return prompt
def format_price_table(df: pd.DataFrame) -> str:
"""Format recent price data as a table"""
table_data = []
for _, row in df.iterrows():
table_data.append(
f"Date: {row['date']} | "
f"O: {row['open']:.2f} | "
f"H: {row['high']:.2f} | "
f"L: {row['low']:.2f} | "
f"C: {row['close']:.2f} | "
f"Vol: {row['volume']:,.0f}"
)
return '\n'.join(table_data)
def create_pattern_detection_prompt(df: pd.DataFrame) -> str:
"""
Create a prompt specifically for pattern recognition
"""
recent = df.tail(30)
# Calculate some pattern-relevant metrics
highs = recent['high'].values
lows = recent['low'].values
closes = recent['close'].values
# Find potential pivot points
pivot_highs = []
pivot_lows = []
for i in range(2, len(highs) - 2):
if highs[i] > highs[i-1] and highs[i] > highs[i-2] and highs[i] > highs[i+1] and highs[i] > highs[i+2]:
pivot_highs.append((i, highs[i]))
if lows[i] < lows[i-1] and lows[i] < lows[i-2] and lows[i] < lows[i+1] and lows[i] < lows[i+2]:
pivot_lows.append((i, lows[i]))
prompt = f"""
PATTERN RECOGNITION ANALYSIS
Analyze the following price data for chart patterns:
RECENT PRICE DATA (30 days):
{format_price_table(recent)}
IDENTIFIED PIVOT POINTS:
Pivot Highs: {[(recent.iloc[i]['date'], price) for i, price in pivot_highs]}
Pivot Lows: {[(recent.iloc[i]['date'], price) for i, price in pivot_lows]}
MOVING AVERAGE ALIGNMENT:
- SMA(10): {recent['sma_10'].iloc[-1]:.2f}
- SMA(20): {recent['sma_20'].iloc[-1]:.2f}
- SMA(50): {recent['sma_50'].iloc[-1]:.2f}
Please identify:
1. Any classic chart patterns (head & shoulders, triangles, flags, wedges)
2. Trend lines and channels
3. Support and resistance zones
4. Potential breakout levels
5. Pattern completion targets
"""
return prompt
def perform_technical_analysis(df: pd.DataFrame, analysis_type: str = 'full') -> str:
"""
Main function to prepare data and create appropriate prompts
"""
# Prepare data with indicators
df = prepare_ta_data(df)
# Create snapshot
snapshot = create_analysis_snapshot(df)
# Generate appropriate prompt based on analysis type
if analysis_type == 'full':
prompt = format_for_llm_analysis(df, snapshot)
elif analysis_type == 'pattern':
prompt = create_pattern_detection_prompt(df)
elif analysis_type == 'momentum':
prompt = create_momentum_analysis_prompt(df, snapshot)
elif analysis_type == 'volume':
prompt = create_volume_analysis_prompt(df)
return prompt
def create_momentum_analysis_prompt(df: pd.DataFrame, snapshot: Dict) -> str:
"""Focus on momentum indicators"""
return f"""
MOMENTUM ANALYSIS
Current Momentum Indicators:
- RSI(14): {snapshot['current_rsi']:.2f}
- MACD Histogram: {snapshot['macd_histogram']:.4f}
- Price Change (20d): {snapshot['price_change_pct']:+.2f}%
Recent RSI Values: {df['rsi'].tail(10).round(2).tolist()}
Recent MACD Histogram: {df['macd_histogram'].tail(10).round(4).tolist()}
Analyze:
1. Momentum strength and direction
2. Divergences between price and indicators
3. Overbought/oversold conditions
4. Momentum shift signals
"""
# Example usage
def analyze_stock(symbol: str, ohlcv_data: pd.DataFrame):
"""
Complete analysis workflow
"""
# Generate the prompt
analysis_prompt = perform_technical_analysis(ohlcv_data, 'full')
# This is where you'd send to LLM
# response = llm.generate(analysis_prompt)
# For multiple perspectives:
pattern_prompt = perform_technical_analysis(ohlcv_data, 'pattern')
momentum_prompt = perform_technical_analysis(ohlcv_data, 'momentum')
return {
'full_analysis': analysis_prompt,
'pattern_analysis': pattern_prompt,
'momentum_analysis': momentum_prompt
}
- Pre-calculate all indicators - Don't ask the LLM to calculate, just interpret
- Provide context - Include timeframe, asset type, and recent market conditions
- Structure data clearly - Use consistent formatting for prices and indicators
- Ask specific questions - Rather than "analyze this", ask for specific insights
- Include multiple timeframes - Show both recent (10-day) and medium-term (30-60 day) data
- Validate results - LLMs can hallucinate patterns; verify key findings programmatically
This structure gives you maximum flexibility to get different types of technical analysis from an LLM while maintaining data integrity and clarity.