← Back to All Tutorials

Tutorial 07: MACD Histogram Filter Backtest Intermediate

Table of Contents
  1. What Does This Script Do?
  2. Key Concepts
  3. The Approach
  4. Code Walkthrough
  5. Understanding Results
  6. Dependencies
  7. Glossary

1. What Does This Script Do?

This is an extension of Tutorial 06. It adds a "histogram strength filter" to the MACD backtest. Instead of trading every time MACD crosses the Signal line, it only trades when the MACD histogram is strong enough (above a minimum threshold). This filters out weak signals and only keeps the high-conviction ones.

Simple Analogy: In Tutorial 06, you traded every time the wind changed direction. Now, you only trade when the wind is strong enough to actually push the sails. A gentle breeze (low histogram) might not mean anything, but a strong gust (high histogram) is worth acting on.

2. Key Concepts

The MACD Histogram

The histogram is the difference between the MACD line and the Signal line:

Histogram = MACD Line - Signal Line

The key insight: not all crossovers are equal. A crossover with a tiny histogram is a weak signal. A crossover with a large histogram is a strong signal.

Histogram Thresholds Tested

ThresholdMeaningEffect
1Very low - almost any signal passesMany trades, lower quality
5Low - moderate filterStill many trades
10Medium - only decent signalsFewer trades, better quality
20High - only strong signalsVery few trades, highest quality
50Very high - only extreme signalsTiny number of trades
100Extreme - only the strongestAlmost no trades

3. The Approach

The script tests a nested grid of parameter combinations:

Why this matters: In trading, there's a tradeoff between signal quality and quantity. Low thresholds give you many trades but more noise. High thresholds give you fewer but higher-quality trades. The backtest finds the sweet spot.

4. Code Walkthrough

1 The Nested Loop

# For each MACD parameter combination...
for fast, slow, signal_period in MACD_COMBOS:
    # Compute MACD with these parameters
    compute_macd(df, fast, slow, signal_period)
    build_5min_markets(df)

    # For each histogram threshold...
    for threshold in HIST_THRESHOLDS:
        # Only keep markets where |histogram| >= threshold
        filtered = markets[abs(markets['histogram_at_open']) >= threshold]

        # Calculate win rate and P&L on filtered trades
        simulate_pnl(filtered)

What it does: For every combination of MACD settings and histogram thresholds, it filters the signals and simulates trading. This creates a grid of results that can be compared.

2 Results Output

# Only show combos with positive edge (above breakeven)
positive = results_df[results_df['edge'] > 0]

for _, row in positive.iterrows():
    print(f"MACD({row['fast']}/{row['slow']}/{row['signal_period']}) hist>{row['hist_threshold']} | "
          f"Trades: {row['trades']} | WR: {row['win_rate']:.2f}% | "
          f"Edge: +{row['edge']:.2f}% | P&L: ${row['pnl']:,.2f}")

What it does: Sorts all results by edge and only shows the ones that are profitable. This helps you quickly see which parameter combinations actually work.

5. Understanding Results

Best reported result: MACD(6/20/5) with histogram > 10 gives 55.12% win rate with +$480 P&L.

How to read results:

Warning: A strategy that looks great in backtesting may not work in live trading due to slippage, fees, market impact, and changing market conditions.

6. Dependencies

pip install pandas pandas_ta numpy

7. Glossary

TermMeaning
HistogramThe difference between MACD line and Signal line, shown as bars
ThresholdA minimum value that must be exceeded for a signal to be used
FilterA rule that removes weak or unwanted signals
Grid SearchTesting all combinations of multiple parameters
OverfittingWhen a strategy works on historical data but fails on new data
EdgeThe statistical advantage of a strategy over random/breakeven

7. Full Code: Python to Pseudo-Code Translation

Configuration - What We're Testing

# --- PYTHON ---
MACD_COMBOS = [(6, 20, 5), (6, 26, 5)]
HIST_THRESHOLDS = [1, 5, 10, 20, 50, 100]
ENTRY_PRICE = 0.54
USD_PER_BET = 10.0

# --- PSEUDO-CODE ---
SET the MACD parameter combinations to test:
    Combo 1: fast=6, slow=20, signal=5
    Combo 2: fast=6, slow=26, signal=5
SET the histogram strength thresholds to test: 1, 5, 10, 20, 50, 100
SET Polymarket entry price to $0.54 per share
SET bet size to $10 per trade

Payout Math Calculation

# --- PYTHON ---
shares_per_bet = USD_PER_BET / ENTRY_PRICE       # 10.0 / 0.54 = ~18.5
win_profit = (1.0 - ENTRY_PRICE) * shares_per_bet  # ~$8.52
loss_amount = ENTRY_PRICE * shares_per_bet          # ~$10.00
breakeven_wr = loss_amount / (win_profit + loss_amount) * 100  # ~54%

# --- PSEUDO-CODE ---
CALCULATE shares per bet = $10.00 / $0.54 = about 18.5 shares
CALCULATE profit per win = ($1.00 - $0.54) x 18.5 = about $8.52
CALCULATE loss per loss = $0.54 x 18.5 = about $10.00
CALCULATE breakeven win rate = $10.00 / ($8.52 + $10.00) = about 54%
    (You need to win more than 54% of bets to be profitable)

The Nested Grid Search Loop

# --- PYTHON ---
for fast, slow, signal in MACD_COMBOS:
    macd_result = ta.macd(df['close'], fast=fast, slow=slow, signal=signal)
    df_temp['macd_line'] = macd_result[...]
    df_temp['macd_signal'] = macd_result[...]
    df_temp['macd_histogram'] = macd_result[...]
    # Build 5-min markets
    markets = df_temp.groupby('market_start').agg(...)
    # For each threshold
    for threshold in HIST_THRESHOLDS:
        filtered = markets[markets['histogram_at_open'].abs() > threshold]
        filtered['pick'] = np.where(
            filtered['macd_at_open'] > filtered['signal_at_open'], 'UP', 'DOWN')
        filtered['win'] = filtered['pick'] == filtered['actual']
        trades = len(filtered)
        wins = filtered['win'].sum()
        win_rate = wins / trades * 100
        edge = win_rate - breakeven_wr
        total_pnl = wins * win_profit - (trades - wins) * loss_amount

# --- PSEUDO-CODE ---
FOR EACH MACD parameter combination (fast, slow, signal):
    CALCULATE MACD using these specific parameters
    ADD macd_line, macd_signal, and macd_histogram columns to data

    GROUP 1-minute data into 5-minute market windows
    FOR EACH histogram threshold value (1, 5, 10, 20, 50, 100):

        FILTER: keep ONLY markets where the absolute histogram value
                is GREATER THAN the threshold
                (This removes weak signals)

        FOR each remaining market:
            IF MACD > Signal at market open: PREDICT "UP"
            ELSE: PREDICT "DOWN"
            CHECK: did the prediction match the actual outcome?

        COUNT total trades, wins, losses
        CALCULATE win rate = wins / total trades x 100
        CALCULATE edge = win rate minus breakeven (54%)
        CALCULATE total P&L = (wins x profit) - (losses x loss_amount)

        RECORD all these results

Results Filtering & Display

# --- PYTHON ---
results_df = pd.DataFrame(all_results).sort_values('edge', ascending=False)
positive = results_df[results_df['edge'] > 0]
for _, row in positive.iterrows():
    print(f"MACD({row['fast']}/{row['slow']}/{row['signal_period']}) "
          f"hist>{row['hist_threshold']} | "
          f"Trades: {row['trades']} | WR: {row['win_rate']:.2f}% | "
          f"Edge: +{row['edge']:.2f}% | P&L: ${row['pnl']:,.2f}")

# --- PSEUDO-CODE ---
COMPILE all results into a table
SORT by edge (best edge first)

FILTER to show ONLY combinations with positive edge (above breakeven)

FOR each profitable combination:
    PRINT in a readable format:
        "MACD(6/20/5) with histogram > 10 |
         5420 trades | 55.12% win rate |
         +1.12% edge over breakeven |
         $480 total profit"

SAVE all results to a CSV file for later analysis