MA Deviation
Strategy that trades when price deviates significantly from its moving average
Testing indicates an average annual return of about 124%. It performs best in the forex market.
MA Deviation enters when price deviates a set percentage from its moving average, anticipating a return to the mean. The position is exited when price converges back toward the average.
Deviation thresholds can be widened or narrowed depending on volatility. Using ATR for position sizing keeps risk consistent across markets.
Details
- Entry Criteria: Signals based on MA, ATR.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or stop.
- Stops: Yes.
- Default Values:
MAPeriod= 20DeviationPercent= 5mAtrPeriod= 14AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MA, ATR
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades when price deviates significantly from its moving average.
/// Opens positions when price deviates by a specified percentage from MA
/// and closes when price returns to MA.
/// </summary>
public class MADeviationStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _deviationPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
/// <summary>
/// Period for Moving Average calculation.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Deviation percentage from MA required for entry.
/// </summary>
public decimal DeviationPercent
{
get => _deviationPercent.Value;
set => _deviationPercent.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize the MA Deviation strategy.
/// </summary>
public MADeviationStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_deviationPercent = Param(nameof(DeviationPercent), 2m)
.SetDisplay("Deviation %", "Deviation percentage from MA required for entry", "Entry")
.SetOptimize(1m, 10m, 1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (maValue == 0)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var deviation = (candle.ClosePrice - maValue) / maValue * 100;
if (Position == 0)
{
// Price far below MA -> buy (expect reversion up)
if (deviation < -DeviationPercent)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Price far above MA -> sell (expect reversion down)
else if (deviation > DeviationPercent)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0)
{
// Exit long when price returns to or above MA
if (candle.ClosePrice >= maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
// Exit short when price returns to or below MA
if (candle.ClosePrice <= maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_deviation_strategy(Strategy):
"""
MA Deviation strategy.
Trades when price deviates significantly from its moving average.
"""
def __init__(self):
super(ma_deviation_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
self._deviation_percent = self.Param("DeviationPercent", 2.0).SetDisplay("Deviation %", "Deviation percentage from MA required for entry", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_deviation_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(ma_deviation_strategy, self).OnStarted2(time)
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
mv = float(ma_val)
if mv == 0:
return
if self._cooldown > 0:
self._cooldown -= 1
return
close = float(candle.ClosePrice)
deviation = (close - mv) / mv * 100.0
threshold = float(self._deviation_percent.Value)
cd = self._cooldown_bars.Value
if self.Position == 0:
if deviation < -threshold:
self.BuyMarket()
self._cooldown = cd
elif deviation > threshold:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0:
if close >= mv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0:
if close <= mv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return ma_deviation_strategy()