How to Use Multiple Moving Averages to Filter Market Noise in Python

python dev.to

Moving averages are everywhere in trading. But using just one window length forces you into a tough choice: smooth signals that lag behind, or responsive signals full of noise.

This article shows how combining multiple SMA windows into a single framework can give you the best of both worlds. You'll learn why this works, how to build it, and what to realistically expect.


Most algo trading content gives you theory.
This gives you the code.

3 Python strategies. Fully backtested. Colab notebook included.
Plus a free ebook with 5 more strategies the moment you subscribe.

5,000 quant traders already run these:

Subscribe | AlgoEdge Insights

What You'll Learn

  • What the strategy is
  • Why it works
  • How to implement it in Python
  • How to test it
  • What results to expect

Understanding the Idea

Think of price data like a radio signal with static. The actual music (the trend) is buried under crackle and hiss (random price swings). A moving average acts like a filter that removes the static so you can hear the melody.

When you average prices over a window, you're essentially telling the computer: "Ignore the tiny blips. Show me what's really happening." Longer windows give you a cleaner picture but react slowly. Shorter windows react fast but pick up more noise.

The core idea is simple. Instead of picking one window and hoping it works, you use several windows together. Each window captures a different "frequency" of price movement. Fast windows catch quick shifts. Slow windows confirm the bigger picture.

When multiple windows agree on direction, you have a stronger signal. When they disagree, the market might be choppy or transitioning.

One important detail: your moving average must only use past and present data. Using future data in calculations creates "lookahead bias" — your backtest looks amazing, but real trading fails because you can't actually see tomorrow's prices today.

Why This Strategy Can Work

Markets move at different speeds. A 5-day trend and a 50-day trend are both real, but they tell different stories. Using multiple windows lets you listen to both conversations at once.

Short-term averages show momentum and quick reversals. Long-term averages show the dominant direction. When short crosses above long, momentum aligns with trend. When they diverge, caution is warranted.

This layered approach reduces false signals. A single moving average might whipsaw you in and out of positions. Multiple confirmations filter out the noise that causes those costly mistakes.

Implementation in Python

Calculating moving averages in Python is straightforward with pandas. Here's how to create multiple SMA windows at once:

This function takes a price series and a list of window lengths. It returns a DataFrame with each SMA as a column. The .rolling() method handles the averaging, and it's causal by default — no future data leaks in.

The early rows will show NaN values because you need enough data points to fill the window before the average becomes valid.

import pandas as pd

def calculate_multi_sma(prices, windows=[10, 20, 50]):
    smas = pd.DataFrame(index=prices.index)
    for w in windows:
        smas[f'sma_{w}'] = prices.rolling(window=w).mean()
    return smas

Enter fullscreen mode Exit fullscreen mode
# Example usage
prices = pd.Series([100, 102, 101, 105, 108, 107, 110, 112])
smas = calculate_multi_sma(prices, windows=[3, 5])
print(smas)

Enter fullscreen mode Exit fullscreen mode

Enjoying this strategy so far? This is only a taste of what's possible.

Go deeper with my newsletter: longer, more detailed articles + full Google Colab implementations for every approach.

Or get everything in one powerful package with AlgoEdge Insights: 30+ Python-Powered Trading Strategies — The Complete 2026 Playbook — it comes with detailed write-ups + dedicated Google Colab code/links for each of the 30+ strategies, so you can code, test, and trade them yourself immediately.

Exclusive for readers: 20% off the book with code MEDIUM20.

Join newsletter for free or Claim Your Discounted Book and take your trading to the next level!

Building the Strategy

Now let's create trading signals based on window alignment. The logic: go long when shorter averages are above longer ones (uptrend), go short or stay flat when the opposite occurs.

This creates a binary signal. When the fast SMA sits above the slow SMA, we're bullish. The signal stays consistent until the relationship flips. No predictions, just following what the averages tell us.

def generate_signals(smas):
    signals = pd.Series(index=smas.index, dtype=float)

    # Extract windows from column names
    windows = sorted([int(col.split('_')[1]) for col in smas.columns])
    shortest = f'sma_{windows[0]}'
    longest = f'sma_{windows[-1]}'

    # Signal: 1 when short > long (bullish), -1 otherwise
    signals = (smas[shortest] > smas[longest]).astype(int)
    signals = signals.replace(0, -1)  # Convert 0 to -1 for short

    return signals

Enter fullscreen mode Exit fullscreen mode

Backtesting the Idea

Testing the strategy requires comparing signal-based returns against buy-and-hold. Here's a simple evaluation:

The .shift(1) is critical. It ensures you trade on yesterday's signal using today's returns, simulating real execution where you can't act on information you don't have yet.

def backtest_strategy(prices, signals):
    returns = prices.pct_change()
    strategy_returns = signals.shift(1) * returns  # Shift to avoid lookahead

    cumulative = (1 + strategy_returns).cumprod()
    total_return = cumulative.iloc[-1] - 1

    # Sharpe ratio (annualized, assuming daily data)
    sharpe = strategy_returns.mean() / strategy_returns.std() * (252 ** 0.5)

    # Max drawdown
    rolling_max = cumulative.cummax()
    drawdown = (cumulative - rolling_max) / rolling_max
    max_drawdown = drawdown.min()

    return {
        'total_return': total_return,
        'sharpe_ratio': sharpe,
        'max_drawdown': max_drawdown
    }

Enter fullscreen mode Exit fullscreen mode

Results and Insights

Running this on historical data typically shows moderate improvements over single-window approaches. You'll see fewer whipsaw trades and smoother equity curves.

Don't expect miracles. Multi-window SMA strategies tend to capture 60-70% of major trends while avoiding some false breakouts. Sharpe ratios around 0.5-1.0 are realistic for equity indices.

The strategy shines in trending markets. During choppy sideways periods, performance degrades as the windows constantly cross back and forth. That's the nature of trend-following approaches.

Limitations

  • Lag is inherent. Moving averages always react after the move starts. You'll never catch the exact bottom or top.

  • Sideways markets hurt. When price oscillates without direction, you'll get chopped up with small losses.

  • Window selection matters. The "right" windows change across assets and time periods. What worked last year might not work next year.

  • Transaction costs add up. Frequent crosses mean more trades, and commissions eat into returns.

  • Past performance doesn't guarantee future results. Markets evolve, and strategies that worked historically can stop working.

Conclusion

Combining multiple moving average windows gives you a structured way to read market trends. Instead of guessing the perfect window length, you let different timeframes vote on market direction.

This approach won't make you rich overnight. But it provides a solid foundation for understanding how filtering and signal confirmation work in quantitative trading.

Start simple. Test on historical data. Adjust the windows for your specific market. Most importantly, understand why each piece of code does what it does before trading real money.


Most algo trading content gives you theory.
This gives you the code.

3 Python strategies. Fully backtested. Colab notebook included.
Plus a free ebook with 5 more strategies the moment you subscribe.

5,000 quant traders already run these:

Subscribe | AlgoEdge Insights

Read Full Tutorial open_in_new
arrow_back Back to Tutorials