0bps

Strategy vault / Mean reversion

Ergotic Dual rail Reverse MACD Quantitative Trading Strategy

This strategy is a dual-rail reverse MACD quantitative trading strategy. It draws on the technical indicators described by William Blau in his book "Momentum, Direction and Divergence" and expands on them. The…

Author
ChaoZhang
Complexity
Intermediate · 190 lines
Parameters
33
Backtest bar
1h · intraday
Tested on
BTC/USDT · futures

IMG

Overview

This strategy is a dual-rail reverse MACD quantitative trading strategy. It draws on the technical indicators described by William Blau in his book "Momentum, Direction and Divergence" and expands on them. The strategy also has backtesting capabilities and can incorporate additional features like alerts, filters, trailing stop loss, etc.

Principles

The core indicator of this strategy is MACD. It calculates the fast moving average EMA(r) and slow moving average EMA(slowMALen), then computes their difference xmacd. It also calculates the EMA(signalLength) of xmacd to get xMA_MACD. A long signal triggers when xmacd crosses above xMA_MACD, and a short signal triggers on a cross below. The key aspect of this strategy is the reverse trading signals, i.e. the relationship between xmacd and xMA_MACD is opposite to that of the conventional MACD indicator, which is also where the name "Reverse MACD" comes from.

In addition, the strategy incorporates trend filters. When a long signal fires, if the bullish trend filter is configured, it will check if the price is increasing. Similarly, the short signal checks for a downward price trend. RSI and MFI indicators can also be used to filter out signals. A stop loss mechanism is included to prevent losses beyond a threshold.

Advantage Analysis

The biggest advantage of this strategy is the powerful backtesting capabilities. You can choose different trading instruments, set the backtest timeframe, and optimize the strategy parameters based on specific instrument data. Compared to a simple MACD strategy, it incorporates trend and overbought/oversold analysis to filter out some identical signals. The dual-rail reverse MACD is different from the traditional MACD, allowing it to capitalize on some opportunities that the traditional MACD may miss.

Risk Analysis

The primary risk of this strategy comes from the reverse trading logic. While reverse signals can capture some opportunities missed by traditional signals, it also means forfeiting some conventional MACD entry points, necessitating careful assessment. Moreover, the MACD itself is prone to generating false bullish signals. The strategy may result in excessive trades and increased costs during choppy, directionless markets.

To mitigate risks, parameters can be optimized - tuning the moving average lengths; combining trends and indicator filters avoids signals in choppy markets; raising stop loss distances ensures capped losses on individual trades.

Optimization Directions

The strategy can be improved in several aspects:

  1. Adjust fast and slow rail parameters, optimize moving average lengths, backtest to find optimal parameter sets for specific instruments
  2. Add or tune trend filters, judge from backtest results whether it improves return
  3. Test different stop loss mechanisms, fixed or trailing, to determine the better performer
  4. Try combining other indicators like KD, Bollinger Bands to set additional filter conditions and ensure signal quality

Summary

The dual-rail reverse MACD quantitative strategy builds upon the classic MACD indicator with extensions and improvements. With flexible parameter configurations, abundant filter choices, and powerful backtesting functionality, it can be tuned to suit different trading instruments. Hence it is an intriguing and promising quantitative trading strategy worthy of further exploration.

Parameters

NameDefaultWhat it controls
v_input_1_close0source: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_20strategyType: Long Only | Long & Short | Short Only
v_input_37From Month
v_input_4trueFrom Day
v_input_52018From Year
v_input_612To Month
v_input_7trueTo Day
v_input_82030To Year
v_input_9144R (32,55,89,100,144,200)
v_input_106slowMALen
v_input_116signalLength
v_input_12falseTrade reverse (long/short switch)
v_input_13trueLong only if price has increased
v_input_14falseShort only if price has decreased
v_input_152trending_price_length
v_input_16falsetrending_price_with_ema
v_input_173trending_price_ema
v_input_1814rsi_length
v_input_1914RSI Sell Cutoff (Sell only if >= #)
v_input_2082RSI Buy Cutoff (Buy only if <= #)
v_input_21falseLong only if RSI has increased
v_input_222trending_rsi_length
v_input_2314mfi_length
v_input_2414mfi_lower
v_input_2582mfi_upper
v_input_26falseLong only if MFI has increased
v_input_272trending_mfi_length
v_input_28_hlc30TSL source: hlc3 | high | low | open | hl2 | close | hlcc4 | ohlc4
v_input_295Use different timeframe for stop trigger? Uncheck box above.
v_input_303tslTrigger
v_input_310.6tslStop
v_input_327Stop Loss %
v_input_331.8Take Profit %

Source Pine Script

/*backtest
start: 2023-11-20 00:00:00
end: 2023-12-20 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

//@version = 3
////////////////////////////////////////////////////////////
//  Copyright by HPotter v1.0 09/12/2016
// This is one of the techniques described by William Blau in his book
// "Momentum, Direction and Divergence" (1995). If you like to learn more,
// we advise you to read this book. His book focuses on three key aspects
// of trading: momentum, direction and divergence. Blau, who was an electrical
// engineer before becoming a trader, thoroughly examines the relationship 
// between price and momentum in step-by-step examples. From this grounding,
// he then looks at the deficiencies in other oscillators and introduces some
// innovative techniques, including a fresh twist on Stochastics. On directional 
// issues, he analyzes the intricacies of ADX and offers a unique approach to help 
// define trending and non-trending periods.
// Blau`s indicator is like usual MACD, but it plots opposite of meaningof
// stndard MACD indicator. 
//
// You can change long to short in the Input Settings
// Please, use it only for learning or paper trading. Do not for real trading.
//
//
// 2018-09 forked by Khalid Salomão
// - Backtesting
// - Added filters: RSI, MFI, Price trend
// - Trailing Stop Loss
// - Other minor adjustments
//
////////////////////////////////////////////////////////////
strategy(title="Ergotic MACD Backtester [forked from HPotter]", shorttitle="Ergotic MACD Backtester", overlay=true, pyramiding=0, default_qty_type=strategy.cash, default_qty_value=25000, initial_capital=50000, commission_type=strategy.commission.percent, commission_value=0.15, slippage=3)


// === BACKTESTING: INPUT BACKTEST RANGE ===
source = input(close)
strategyType = input(defval="Long Only", options=["Long & Short", "Long Only", "Short Only"])

FromMonth = input(defval = 7, title = "From Month", minval = 1, maxval = 12)
FromDay   = input(defval = 1, title = "From Day", minval = 1, maxval = 31)
FromYear  = input(defval = 2018, title = "From Year", minval = 2017)
ToMonth   = input(defval = 12, title = "To Month", minval = 1, maxval = 12)
ToDay     = input(defval = 1, title = "To Day", minval = 1, maxval = 31)
ToYear    = input(defval = 2030, title = "To Year", minval = 2017)

start     = timestamp(FromYear, FromMonth, FromDay, 00, 00)  
finish    = timestamp(ToYear, ToMonth, ToDay, 23, 59)        
window()  => true // window of time verification

// === STRATEGY ===

r = input(144, minval=1, title="R (32,55,89,100,144,200)") // default 32
slowMALen = input(6, minval=1) // default 32
signalLength = input(6, minval=1)
reverse = input(false, title="Trade reverse (long/short switch)")

//hline(0, color=blue, linestyle=line)

fastMA = ema(source, r)
slowMA = ema(source, slowMALen)
xmacd = fastMA - slowMA
xMA_MACD = ema(xmacd, signalLength)

pos = 0
pos := iff(xmacd < xMA_MACD, 1,
	   iff(xmacd > xMA_MACD, -1, nz(pos[1], 0))) 
possig = 0
possig := iff(reverse and pos == 1, -1,
          iff(reverse and pos == -1, 1, pos))

// === FILTER: price trend ====
trending_price_long = input(true, title="Long only if price has increased" )
trending_price_short = input(false, title="Short only if price has decreased" )
trending_price_length = input( 2, minval=1 )
trending_price_with_ema = input( false )
trending_price_ema = input( 3, minval=1 )
price_trend = trending_price_with_ema ? ema(source, trending_price_ema) : source
priceLongTrend() => (trending_price_long ? rising(price_trend, trending_price_length) : true)
priceShortTrend() => (trending_price_short ? falling(price_trend, trending_price_length) : true)

// === FILTER: RSI ===
rsi_length = input( 14, minval=1 )
rsi_overSold = input( 14, minval=0, title="RSI Sell Cutoff (Sell only if >= #)" )
rsi_overBought = input( 82, minval=0, title="RSI Buy Cutoff (Buy only if <= #)" )

vrsi = rsi(source, rsi_length)
rsiOverbought() => vrsi > rsi_overBought
rsiOversold() => vrsi < rsi_overSold

trending_rsi_long = input(false, title="Long only if RSI has increased" )
trending_rsi_length = input( 2 )
rsiLongTrend() => trending_rsi_long ? rising(vrsi, trending_rsi_length) : true

// === FILTER: MFI ===
mfi_length = input(14, minval=1)
mfi_lower = input(14, minval=0, maxval=50)
mfi_upper = input(82, minval=50, maxval=100)
upper_s = sum(volume * (change(source) <= 0 ? 0 : source), mfi_length)
lower_s = sum(volume * (change(source) >= 0 ? 0 : source), mfi_length)
mf = rsi(upper_s, lower_s)

mfiOverbought() => (mf > mfi_upper)
mfiOversold() => (mf < mfi_lower)

trending_mfi_long = input(false, title="Long only if MFI has increased" )
trending_mfi_length = input( 2 )
mfiLongTrend() => trending_mfi_long ? rising(mf, trending_mfi_length) : true

// === SIGNAL CALCULATION ===
long  = window() and possig == 1 and rsiLongTrend() and mfiLongTrend() and not rsiOverbought() and not mfiOverbought() and priceLongTrend()
short = window() and possig == -1 and not rsiOversold() and not mfiOversold() and priceShortTrend()

// === trailing stop
tslSource=input(hlc3,title="TSL source")
//suseCurrentRes = input(true, title="Use current chart resolution for stop trigger?")
tslResolution = input(title="Use different timeframe for stop trigger? Uncheck box above.", defval="5")
tslTrigger = input(3.0) / 100
tslStop = input(0.6) / 100

currentPrice = request.security(syminfo.tickerid, tslResolution, tslSource, barmerge.gaps_off, barmerge.lookahead_off)

isLongOpen = false
isLongOpen := nz(isLongOpen[1], false)
entryPrice=0.0
entryPrice:= nz(entryPrice[1], 0.0)
trailPrice=0.0
trailPrice:=nz(trailPrice[1], 0.0)

// update TSL high mark
if (isLongOpen )
    if (not trailPrice and currentPrice >= entryPrice * (1 + tslTrigger))
        trailPrice := currentPrice
    else 
        if (trailPrice and currentPrice > trailPrice)
            trailPrice := currentPrice

if (trailPrice and currentPrice <= trailPrice * (1 - tslStop))
    // FIRE TSL SIGNAL
    short:=true // <===
    long := false

// if short clean up
if (short)
    isLongOpen := false
    entryPrice := 0.0
    trailPrice := 0.0

if (long)
    isLongOpen := true
    if (not entryPrice)
        entryPrice := currentPrice

// === BACKTESTING: ENTRIES ===
if long
    if (strategyType == "Short Only")
        strategy.close("Short")
    else
        strategy.entry("Long", strategy.long, comment="Long")

if short
    if (strategyType == "Long Only")
        strategy.close("Long")
    else
        strategy.entry("Short", strategy.short, comment="Short")	  
    
//barcolor(possig == -1 ? red: possig == 1 ? green : blue )
//plot(xmacd, color=green, title="Ergotic MACD")
//plot(xMA_MACD, color=red, title="SigLin")

plotshape(trailPrice ? trailPrice : na, style=shape.circle, location=location.absolute, color=blue, size=size.tiny)

plotshape(long, style=shape.triangleup, location=location.belowbar, color=green, size=size.tiny)
plotshape(short, style=shape.triangledown, location=location.abovebar, color=red, size=size.tiny)

// === Strategy Alert ===
alertcondition(long, title='BUY - Ergotic MACD Long Entry', message='Go Long!')
alertcondition(short, title='SELL - Ergotic MACD Long Entry', message='Go Short!')

// === BACKTESTING: EXIT strategy ===
sl_inp = input(7, title='Stop Loss %', type=float)/100
tp_inp = input(1.8, title='Take Profit %', type=float)/100

stop_level = strategy.position_avg_price * (1 - sl_inp)
take_level = strategy.position_avg_price * (1 + tp_inp)

strategy.exit("Stop Loss/Profit", "Long", stop=stop_level, limit=take_level)

Published by ChaoZhang on the FMZ strategy square · last modified 2023-12-21. Collected in the Quant Trading Vault. Unvalidated research: re-test with your own data and costs before trusting it.

Build this with AgenKit

A spec is not a trading system. Paste this into your coding agent and AgenKit runs a specialist team — architecture, test-first build, risk gates, review — with paper trading on by default.

in Claude Code, Cursor, Codex & 4 more
/agenkit build a production-grade trading bot with a backtest engine, risk controls and paper trading by default from strategies/Ergotic-Dual-rail-Reverse-MACD-Quantitative-Trading-Strategy.md
  1. npx agenkit install engineering-kit in your project
  2. Paste the command into your agent and approve each phase
  3. Get tests, backtest, risk controls and a paper-trading switch

Understand it first

Before you run this, read the lesson behind the idea and how to backtest it honestly. Specs like this one are the survivors of an unknown number of trials; deflate their results accordingly.

Related specs

esc

Try , , or .

↑↓ move↵ open