0bps

Strategy vault / Momentum

Momentum Moving Average Crossover Strategy

This strategy utilizes the crossover principles between fast and slow moving averages to determine market trend directions and generate buy and sell signals. The strategy is simple and easy to implement, suitable for…

Author
ChaoZhang
Complexity
Intermediate · 138 lines
Parameters
19
Backtest bar
1d · swing
Tested on
BTC/USDT · futures

Overview

This strategy utilizes the crossover principles between fast and slow moving averages to determine market trend directions and generate buy and sell signals. The strategy is simple and easy to implement, suitable for medium-term trading.

Strategy Logic

The strategy employs two moving averages, one fast line and one slow line. The fast line uses 3-day EMA and the slow line uses 15-day EMA. When the fast line crosses above the slow line from below, it indicates an upward trend and gives a buy signal. On the contrary, when the fast line crosses below the slow line from above, it signals a downward trend and gives a sell signal.

The strategy also sets a faster 3-day EMA as the fast exit line. When price breaks below this fast exit line, it judges the trend has reversed and should exit the existing long position. Likewise, when price breaks back above the exit line, it indicates a renewed uptrend and gives a signal to re-enter long.

The specific operation signals are set as:

  1. Fast line crosses above slow line from below, go long

  2. Fast line crosses below slow line from above, go short

  3. Price breaks below fast exit line, close long position

  4. Price breaks back above fast exit line, re-enter long

Advantages

  • Simple to use, only need to configure two moving average parameters, easy to implement

  • Sufficient backtesting data, uses common indicators to evaluate viability

  • Many configurable parameters for optimization

  • Adopts fast exit line as stop loss to better control risk

  • Clear strategy logic, explicit buy and sell signals

  • Appropriate operation frequency, avoids over-trading

Risks

  • Prone to more false signals when trend is unclear as trend following strategy

  • Moving averages have lagging nature, may miss turn points

  • Fixed parameters cannot adapt to market changes, needs optimization

  • Stop loss may be too soft, unable to stop loss in time

  • Frequent signals may lead to higher trading costs

  • Signals may diverge and needs confirmation with other indicators

Risks can be managed by parameter optimization, adding filters, relaxing stop loss, updating parameters timely etc.

Enhancement

  • Test and optimize parameters to better fit market conditions

  • Introduce more indicators to form a robust system

  • Build adaptive parameter settings based on real-time market

  • Apply machine learning models for smarter optimization

  • Set dynamic or trailing stop loss for better risk control

  • Combine volume indicators to avoid divergence

Conclusion

This is a relatively simple dual moving average crossover strategy. It determines market trend and trading signals based on the interaction between fast and slow moving averages. The strategy is easy to implement and can be adapted via optimization. But it also has some risks. More filters are needed to confirm signals and manage risks. When properly optimized and applied to medium-term trading, it can become a very practical quantitative trading system.

Parameters

NameDefaultWhat it controls
v_input_13FAST EMA
v_input_2_close0Source for Fast: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_315SLOW EMA
v_input_4_close0Source for Slow: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_53FAST EMA Exit
v_input_6_close0Source for Fast Exit: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_7_low0Source for Short Entry: low | high | close | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_8_high0Source for Long Entry: high | close | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_9_low0Source for Short Exit: low | high | close | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_10_high0Source for Long Exit: high | close | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_110.005Exit Tuner to touch slowEMA
v_input_12trueRun Strategy
v_input_13trueFrom Month
v_input_14trueFrom Day
v_input_152020From Year
v_input_16trueThru Month
v_input_17trueThru Day
v_input_182100Thru Year
v_input_19trueShow Date Range

Source Pine Script

/*backtest
start: 2023-01-01 00:00:00
end: 2023-02-03 00:00:00
period: 1d
basePeriod: 1h
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/
// © ehaarjee, ECHKAY, JackBauer007

//@version=4
//study(title="Tale_indicators", overlay=true)
strategy("Tale Indicators Strategy", overlay=true, precision=8, max_bars_back=200, pyramiding=0, initial_capital=20000, commission_type="percent", commission_value=0.1)

len_fast = input(3, minval=1, title="FAST EMA")
src_fast = input(close, title="Source for Fast")
fastMA = ema(src_fast, len_fast)
plot(fastMA, title="Slow EMA", color=color.orange)

len_slow = input(15, minval=1, title="SLOW EMA")
src_slow = input(close, title="Source for Slow")
slowMA = ema(src_slow, len_slow)
plot(slowMA, title="Fast EMA", color=color.blue)

len_fast_exit = input(3, minval=1, title="FAST EMA Exit")
src_fast_exit = input(close, title="Source for Fast Exit")
fastMAE = ema(src_fast_exit, len_fast_exit)
plot(fastMAE, title="Fast EMA Ex", color=color.red)


src_slow_enter_short = input(low, title="Source for Short Entry")
slowMASEn = ema(src_slow_enter_short, len_slow)

src_slow_enter_long = input(high, title="Source for Long Entry")
slowMALEn = ema(src_slow_enter_long, len_slow)

src_slow_exit_short = input(low, title="Source for Short Exit")
slowMASEx = ema(src_slow_enter_short, len_slow)

src_slow_exit_long = input(high, title="Source for Long Exit")
slowMALEx = ema(src_slow_enter_long, len_slow)

enter_long = crossover(fastMA, slowMALEn)
enter_short = crossunder(fastMA, slowMASEn)
exit_long = crossunder(fastMAE, slowMALEx)
exit_short = crossover(fastMAE, slowMALEx)

out_enter = iff(enter_long == true, 1, iff(enter_short == true, -1, 0))
plotarrow(out_enter, "Plot Enter Points", colorup=color.green, colordown=color.red, maxheight = 30)

bull = fastMA > slowMALEn
bear = fastMA < slowMASEn

c = bull ? color.green : bear ? color.red  : color.white
bgcolor(c)

exit_tuner = input(0.005, title="Exit Tuner to touch slowEMA")

bull_exit = (bull and (low>(fastMAE*(1+exit_tuner)))) or exit_long or (not(bear) and (fastMAE>high))
bear_exit = (bear and ((fastMAE*(1-exit_tuner))>high)) or exit_short or (not(bull) and (low>fastMAE))

bull_r = (bull and ((bull_exit[1]) or (bull_exit[2] and bull_exit[1])) and (low<=fastMAE))
bear_r = (bear and ((bear_exit[1]) or (bull_exit[2] and bull_exit[1])) and (fastMAE<=high))

bull_re = (bull and (low<slowMALEn)) and not(enter_long)
bear_re = (bear and (high>slowMASEn)) and not(enter_short)

bull_ree = (bull and ((low<slowMALEn) and not(bull_re[1] or enter_long[1]))) 
bear_ree = (bear and ((high>slowMASEn) and not(bear_re[1] or enter_short[1])))

bull_reenter =  (bull_r) and not(enter_long)
bear_reenter =  (bear_r) and not(enter_short)

plotshape(bull_exit, "Plot Bull Exit", style = shape.arrowdown, color=color.green, size=size.small, text="ExL", location=location.abovebar)
plotshape(bear_exit, "Plot Bear Exit", style = shape.arrowup, color=color.red, size=size.small, text="ExS", location=location.belowbar)

plotshape(bull_reenter, "Plot Bull ReEnter", style = shape.arrowup, color=color.green, size=size.small, text="ReL", location=location.belowbar)
plotshape(bear_reenter, "Plot Bear ReEnter", style = shape.arrowdown, color=color.red, size=size.small, text="ReS", location=location.abovebar)

run_strategy = input(true, title="Run Strategy")
// === INPUT BACKTEST RANGE ===
fromMonth = input(defval = 1,    title = "From Month",      type = input.integer, minval = 1, maxval = 12)
fromDay   = input(defval = 1,    title = "From Day",        type = input.integer, minval = 1, maxval = 31)
fromYear  = input(defval = 2020, title = "From Year",       type = input.integer, minval = 2000)
thruMonth = input(defval = 1,    title = "Thru Month",      type = input.integer, minval = 1, maxval = 12)
thruDay   = input(defval = 1,    title = "Thru Day",        type = input.integer, minval = 1, maxval = 31)
thruYear  = input(defval = 2100, title = "Thru Year",       type = input.integer, minval = 2000)

// === INPUT SHOW PLOT ===
showDate  = input(defval = true, title = "Show Date Range", type = input.bool)
// === FUNCTION EXAMPLE ===
start     = timestamp(fromYear, fromMonth, fromDay, 00, 00)        // backtest start window
finish    = timestamp(thruYear, thruMonth, thruDay, 23, 59)        // backtest finish window
window()  => true       // create function "within window of time"

var long_position_open = false
var short_position_open = false


if (enter_long and not(bull_exit) and not(long_position_open))
//    strategy.entry("LO", strategy.long, qty=4)
    long_position_open := true
    if (short_position_open)
//        strategy.close("SO")
        short_position_open := false

if (bull_reenter and not(long_position_open))  
//    strategy.entry("LO", strategy.long, qty=1)
    long_position_open := true

if (bull_exit and long_position_open)
//  strategy.close("LO")
    long_position_open := false
    
if (enter_short and not(bear_exit) and not(short_position_open))
//    strategy.entry("SO", strategy.short, qty=4)
    short_position_open := true
    if(long_position_open)
//        strategy.close("LO")
        long_position_open := false

if (bear_reenter and not(short_position_open))  
//    strategy.entry("SO", strategy.long, qty=1)
    long_position_open := true

if (bear_exit and short_position_open)
//    strategy.close("SO")
    short_position_open := false

if(run_strategy)
    strategy.entry("LO", strategy.long, when=(window() and enter_long), qty=4)
    strategy.entry("LO", strategy.long, when=(window() and bull_reenter and not(long_position_open)), qty=1)
    strategy.close("LO", when=(window() and bull_exit and long_position_open))
    strategy.entry("SO", strategy.short, when=(window() and enter_short), qty=4)
    strategy.entry("SO", strategy.short, when=(window() and bear_reenter and not(short_position_open)), qty=1)
    strategy.close("SO", when=(window() and bear_exit and short_position_open))

Published by ChaoZhang on the FMZ strategy square · last modified 2023-09-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/Momentum-Moving-Average-Crossover-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