Quantitative Financial Insights

Quantitative Financial Insights

Share this post

Quantitative Financial Insights
Quantitative Financial Insights
Building a Multi-Indicator Momentum Trading Strategy: From Theory to Python Implementation

Building a Multi-Indicator Momentum Trading Strategy: From Theory to Python Implementation

A deep dive into creating a sophisticated algorithmic trading strategy that combines multiple technical indicators for enhanced signal accuracy

QPY's avatar
QPY
Jun 09, 2025
∙ Paid
1

Share this post

Quantitative Financial Insights
Quantitative Financial Insights
Building a Multi-Indicator Momentum Trading Strategy: From Theory to Python Implementation
1
Share

In the fast-paced world of algorithmic trading, having a robust strategy that can identify high-probability entry and exit points is crucial. Today, we're going to dissect and rebuild an advanced momentum-based trading strategy that combines multiple technical indicators to create a comprehensive trading system.

This strategy is particularly interesting because it doesn't rely on a single indicator but instead uses a confluence of signals to increase the probability of successful trades. We'll explore the theory behind each component and then implement it from scratch in Python.

The Strategy Overview

Our momentum strategy is built around a simple yet powerful concept: buy when multiple oversold conditions align with trend confirmation, and sell when momentum reverses.

The strategy operates on these core principles:

  1. Multiple Confirmation Signals: Never rely on a single indicator

  2. Risk Management: Defined stop losses and profit targets

  3. Trend Awareness: Consider both short-term momentum and longer-term trends

  4. Adaptive Exit Strategy: Multiple exit conditions for different market scenarios

Technical Indicators Deep Dive

1. RSI (Relative Strength Index)

The RSI is our primary momentum oscillator, measuring the speed and magnitude of price changes. We use it to identify oversold conditions (RSI < 28).

Why RSI?

  • Bounded between 0 and 100, making it easy to interpret

  • Excellent for identifying oversold/overbought conditions

  • Works well in ranging markets

2. Fisher RSI Transform

This is where things get interesting. The Fisher Transform converts the RSI into a Gaussian normal distribution, making extreme values more pronounced.

def fisher_rsi_transform(rsi):
    normalized_rsi = 0.1 * (rsi - 50)
    exp_2rsi = np.exp(2 * normalized_rsi)
    return (exp_2rsi - 1) / (exp_2rsi + 1)

The Magic of Fisher Transform:

  • Transforms RSI from 0-100 scale to -1 to +1 scale

  • Makes extreme oversold/overbought conditions more obvious

  • Reduces false signals in the middle range

  • Values below -0.94 indicate extreme oversold conditions

3. MFI (Money Flow Index)

While RSI only considers price, MFI incorporates volume, making it a more complete momentum indicator.

Why MFI Matters:

  • Combines price and volume for better signal quality

  • Helps confirm RSI signals

  • Identifies when selling pressure is exhausted (MFI < 16)

4. Stochastic Oscillator

The stochastic oscillator compares the current closing price to its price range over a specific period.

Key Insight: We look for %D > %K, which indicates that momentum is building upward even in oversold conditions.

5. Moving Averages (EMA & SMA)

We use multiple moving averages for different purposes:

  • EMA 5 & 10: Short-term momentum detection

  • EMA 50 & 100: Trend confirmation

  • SMA 40: Price level reference

Crossover Strategy: When EMA 5 crosses above EMA 10, it often signals the beginning of an upward momentum shift.

6. Parabolic SAR

SAR (Stop And Reverse) is excellent for trailing stops and trend reversal detection.

Exit Strategy: When SAR moves above the price, it typically indicates a trend reversal, making it perfect for our exit strategy.

Entry Signal Logic

Our entry signal requires ALL of the following conditions to be true:

entry_conditions = (
    (rsi < 28) &                    # RSI oversold
    (rsi > 0) &                     # Valid RSI
    (fisher_rsi < -0.94) &          # Extreme oversold
    (mfi < 16.0) &                  # Volume-weighted oversold
    (close < sma40) &               # Price below MA (downtrend)
    ((ema50 > ema100) |             # Overall uptrend OR
     (ema5_crossed_above_ema10)) &  # Momentum shift
    (stoch_d > stoch_k) &           # Stochastic momentum
    (stoch_d > 0)                   # Valid stochastic
)

Why This Combination Works:

  1. Multiple Oversold Confirmations: RSI, Fisher RSI, and MFI all confirm oversold conditions

  2. Trend Context: We only buy during overall uptrends or momentum shifts

  3. Volume Confirmation: MFI ensures selling pressure is exhausted

  4. Momentum Building: Stochastic conditions show momentum is starting to build

Exit Strategy

Our exit strategy is multi-layered:

1. Technical Exit

exit_signal = (sar > close) & (fisher_rsi > 0.3)

2. Profit Targets (ROI-based)

Dynamic profit targets based on holding time:

  • Immediate: 5% profit target

  • After 20 minutes: 4% target

  • After 30 minutes: 3% target

  • After 60 minutes: 1% target

3. Stop Loss

Fixed 10% stop loss to limit downside risk.

Code Architecture Deep Dive

Design Patterns Used

1. Configuration Pattern

@dataclass
class TradingConfig:
    stop_loss_pct: float = 0.10
    rsi_oversold: int = 28
    # ... other parameters

This makes the strategy highly configurable and testable.

2. Strategy Pattern The MomentumStrategy class encapsulates all trading logic, making it easy to swap different strategies.

3. Single Responsibility Principle Each method has a single, clear purpose:

  • calculate_indicators(): Only calculates technical indicators

  • generate_entry_signals(): Only generates buy signals

  • generate_exit_signals(): Only generates sell signals

Key Implementation Details

Error Handling:

required_cols = ['open', 'high', 'low', 'close', 'volume']
for col in required_cols:
    if col not in data.columns:
        raise ValueError(f"Missing required column: {col}")

Type Safety: Using type hints throughout for better code maintainability:

def calculate_roi_exit(self, entry_price: float, entry_time: pd.Timestamp, 
                      current_time: pd.Timestamp, current_price: float) -> bool:

Vectorized Operations: All indicator calculations use vectorized operations for performance:

data['fisher_rsi'] = self.indicators.fisher_rsi_transform(data['rsi'])

Backtesting Framework

The included backtesting framework provides:

  • Trade Tracking: Records every trade with entry/exit prices and reasons

  • Performance Metrics: Win rate, average profit, max drawdown

  • Risk Analysis: Tracks stop losses vs. profit targets

results = {
    'total_trades': len(trades),
    'win_rate': win_rate,
    'avg_profit_pct': avg_profit,
    'total_return_pct': sum(profits),
    'trades': trades
}

Strategy Strengths and Limitations

Strengths

  1. Multiple Confirmation: Reduces false signals

  2. Risk Management: Built-in stop losses and profit targets

  3. Adaptable: Easy to modify parameters

  4. Comprehensive: Considers price, volume, and momentum

Limitations

  1. Lagging Nature: All indicators are based on historical data

  2. Market Regime Sensitivity: May not work well in all market conditions

  3. Over-optimization Risk: Many parameters could lead to curve fitting

Practical Implementation Tips

1. Parameter Optimization

Don't optimize all parameters at once. Focus on the most impactful ones:

  • RSI oversold threshold

  • Fisher RSI threshold

  • Stop loss percentage

2. Market Regime Detection

Consider adding market regime filters:

def is_trending_market(df):
    # Add logic to detect trending vs. ranging markets
    pass

3. Position Sizing

Implement proper position sizing based on volatility:

def calculate_position_size(self, account_balance, volatility):
    # Kelly Criterion or fixed fractional sizing
    pass

Advanced Extensions

1. Machine Learning Integration

Use the technical indicators as features for ML models:

features = ['rsi', 'fisher_rsi', 'mfi', 'stoch_k', 'stoch_d']
X = data[features]
y = future_returns  # Target variable

2. Multi-Timeframe Analysis

Incorporate signals from multiple timeframes:

def get_higher_timeframe_trend(self, symbol, timeframe):
    # Get trend from higher timeframe
    pass

3. Volatility Adjustment

Adjust parameters based on market volatility:

def adjust_parameters_for_volatility(self, volatility):
    if volatility > self.high_vol_threshold:
        self.config.stop_loss_pct *= 1.5  # Wider stops in volatile markets

Performance Optimization

1. Vectorization

Always use pandas vectorized operations:

# Good
conditions = (df['rsi'] < 28) & (df['mfi'] < 16)

# Bad
conditions = df.apply(lambda row: row['rsi'] < 28 and row['mfi'] < 16, axis=1)

2. Memory Management

For large datasets, consider chunking:

def process_in_chunks(self, df, chunk_size=10000):
    for chunk in pd.read_csv(file, chunksize=chunk_size):
        yield self.calculate_indicators(chunk)

3. Caching

Cache expensive calculations:

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_calculation(self, param1, param2):
    # Expensive operation
    pass

Testing and Validation

1. Unit Testing

Test each component individually:

def test_fisher_transform():
    rsi = pd.Series([20, 50, 80])
    fisher = TechnicalIndicators.fisher_rsi_transform(rsi)
    assert fisher.iloc[0] < -0.5  # Oversold
    assert abs(fisher.iloc[1]) < 0.1  # Neutral
    assert fisher.iloc[2] > 0.5  # Overbought

2. Walk-Forward Analysis

Test the strategy on rolling windows:

def walk_forward_analysis(self, df, train_periods=252, test_periods=63):
    results = []
    for i in range(train_periods, len(df), test_periods):
        train_data = df.iloc[i-train_periods:i]
        test_data = df.iloc[i:i+test_periods]
        # Optimize on train_data, test on test_data
        results.append(self.backtest(test_data))
    return results

Conclusion

This momentum strategy demonstrates how combining multiple technical indicators can create a robust trading system. The key insights are:

  1. No single indicator is perfect - use multiple confirmations

  2. Risk management is crucial - always have stop losses and profit targets

  3. Code quality matters - clean, maintainable code is essential for live trading

  4. Testing is everything - backtest thoroughly on out-of-sample data

The strategy provides a solid foundation that can be extended and customized for different markets and timeframes. Remember, successful algorithmic trading is as much about risk management and position sizing as it is about signal generation.

Next Steps

  1. Paper Trading: Test the strategy with real market data without risking capital

  2. Parameter Optimization: Use walk-forward analysis to find optimal parameters

  3. Market Regime Analysis: Study how the strategy performs in different market conditions

  4. Portfolio Integration: Consider how this strategy fits into a broader portfolio of strategies

Remember: past performance doesn't guarantee future results. Always test thoroughly and start with small position sizes when going live.


Happy trading, and may your backtests be profitable and your forward tests even more so!

Source Code

Keep reading with a 7-day free trial

Subscribe to Quantitative Financial Insights to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2025 QPY
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share