Skip to content

Momentum Trading Examples

Examples of using momentum indicators like RSI, MACD, and Stochastic for trading strategies.

RSI Overbought/Oversold Strategy

Classic RSI trading strategy using 70/30 levels.

require 'sqa/tai'

def rsi_strategy(prices)
  rsi = SQA::TAI.rsi(prices, period: 14)
  current_rsi = rsi.last
  current_price = prices.last

  signal = if current_rsi < 30
    { action: :buy, reason: "Oversold (RSI: #{current_rsi.round(2)})" }
  elsif current_rsi > 70
    { action: :sell, reason: "Overbought (RSI: #{current_rsi.round(2)})" }
  else
    { action: :hold, reason: "Neutral (RSI: #{current_rsi.round(2)})" }
  end

  signal.merge(price: current_price.round(2), rsi: current_rsi.round(2))
end

# Generate sample data with oversold/overbought conditions
prices = [50.0, 48.5, 47.0, 45.5, 44.0, 42.5, 41.0, 40.0, 39.0, 38.5,  # Oversold
          40.0, 42.0, 44.0, 46.0, 48.0, 50.0, 52.0, 54.0, 56.0, 58.0]  # Recovery

signal = rsi_strategy(prices)

puts "RSI Strategy Signal:"
puts "Action: #{signal[:action]}"
puts "Reason: #{signal[:reason]}"
puts "Price: $#{signal[:price]}"

MACD Crossover System

Trade based on MACD line crossing the signal line.

require 'sqa/tai'

class MACDStrategy
  def initialize(fast: 12, slow: 26, signal: 9)
    @fast = fast
    @slow = slow
    @signal = signal
  end

  def analyze(prices)
    macd, signal, histogram = SQA::TAI.macd(
      prices,
      fast_period: @fast,
      slow_period: @slow,
      signal_period: @signal
    )

    # Check for crossovers
    signal_type = detect_crossover(macd, signal)

    # Histogram momentum
    histogram_momentum = if histogram[-1] > histogram[-2] && histogram[-1] > 0
      :increasing_bullish
    elsif histogram[-1] < histogram[-2] && histogram[-1] < 0
      :increasing_bearish
    elsif histogram[-1] > 0
      :decreasing_bullish
    else
      :decreasing_bearish
    end

    {
      macd: macd.last.round(4),
      signal: signal.last.round(4),
      histogram: histogram.last.round(4),
      crossover: signal_type,
      momentum: histogram_momentum,
      trend: macd.last > 0 ? :bullish : :bearish
    }
  end

  def generate_signal(prices)
    analysis = analyze(prices)

    if analysis[:crossover] == :bullish
      {
        action: :buy,
        strength: analysis[:histogram] > 0 ? :strong : :moderate,
        reason: "MACD bullish crossover"
      }
    elsif analysis[:crossover] == :bearish
      {
        action: :sell,
        strength: analysis[:histogram] < 0 ? :strong : :moderate,
        reason: "MACD bearish crossover"
      }
    elsif analysis[:momentum] == :increasing_bullish && analysis[:trend] == :bullish
      {
        action: :hold_long,
        strength: :moderate,
        reason: "Bullish momentum strengthening"
      }
    elsif analysis[:momentum] == :increasing_bearish && analysis[:trend] == :bearish
      {
        action: :hold_short,
        strength: :moderate,
        reason: "Bearish momentum strengthening"
      }
    else
      {
        action: :hold,
        strength: :weak,
        reason: "No clear signal"
      }
    end
  end

  private

  def detect_crossover(macd, signal)
    return nil if macd[-2].nil? || signal[-2].nil?

    if macd[-2] < signal[-2] && macd[-1] > signal[-1]
      :bullish
    elsif macd[-2] > signal[-2] && macd[-1] < signal[-1]
      :bearish
    else
      nil
    end
  end
end

# Example usage
prices = Array.new(50) { |i| 100 + Math.sin(i * 0.2) * 15 + i * 0.3 }

strategy = MACDStrategy.new
signal = strategy.generate_signal(prices)

puts "MACD Strategy Signal:"
puts "Action: #{signal[:action]}"
puts "Strength: #{signal[:strength]}"
puts "Reason: #{signal[:reason]}"

RSI Divergence Detection

Identify bullish and bearish divergences between price and RSI.

require 'sqa/tai'

def find_divergence(prices, lookback: 20)
  rsi = SQA::TAI.rsi(prices, period: 14)

  # Find recent peaks and troughs
  price_peak_idx = find_peak_index(prices, lookback)
  rsi_peak_idx = find_peak_index(rsi.compact, lookback)

  price_trough_idx = find_trough_index(prices, lookback)
  rsi_trough_idx = find_trough_index(rsi.compact, lookback)

  divergences = []

  # Bearish divergence: price higher high, RSI lower high
  if price_peak_idx && rsi_peak_idx
    price_current_peak = prices[-1..-lookback/2].max
    price_prev_peak = prices[-lookback..-(lookback/2+1)].max

    rsi_current_peak = rsi.compact[-1..-lookback/2].max
    rsi_prev_peak = rsi.compact[-lookback..-(lookback/2+1)].max

    if price_current_peak > price_prev_peak && rsi_current_peak < rsi_prev_peak
      divergences << {
        type: :bearish,
        reason: "Price higher high but RSI lower high"
      }
    end
  end

  # Bullish divergence: price lower low, RSI higher low
  if price_trough_idx && rsi_trough_idx
    price_current_trough = prices[-1..-lookback/2].min
    price_prev_trough = prices[-lookback..-(lookback/2+1)].min

    rsi_current_trough = rsi.compact[-1..-lookback/2].min
    rsi_prev_trough = rsi.compact[-lookback..-(lookback/2+1)].min

    if price_current_trough < price_prev_trough && rsi_current_trough > rsi_prev_trough
      divergences << {
        type: :bullish,
        reason: "Price lower low but RSI higher low"
      }
    end
  end

  divergences
end

def find_peak_index(data, lookback)
  recent_data = data.compact.last(lookback)
  return nil if recent_data.length < lookback
  recent_data.index(recent_data.max)
end

def find_trough_index(data, lookback)
  recent_data = data.compact.last(lookback)
  return nil if recent_data.length < lookback
  recent_data.index(recent_data.min)
end

# Example: Create data with bearish divergence
prices = Array.new(40) { |i| 100 + i * 2 }  # Uptrend
# Add a peak, then higher peak with weakening momentum
prices += [175, 178, 180, 178, 176, 174, 172, 170, 168, 166]

divergences = find_divergence(prices, lookback: 20)

if divergences.any?
  puts "Divergences Detected:"
  divergences.each do |div|
    puts "#{div[:type].to_s.capitalize} Divergence: #{div[:reason]}"
  end
else
  puts "No divergences detected"
end

Multi-Indicator Momentum System

Combine RSI, MACD, and Momentum for stronger signals.

require 'sqa/tai'

class MultiMomentumSystem
  def analyze(prices)
    # Calculate indicators
    rsi = SQA::TAI.rsi(prices, period: 14)
    macd, signal, histogram = SQA::TAI.macd(prices)
    mom = SQA::TAI.mom(prices, period: 10)

    # Individual signals
    rsi_signal = get_rsi_signal(rsi.last)
    macd_signal = get_macd_signal(macd.last, signal.last)
    mom_signal = get_mom_signal(mom.last)

    # Combine signals
    bullish_count = [rsi_signal, macd_signal, mom_signal].count(:bullish)
    bearish_count = [rsi_signal, macd_signal, mom_signal].count(:bearish)

    overall_signal = if bullish_count >= 2
      :buy
    elsif bearish_count >= 2
      :sell
    else
      :hold
    end

    {
      signal: overall_signal,
      confidence: [bullish_count, bearish_count].max,
      indicators: {
        rsi: { value: rsi.last.round(2), signal: rsi_signal },
        macd: { value: macd.last.round(4), signal: macd_signal },
        momentum: { value: mom.last.round(2), signal: mom_signal }
      }
    }
  end

  private

  def get_rsi_signal(rsi_value)
    if rsi_value < 30
      :bullish
    elsif rsi_value > 70
      :bearish
    else
      :neutral
    end
  end

  def get_macd_signal(macd_value, signal_value)
    if macd_value > signal_value
      :bullish
    elsif macd_value < signal_value
      :bearish
    else
      :neutral
    end
  end

  def get_mom_signal(mom_value)
    if mom_value > 0
      :bullish
    elsif mom_value < 0
      :bearish
    else
      :neutral
    end
  end
end

# Example usage
prices = Array.new(30) { |i| 100 + i * 0.5 + rand(-2.0..2.0) }

system = MultiMomentumSystem.new
analysis = system.analyze(prices)

puts "Multi-Indicator Momentum Analysis:"
puts "Overall Signal: #{analysis[:signal]}"
puts "Confidence: #{analysis[:confidence]}/3 indicators agree"
puts "\nIndividual Indicators:"
puts "RSI (#{analysis[:indicators][:rsi][:value]}): #{analysis[:indicators][:rsi][:signal]}"
puts "MACD (#{analysis[:indicators][:macd][:value]}): #{analysis[:indicators][:macd][:signal]}"
puts "Momentum (#{analysis[:indicators][:momentum][:value]}): #{analysis[:indicators][:momentum][:signal]}"

Stochastic Oscillator Strategy

Use Stochastic for entry timing in trending markets.

require 'sqa/tai'

def stochastic_strategy(high, low, close)
  # Calculate Stochastic
  slowk, slowd = SQA::TAI.stoch(high, low, close)

  # Calculate trend
  sma_50 = SQA::TAI.sma(close, period: 50)
  trend = close.last > sma_50.last ? :up : :down

  current_k = slowk.last
  current_d = slowd.last
  prev_k = slowk[-2]
  prev_d = slowd[-2]

  # Detect crossovers
  bullish_cross = prev_k < prev_d && current_k > current_d
  bearish_cross = prev_k > prev_d && current_k < current_d

  # Generate signals based on trend
  if trend == :up && bullish_cross && current_k < 20
    {
      action: :buy,
      strength: :strong,
      reason: "Stochastic bullish crossover in oversold zone (uptrend)",
      k: current_k.round(2),
      d: current_d.round(2)
    }
  elsif trend == :down && bearish_cross && current_k > 80
    {
      action: :sell,
      strength: :strong,
      reason: "Stochastic bearish crossover in overbought zone (downtrend)",
      k: current_k.round(2),
      d: current_d.round(2)
    }
  elsif bullish_cross
    {
      action: :buy,
      strength: :moderate,
      reason: "Stochastic bullish crossover",
      k: current_k.round(2),
      d: current_d.round(2)
    }
  elsif bearish_cross
    {
      action: :sell,
      strength: :moderate,
      reason: "Stochastic bearish crossover",
      k: current_k.round(2),
      d: current_d.round(2)
    }
  else
    {
      action: :hold,
      strength: :none,
      reason: "No clear signal",
      k: current_k.round(2),
      d: current_d.round(2)
    }
  end
end

# Generate sample OHLC data
length = 60
high = Array.new(length) { |i| 100 + i * 0.3 + rand(0..2.0) }
low = high.map { |h| h - rand(1.0..3.0) }
close = low.zip(high).map { |l, h| l + (h - l) * rand(0.3..0.7) }

signal = stochastic_strategy(high, low, close)

puts "Stochastic Strategy Signal:"
puts "Action: #{signal[:action]}"
puts "Strength: #{signal[:strength]}"
puts "Reason: #{signal[:reason]}"
puts "Stochastic K: #{signal[:k]}"
puts "Stochastic D: #{signal[:d]}"

Momentum Breakout System

Combine momentum with price breakouts for high-probability trades.

require 'sqa/tai'

def momentum_breakout(prices, volume, lookback: 20)
  # Find resistance
  resistance = prices[-lookback..-1].max

  # Calculate momentum
  rsi = SQA::TAI.rsi(prices, period: 14)
  macd, signal, histogram = SQA::TAI.macd(prices)

  # Volume analysis
  avg_volume = volume[-lookback..-1].sum / lookback.to_f
  volume_surge = volume.last > avg_volume * 1.5

  current_price = prices.last

  # Breakout conditions
  price_breakout = current_price > resistance
  momentum_positive = rsi.last > 50 && macd.last > signal.last

  if price_breakout && momentum_positive && volume_surge
    {
      signal: :strong_buy,
      reason: "Price breakout with momentum and volume confirmation",
      price: current_price.round(2),
      resistance: resistance.round(2),
      rsi: rsi.last.round(2),
      volume_ratio: (volume.last / avg_volume).round(2)
    }
  elsif price_breakout && momentum_positive
    {
      signal: :buy,
      reason: "Price breakout with momentum (low volume)",
      price: current_price.round(2),
      resistance: resistance.round(2),
      rsi: rsi.last.round(2),
      volume_ratio: (volume.last / avg_volume).round(2)
    }
  else
    {
      signal: :hold,
      reason: "No breakout or momentum confirmation",
      price: current_price.round(2),
      resistance: resistance.round(2),
      rsi: rsi.last.round(2),
      volume_ratio: (volume.last / avg_volume).round(2)
    }
  end
end

# Example usage
prices = Array.new(50) { |i| 100 + i * 0.2 + rand(-1.0..1.0) }
# Add breakout
prices += [115, 118, 120, 122, 125]

volume = Array.new(50) { 1000 + rand(-200..200) }
# Volume surge on breakout
volume += [1800, 2200, 2500, 2100, 1900]

result = momentum_breakout(prices, volume, lookback: 20)

puts "Momentum Breakout Analysis:"
puts "Signal: #{result[:signal]}"
puts "Reason: #{result[:reason]}"
puts "Price: $#{result[:price]} (Resistance: $#{result[:resistance]})"
puts "RSI: #{result[:rsi]}"
puts "Volume Ratio: #{result[:volume_ratio]}x"

See Also