🇪🇸 Leer en Español 🇺🇸 English

F3: Technical Indicators

Fundamental Module 3 - Duration: 2-3 hours

Module Objectives

After completing this module you will be able to:

  • Understand what technical indicators are and what they are used for
  • Calculate and interpret the most important indicators
  • Combine multiple indicators for stronger signals
  • Avoid common mistakes when using indicators

What Are Technical Indicators?

Technical indicators are mathematical calculations based on price and volume that help to:

  • Identify trends (is it going up or down?)
  • Detect reversals (is it going to change direction?)
  • Measure momentum (how strong is the movement?)
  • Find levels (where to buy/sell?)

Types of Indicators

Type Function Examples
Trend Identify direction SMA, EMA, MACD
Momentum Measure strength/speed RSI, Stochastic
Volatility Measure variability Bollinger Bands, ATR
Volume Confirm movements OBV, Volume Profile

The 5 Essential Indicators

1. Simple Moving Average (SMA)

What is it? The average price over the last N days.

# Code for Google Colab
!pip install yfinance matplotlib pandas

import yfinance as yf
import matplotlib.pyplot as plt
import pandas as pd

# Download data
symbol = 'AAPL'
data = yf.download(symbol, period='6mo')

# Calculate SMAs for different periods
data['SMA_20'] = data['Close'].rolling(window=20).mean()
data['SMA_50'] = data['Close'].rolling(window=50).mean()
data['SMA_200'] = data['Close'].rolling(window=200).mean()

# Visualize
plt.figure(figsize=(14, 7))
plt.plot(data.index, data['Close'], label='Price', linewidth=2)
plt.plot(data.index, data['SMA_20'], label='SMA 20', alpha=0.8)
plt.plot(data.index, data['SMA_50'], label='SMA 50', alpha=0.8)
plt.plot(data.index, data['SMA_200'], label='SMA 200', alpha=0.8)
plt.title(f'{symbol} - Simple Moving Averages')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# Interpretation
current_price = data['Close'].iloc[-1]
sma20_current = data['SMA_20'].iloc[-1]

if current_price > sma20_current:
    print(f"Price ({current_price:.2f}) > SMA20 ({sma20_current:.2f}) = UPTREND")
else:
    print(f"Price ({current_price:.2f}) < SMA20 ({sma20_current:.2f}) = DOWNTREND")

Interpretation:

  • Price > SMA = Uptrend
  • Price < SMA = Downtrend
  • Short SMA > Long SMA = Golden Cross (very bullish)
  • Short SMA < Long SMA = Death Cross (very bearish)

2. RSI (Relative Strength Index)

What is it? Measures whether a stock is overbought or oversold (scale 0-100).

def calculate_rsi(data, period=14):
    """Calculates the RSI"""
    delta = 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

# Calculate RSI
data['RSI'] = calculate_rsi(data)

# Visualize
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)

# Price
ax1.plot(data.index, data['Close'], label='Price')
ax1.set_title(f'{symbol} - Price and RSI')
ax1.legend()
ax1.grid(True, alpha=0.3)

# RSI
ax2.plot(data.index, data['RSI'], color='purple', linewidth=2)
ax2.axhline(y=70, color='red', linestyle='--', alpha=0.7, label='Overbought (70)')
ax2.axhline(y=30, color='green', linestyle='--', alpha=0.7, label='Oversold (30)')
ax2.axhline(y=50, color='gray', linestyle='-', alpha=0.5)
ax2.fill_between(data.index, 30, 70, alpha=0.1, color='gray')
ax2.set_ylabel('RSI')
ax2.set_ylim(0, 100)
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Interpretation
current_rsi = data['RSI'].iloc[-1]
if current_rsi > 70:
    print(f"RSI = {current_rsi:.1f} - OVERBOUGHT (possible correction)")
elif current_rsi < 30:
    print(f"RSI = {current_rsi:.1f} - OVERSOLD (possible bounce)")
else:
    print(f"RSI = {current_rsi:.1f} - NEUTRAL ZONE")

Interpretation:

  • RSI > 70 = Overbought (careful, may drop)
  • RSI < 30 = Oversold (opportunity, may rise)
  • RSI = 50 = Equilibrium
  • Divergences = When price and RSI go in opposite directions

3. MACD (Moving Average Convergence Divergence)

What is it? Shows the relationship between two moving averages and momentum.

def calculate_macd(data, fast=12, slow=26, signal=9):
    """Calculates MACD, Signal and Histogram"""
    ema_fast = data['Close'].ewm(span=fast, adjust=False).mean()
    ema_slow = data['Close'].ewm(span=slow, adjust=False).mean()

    macd_line = ema_fast - ema_slow
    signal_line = macd_line.ewm(span=signal, adjust=False).mean()
    histogram = macd_line - signal_line

    return macd_line, signal_line, histogram

# Calculate MACD
data['MACD'], data['Signal'], data['Histogram'] = calculate_macd(data)

# Visualize
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)

# Price
ax1.plot(data.index, data['Close'], label='Price')
ax1.set_title(f'{symbol} - MACD Analysis')
ax1.legend()
ax1.grid(True, alpha=0.3)

# MACD
ax2.plot(data.index, data['MACD'], label='MACD', linewidth=2)
ax2.plot(data.index, data['Signal'], label='Signal', linewidth=2)
ax2.bar(data.index, data['Histogram'], label='Histogram', alpha=0.3)
ax2.axhline(y=0, color='black', linestyle='-', alpha=0.3)
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Interpretation
if data['MACD'].iloc[-1] > data['Signal'].iloc[-1]:
    print("MACD > Signal = BULLISH MOMENTUM")
    if data['Histogram'].iloc[-1] > data['Histogram'].iloc[-2]:
        print("   Histogram growing = Momentum accelerating")
else:
    print("MACD < Signal = BEARISH MOMENTUM")
    if data['Histogram'].iloc[-1] < data['Histogram'].iloc[-2]:
        print("   Histogram shrinking = Momentum weakening")

Interpretation:

  • MACD crosses Signal upward = Buy signal
  • MACD crosses Signal downward = Sell signal
  • Positive and growing histogram = Strong bullish momentum
  • Negative and shrinking histogram = Strong bearish momentum

4. Bollinger Bands

What is it? Shows a “normal” price range based on volatility.

def calculate_bollinger_bands(data, period=20, std_dev=2):
    """Calculates Bollinger Bands"""
    sma = data['Close'].rolling(window=period).mean()
    std = data['Close'].rolling(window=period).std()

    upper_band = sma + (std * std_dev)
    lower_band = sma - (std * std_dev)

    return upper_band, sma, lower_band

# Calculate Bands
data['BB_Upper'], data['BB_Middle'], data['BB_Lower'] = calculate_bollinger_bands(data)

# Calculate band width (volatility)
data['BB_Width'] = data['BB_Upper'] - data['BB_Lower']
data['BB_Position'] = (data['Close'] - data['BB_Lower']) / (data['BB_Upper'] - data['BB_Lower'])

# Visualize
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)

# Price and Bands
ax1.plot(data.index, data['Close'], label='Price', linewidth=2, color='black')
ax1.plot(data.index, data['BB_Upper'], label='Upper Band', alpha=0.7)
ax1.plot(data.index, data['BB_Middle'], label='SMA 20', alpha=0.7)
ax1.plot(data.index, data['BB_Lower'], label='Lower Band', alpha=0.7)
ax1.fill_between(data.index, data['BB_Upper'], data['BB_Lower'], alpha=0.1, color='gray')
ax1.set_title(f'{symbol} - Bollinger Bands')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Band Width (Volatility)
ax2.plot(data.index, data['BB_Width'], color='orange', linewidth=2)
ax2.set_ylabel('Band Width')
ax2.set_title('Volatility (Band Width)')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Interpretation
position = data['BB_Position'].iloc[-1]
if position > 1:
    print(f"Price ABOVE upper band ({position:.2f}) - Possible resistance")
elif position < 0:
    print(f"Price BELOW lower band ({position:.2f}) - Possible support")
elif position > 0.8:
    print(f"Price near upper band ({position:.2f}) - Strong trend")
elif position < 0.2:
    print(f"Price near lower band ({position:.2f}) - Bearish pressure")
else:
    print(f"Price in middle zone ({position:.2f}) - Normal range")

Interpretation:

  • Price touches upper band = Possible resistance
  • Price touches lower band = Possible support
  • Bands narrowing = Low volatility (calm before the storm)
  • Bands expanding = High volatility (strong move)

5. Volume (The Confirmer)

What is it? Shows how much interest there is in a price movement.

# Volume Analysis
data['Volume_SMA'] = data['Volume'].rolling(window=20).mean()
data['Volume_Ratio'] = data['Volume'] / data['Volume_SMA']
data['Price_Change'] = data['Close'].pct_change()

# Visualize
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(14, 12), sharex=True)

# Price
ax1.plot(data.index, data['Close'], label='Price')
ax1.set_title(f'{symbol} - Volume Analysis')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Volume
colors = ['green' if c > 0 else 'red' for c in data['Price_Change']]
ax2.bar(data.index, data['Volume'], color=colors, alpha=0.7, label='Volume')
ax2.plot(data.index, data['Volume_SMA'], color='blue', linewidth=2, label='20d Avg Volume')
ax2.set_ylabel('Volume')
ax2.legend()
ax2.grid(True, alpha=0.3)

# Volume Ratio
ax3.bar(data.index, data['Volume_Ratio'], color='purple', alpha=0.7)
ax3.axhline(y=1, color='black', linestyle='--', alpha=0.5)
ax3.axhline(y=2, color='red', linestyle='--', alpha=0.5, label='High Volume (2x)')
ax3.set_ylabel('Volume Ratio')
ax3.set_ylim(0, 4)
ax3.legend()
ax3.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Interpretation
vol_ratio = data['Volume_Ratio'].iloc[-1]
price_change = data['Price_Change'].iloc[-1] * 100

if vol_ratio > 2 and price_change > 0:
    print(f"HIGH volume ({vol_ratio:.1f}x) + Price RISING = STRONG BUY SIGNAL")
elif vol_ratio > 2 and price_change < 0:
    print(f"HIGH volume ({vol_ratio:.1f}x) + Price FALLING = STRONG SELL SIGNAL")
elif vol_ratio < 0.5:
    print(f"LOW volume ({vol_ratio:.1f}x) = Weak movement, unreliable")
else:
    print(f"Normal volume ({vol_ratio:.1f}x)")

Interpretation:

  • Price up + High volume = Strong, reliable movement
  • Price up + Low volume = Weak movement, be careful
  • Price down + High volume = Massive selling, stay away
  • Volume spike = Something important is happening

Combining Indicators (The Real Power)

Multi-Indicator Signal System

def generate_combined_signals(data):
    """Combines multiple indicators for more reliable signals"""

    signals = []

    # Calculate all indicators
    data['SMA_20'] = data['Close'].rolling(20).mean()
    data['SMA_50'] = data['Close'].rolling(50).mean()
    data['RSI'] = calculate_rsi(data)
    data['MACD'], data['Signal'], _ = calculate_macd(data)
    data['BB_Upper'], data['BB_Middle'], data['BB_Lower'] = calculate_bollinger_bands(data)
    data['Volume_Ratio'] = data['Volume'] / data['Volume'].rolling(20).mean()

    # Scoring system
    score = 0

    # 1. Trend (SMA)
    if data['Close'].iloc[-1] > data['SMA_20'].iloc[-1]:
        score += 1
        signals.append("Price > SMA20")
    if data['SMA_20'].iloc[-1] > data['SMA_50'].iloc[-1]:
        score += 1
        signals.append("SMA20 > SMA50")

    # 2. Momentum (RSI)
    if 30 < data['RSI'].iloc[-1] < 70:
        score += 1
        signals.append(f"RSI in neutral zone ({data['RSI'].iloc[-1]:.1f})")
    elif data['RSI'].iloc[-1] < 30:
        score += 2
        signals.append(f"RSI oversold ({data['RSI'].iloc[-1]:.1f})")

    # 3. MACD
    if data['MACD'].iloc[-1] > data['Signal'].iloc[-1]:
        score += 1
        signals.append("MACD > Signal")

    # 4. Bollinger Bands
    bb_pos = (data['Close'].iloc[-1] - data['BB_Lower'].iloc[-1]) / (data['BB_Upper'].iloc[-1] - data['BB_Lower'].iloc[-1])
    if 0.2 < bb_pos < 0.8:
        score += 1
        signals.append(f"Price in normal BB range ({bb_pos:.2f})")

    # 5. Volume
    if data['Volume_Ratio'].iloc[-1] > 1.5:
        score += 1
        signals.append(f"High volume ({data['Volume_Ratio'].iloc[-1]:.1f}x)")

    return score, signals

# Analyze
score, signals = generate_combined_signals(data)

print(f"\n{'='*50}")
print(f"MULTI-INDICATOR ANALYSIS: {symbol}")
print(f"{'='*50}")
print(f"Total Score: {score}/7")
print("\nDetected Signals:")
for signal in signals:
    print(f"  {signal}")

print(f"\nRECOMMENDATION:")
if score >= 6:
    print("  STRONG BUY - Multiple confirmations")
elif score >= 4:
    print("  MODERATE BUY - Positive signals")
elif score >= 2:
    print("  NEUTRAL - Mixed signals")
else:
    print("  AVOID - Few positive signals")

Common Mistakes to Avoid

Mistake #1: Using a single indicator

Problem: Indicators fail, especially on their own. Solution: Always combine 2-3 indicators of different types.

Mistake #2: Ignoring market context

Problem: RSI can be overbought for weeks in a strong trend. Solution: Consider the overall trend before acting on signals.

Mistake #3: Not adjusting parameters

Problem: RSI(14) works differently in crypto vs. blue chips. Solution: Test different periods and adjust per asset.

Mistake #4: Reacting to every signal

Problem: Too many signals = overtrading. Solution: Only act when multiple indicators agree.

Mistake #5: Forgetting volume

Problem: Movements without volume are false. Solution: ALWAYS confirm with volume.

Final Project: Your Indicator Dashboard

def create_complete_dashboard(symbol='AAPL'):
    """Creates a complete dashboard with all indicators"""

    # Download data
    data = yf.download(symbol, period='6mo')

    # Calculate all indicators
    data['SMA_20'] = data['Close'].rolling(20).mean()
    data['RSI'] = calculate_rsi(data)
    data['MACD'], data['Signal'], data['Histogram'] = calculate_macd(data)
    data['BB_Upper'], data['BB_Middle'], data['BB_Lower'] = calculate_bollinger_bands(data)

    # Create visualization
    fig, axes = plt.subplots(5, 1, figsize=(15, 20), sharex=True)

    # 1. Price and SMA
    axes[0].plot(data.index, data['Close'], label='Price', linewidth=2)
    axes[0].plot(data.index, data['SMA_20'], label='SMA 20', alpha=0.7)
    axes[0].set_title(f'{symbol} - Complete Indicator Dashboard')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)

    # 2. RSI
    axes[1].plot(data.index, data['RSI'], color='purple', linewidth=2)
    axes[1].axhline(y=70, color='red', linestyle='--', alpha=0.7)
    axes[1].axhline(y=30, color='green', linestyle='--', alpha=0.7)
    axes[1].set_ylabel('RSI')
    axes[1].set_ylim(0, 100)
    axes[1].grid(True, alpha=0.3)

    # 3. MACD
    axes[2].plot(data.index, data['MACD'], label='MACD')
    axes[2].plot(data.index, data['Signal'], label='Signal')
    axes[2].bar(data.index, data['Histogram'], alpha=0.3)
    axes[2].axhline(y=0, color='black', linestyle='-', alpha=0.3)
    axes[2].set_ylabel('MACD')
    axes[2].legend()
    axes[2].grid(True, alpha=0.3)

    # 4. Bollinger Bands
    axes[3].plot(data.index, data['Close'], label='Price', color='black')
    axes[3].plot(data.index, data['BB_Upper'], 'r--', alpha=0.7)
    axes[3].plot(data.index, data['BB_Lower'], 'g--', alpha=0.7)
    axes[3].fill_between(data.index, data['BB_Upper'], data['BB_Lower'], alpha=0.1)
    axes[3].set_ylabel('Bollinger')
    axes[3].grid(True, alpha=0.3)

    # 5. Volume
    colors = ['green' if c > o else 'red' for c, o in zip(data['Close'], data['Open'])]
    axes[4].bar(data.index, data['Volume'], color=colors, alpha=0.7)
    axes[4].set_ylabel('Volume')
    axes[4].grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    # Final analysis
    score, signals = generate_combined_signals(data)
    return score, signals

# Run dashboard
score, signals = create_complete_dashboard('AAPL')

Module Checkpoint

Knowledge

  • I understand the 5 main types of indicators
  • I can calculate SMA, RSI, MACD, Bollinger Bands
  • I can interpret signals from each indicator
  • I understand why combining indicators is crucial

Skills

  • I can code any indicator from scratch
  • I can create clear indicator visualizations
  • I can combine multiple indicators for signals
  • I avoid common beginner mistakes

Project

  • My multi-indicator dashboard works
  • It generates combined signals correctly
  • I can apply it to any stock

Next Module

F4: Your First Complete Strategy

  • Systematic strategy design
  • Entry and exit rules
  • Risk management
  • Basic backtesting

You now master the indicators! Time to create your first real strategy


Ready for your first strategy? -> F4: First Strategy