0bps

Strategy vault / Trend following

An Optimization of Dual Moving Average Trend Following Strategy Based on Indicators Combination

This strategy generates trading signals by calculating fast and slow moving average lines and combining Parabolic SAR indicator. It belongs to the trend following strategy. When the fast MA crosses over the slow MA…

Author
ChaoZhang
Complexity
Intermediate · 204 lines
Parameters
20
Backtest bar
4h · intraday
Tested on
BTC/USDT · futures

IMG

Overview

This strategy generates trading signals by calculating fast and slow moving average lines and combining Parabolic SAR indicator. It belongs to the trend following strategy. When the fast MA crosses over the slow MA, long position will be opened. When the fast MA crosses below the slow MA, short position will be opened. Parabolic SAR is used to filter fake breakouts.

Strategy Principle

  1. Calculate fast and slow moving average lines. The parameters can be customized.
  2. Compare the two MA lines to determine market trend. When fast MA crosses over slow MA, it indicates bullish trend. When fast MA crosses below slow MA, it indicates bearish trend.
  3. Further confirmation is made by checking if close price is above/below fast MA. Only when fast MA crosses over slow MA and close price is above fast MA, long signal is generated. Only when fast MA crosses below slow MA and close price is below fast MA, short signal is generated.
  4. Parabolic SAR is used to filter fake signals. Only when all the three criteria are met, final signal is generated.
  5. Stop loss is set based on maximum tolerable loss. ATR indicator is used to calculate dynamic stop loss price.

Advantages

  1. MA lines determine market trend and avoid excessive trading in range-bound market.
  2. Dual filters lower risk of fake breakout significantly.
  3. Stop loss strategy effectively limits per trade loss.

Risks

  1. Indicator strategies tend to generate false signals
  2. No consideration of currency exposure risk
  3. Potentially miss initial trend in opposite direction

The strategy can be optimized in below aspects:

  1. Optimize MA parameters to fit specific product
  2. Add other indicators or models for signal filtering
  3. Consider real-time hedging or auto currency conversion

Directions for Optimization

  1. Optimize MA parameters to better capture trends
  2. Increase model diversity to improve signal accuracy
  3. Multi-timeframe verification to avoid being trapped
  4. Enhance stop loss strategy to increase stability

Conclusion

This is a typical dual moving average cross and indicators combination trend following strategy. By comparing fast and slow MA directions, market trend is determined. Various filter indicators are used avoid false signals. At the same time, stop loss function is implemented to control per trade loss. The advantage is that the strategy logic is simple and easy to understand and optimize. The disadvantage is that as a coarse trend tool, there is still room to improve signal accuracy, by introducing machine learning models for example.

Parameters

NameDefaultWhat it controls
v_input_bool_1true(?Backtest Time Period)Filter Date Range of Backtest
v_input_1timestamp(1 jan 2000)Start Date
v_input_2timestamp(1 Jul 2100)End Date
v_input_bool_2true(?Long & Short Position)On/Off Long Postion
v_input_bool_3trueOn/Off Short Postion
v_input_string_10(?Slow MA Inputs)Slow MA Type: SMA | EMA | WMA | HMA | RMA | SWMA | ALMA | VWMA | VWAP
v_input_int_1160Slow MA Length
v_input_3_close0Slow MA Source: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_string_20(?Fast MA Inputs)Fast MA Type: SMA | EMA | WMA | HMA | RMA | SWMA | ALMA | VWMA | VWAP
v_input_int_240Fast MA Length
v_input_4_close0Fast MA Source: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_bool_4true(?Parabolic SAR Inputs)Use Parabolic Sar?
v_input_bool_5falseDisplay Parabolic Sar?
v_input_float_10.02Start
v_input_float_20.02Increment
v_input_float_30.2Maximum
v_input_int_314(?Risk Management Inputs)ATR Length
v_input_float_42Long Position - Stop Loss - ATR Multiplier
v_input_float_52Short Position - Stop Loss - ATR Multiplier
v_input_float_62% of Equity at Risk

Source Pine Script

/*backtest
start: 2024-01-01 00:00:00
end: 2024-01-31 00:00:00
period: 4h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © sosacur01

//@version=5
strategy(title="2 MA | Trend Following", overlay=true, pyramiding=1, commission_type=strategy.commission.percent, commission_value=0.2, initial_capital=10000)

//==========================================


//BACKTEST RANGE
useDateFilter = input.bool(true, title="Filter Date Range of Backtest",
     group="Backtest Time Period")
backtestStartDate = input(timestamp("1 jan 2000"), 
     title="Start Date", group="Backtest Time Period",
     tooltip="This start date is in the time zone of the exchange " + 
     "where the chart's instrument trades. It doesn't use the time " + 
     "zone of the chart or of your computer.")
backtestEndDate = input(timestamp("1 Jul 2100"),
     title="End Date", group="Backtest Time Period",
     tooltip="This end date is in the time zone of the exchange " + 
     "where the chart's instrument trades. It doesn't use the time " + 
     "zone of the chart or of your computer.")
inTradeWindow = true
if not inTradeWindow and inTradeWindow[1]
    strategy.cancel_all()
    strategy.close_all(comment="Date Range Exit")

//--------------------------------------

//LONG/SHORT POSITION ON/OFF INPUT
LongPositions   = input.bool(title='On/Off Long Postion', defval=true, group="Long & Short Position")
ShortPositions  = input.bool(title='On/Off Short Postion', defval=true, group="Long & Short Position")

//---------------------------------------

//SLOW MA INPUTS
averageType1   = input.string(defval="SMA", group="Slow MA Inputs", title="Slow MA Type", options=["SMA", "EMA", "WMA", "HMA", "RMA", "SWMA", "ALMA", "VWMA", "VWAP"])
averageLength1 = input.int(defval=160, group="Slow MA Inputs", title="Slow MA Length", minval=50)
averageSource1 = input(close, title="Slow MA Source", group="Slow MA Inputs")
           

//SLOW MA TYPE
MovAvgType1(averageType1, averageSource1, averageLength1) =>
	switch str.upper(averageType1)
        "SMA"  => ta.sma(averageSource1, averageLength1)
        "EMA"  => ta.ema(averageSource1, averageLength1)
        "WMA"  => ta.wma(averageSource1, averageLength1)
        "HMA"  => ta.hma(averageSource1, averageLength1)
        "RMA"  => ta.rma(averageSource1, averageLength1)
        "SWMA" => ta.swma(averageSource1)
        "ALMA" => ta.alma(averageSource1, averageLength1, 0.85, 6)
        "VWMA" => ta.vwma(averageSource1, averageLength1)
        "VWAP" => ta.vwap(averageSource1)
        => runtime.error("Moving average type '" + averageType1 + 
             "' not found!"), na


//----------------------------------

//FAST MA INPUTS
averageType2   = input.string(defval="SMA", group="Fast MA Inputs", title="Fast MA Type", options=["SMA","EMA","WMA","HMA","RMA","SWMA","ALMA","VWMA","VWAP"])
averageLength2 = input.int(defval=40, group="Fast MA Inputs", title="Fast MA Length", maxval=40)
averageSource2 = input(close, title="Fast MA Source", group="Fast MA Inputs")

//FAST MA TYPE
MovAvgType2(averageType2, averageSource2, averageLength2) =>
	switch str.upper(averageType2)
        "SMA"  => ta.sma(averageSource2, averageLength2)
        "EMA"  => ta.ema(averageSource2, averageLength2)
        "WMA"  => ta.wma(averageSource2, averageLength2)
        "HMA"  => ta.hma(averageSource2, averageLength2)
        "RMA"  => ta.rma(averageSource2, averageLength2)
        "SWMA" => ta.swma(averageSource2)
        "ALMA" => ta.alma(averageSource2, averageLength2, 0.85, 6)
        "VWMA" => ta.vwma(averageSource2, averageLength2)
        "VWAP" => ta.vwap(averageSource2)
        => runtime.error("Moving average type '" + averageType2 + 
             "' not found!"), na

//---------------------------------------------------

//MA VALUES
FASTMA = MovAvgType2(averageType2, averageSource2, averageLength2)
SLOWMA = MovAvgType1(averageType1, averageSource1, averageLength1)

//BUY/SELL TRIGGERS
bullish_trend = FASTMA > SLOWMA and close > FASTMA
bearish_trend = FASTMA < SLOWMA and close < FASTMA

//MAs PLOT
plot1 = plot(SLOWMA,color=color.gray, linewidth=1, title="Slow-MA")
plot2 = plot(FASTMA,color=color.yellow, linewidth=1, title="Fast-MA")
fill(plot1, plot2, color=SLOWMA>FASTMA ? color.new(color.red, 70) : color.new(color.green, 70), title="EMA Clouds")

//-----------------------------------------------------

//PARABOLIC SAR USER INPUT
usepsarFilter = input.bool(title='Use Parabolic Sar?', defval=true, group = "Parabolic SAR Inputs")
psar_display  = input.bool(title="Display Parabolic Sar?", defval=false, group="Parabolic SAR Inputs")
start         = input.float(title="Start", defval=0.02, group="Parabolic SAR Inputs", step=0.001)
increment     = input.float(title="Increment", defval=0.02, group="Parabolic SAR Inputs", step=0.001)
maximum       = input.float(title="Maximum", defval=0.2, group="Parabolic SAR Inputs", step=0.001)

//SAR VALUES
psar        = request.security(syminfo.tickerid, "D", ta.sar(start, increment, maximum))

//BULLISH & BEARISH PSAR CONDITIONS
bullish_psar = (usepsarFilter ? low > psar : bullish_trend )
bearsish_psar = (usepsarFilter ? high < psar : bearish_trend)

//SAR PLOT
psar_plot    = if low > psar
    color.rgb(198, 234, 199, 13)
else
    color.rgb(219, 134, 134, 48)
    
plot(psar_display ? psar : na, color=psar_plot, title="Par SAR")

//-------------------------------------

//ENTRIES AND EXITS
long_entry  = if inTradeWindow and bullish_trend  and bullish_psar and LongPositions
    true
long_exit   = if inTradeWindow and bearish_trend   
    true

short_entry = if inTradeWindow  and bearish_trend and bearsish_psar and ShortPositions
    true
short_exit  = if inTradeWindow  and bullish_trend 
    true

//--------------------------------------

//RISK MANAGEMENT - SL, MONEY AT RISK, POSITION SIZING
atrPeriod                = input.int(14, "ATR Length", group="Risk Management Inputs")
sl_atr_multiplier        = input.float(title="Long Position - Stop Loss - ATR Multiplier", defval=2, group="Risk Management Inputs", step=0.5)
sl_atr_multiplier_short  = input.float(title="Short Position - Stop Loss - ATR Multiplier", defval=2, group="Risk Management Inputs", step=0.5)
i_pctStop                = input.float(2, title="% of Equity at Risk", step=.5, group="Risk Management Inputs")/100

//ATR VALUE
_atr = ta.atr(atrPeriod)

//CALCULATE LAST ENTRY PRICE
lastEntryPrice = strategy.opentrades.entry_price(strategy.opentrades - 1)

//STOP LOSS - LONG POSITIONS 
var float sl = na

//CALCULTE SL WITH ATR AT ENTRY PRICE - LONG POSITION
if (strategy.position_size[1] != strategy.position_size)
    sl := lastEntryPrice - (_atr * sl_atr_multiplier)

//IN TRADE - LONG POSITIONS
inTrade = strategy.position_size > 0

//PLOT SL - LONG POSITIONS
plot(inTrade ? sl : na, color=color.blue, style=plot.style_circles, title="Long Position - Stop Loss")

//CALCULATE ORDER SIZE - LONG POSITIONS
positionSize = (strategy.equity * i_pctStop) / (_atr * sl_atr_multiplier)

//============================================================================================

//STOP LOSS - SHORT POSITIONS 
var float sl_short = na

//CALCULTE SL WITH ATR AT ENTRY PRICE - SHORT POSITIONS 
if (strategy.position_size[1] != strategy.position_size)
    sl_short := lastEntryPrice + (_atr * sl_atr_multiplier_short)

//IN TRADE SHORT POSITIONS
inTrade_short = strategy.position_size < 0

//PLOT SL - SHORT POSITIONS
plot(inTrade_short ? sl_short : na, color=color.red, style=plot.style_circles, title="Short Position - Stop Loss")

//CALCULATE ORDER - SHORT POSITIONS
positionSize_short = (strategy.equity * i_pctStop) / (_atr * sl_atr_multiplier_short) 


//===============================================

//LONG STRATEGY
strategy.entry("Long", strategy.long, comment="Long", when = long_entry, qty=positionSize)
if (strategy.position_size > 0)
    strategy.close("Long", when = (long_exit), comment="Close Long")
    strategy.exit("Long", stop = sl, comment="Exit Long")

//SHORT STRATEGY
strategy.entry("Short", strategy.short, comment="Short", when = short_entry, qty=positionSize_short)
if (strategy.position_size < 0) 
    strategy.close("Short", when = (short_exit), comment="Close Short")
    strategy.exit("Short", stop = sl_short, comment="Exit Short")

//ONE DIRECTION TRADING COMMAND (BELLOW ONLY ACTIVATE TO CORRECT BUGS)

Published by ChaoZhang on the FMZ strategy square · last modified 2024-02-01. 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/An-Optimization-of-Dual-Moving-Average-Trend-Following-Strategy-Based-on-Indicators-Combination.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