Skip to content

Instantly share code, notes, and snippets.

@rjurney
Created June 1, 2025 04:51
Show Gist options
  • Save rjurney/e207a5c73ff6f4fb6c61c602152d4b7b to your computer and use it in GitHub Desktop.
Save rjurney/e207a5c73ff6f4fb6c61c602152d4b7b to your computer and use it in GitHub Desktop.
Claude 4 Opus created a technical analysis agent given a DataFrame of tick data for an equity

I'll show you how to structure data and prompts for effective technical analysis with LLMs. Here's a comprehensive approach:

1. Data Preparation Structure

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'

2. Formatting Data for LLM Prompts

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)

3. Advanced Pattern Detection Prompt

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

4. Comprehensive Analysis Function

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
    }

Key Tips for Best Results:

  1. Pre-calculate all indicators - Don't ask the LLM to calculate, just interpret
  2. Provide context - Include timeframe, asset type, and recent market conditions
  3. Structure data clearly - Use consistent formatting for prices and indicators
  4. Ask specific questions - Rather than "analyze this", ask for specific insights
  5. Include multiple timeframes - Show both recent (10-day) and medium-term (30-60 day) data
  6. 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.​​​​​​​​​​​​​​​​

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment