Smoothing Average Strategy
Overview
The strategy trades around a simple moving average (SMA) with an additional smoothing offset. It attempts to exploit price deviations from the moving average by entering positions when the close price crosses an offset distance from the average.
How It Works
- Calculate an SMA of the chosen candle type.
- If there is no open position:
- Enter a short position when the close price is below
SMA + Smoothing. - Enter a long position when the close price is above
SMA - Smoothing.
- Enter a short position when the close price is below
- For an open short position:
- Close the position when the close price rises above
SMA + Smoothing.
- Close the position when the close price rises above
- For an open long position:
- Close the position when the close price falls below
SMA - Smoothing.
- Close the position when the close price falls below
The strategy uses market orders and works with finished candles only.
Parameters
- MA Period – lookback period for the SMA.
- Smoothing – price offset added or subtracted from the SMA when generating signals.
- Candle Type – timeframe of candles used for calculations.
Notes
This conversion is based on the original MQL4 script smoothingaverage.mq4.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving average strategy with smoothing offset.
/// </summary>
public class SmoothingAverageStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _smoothing;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
public decimal Smoothing
{
get => _smoothing.Value;
set => _smoothing.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public SmoothingAverageStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 200)
.SetDisplay("MA Period", "Moving average period", "MA")
;
_smoothing = Param(nameof(Smoothing), 1400m)
.SetDisplay("Smoothing", "Price offset from moving average", "General")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 36)
.SetDisplay("Cooldown Bars", "Bars to wait between new signals", "General")
;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// create moving average indicator
var sma = new SimpleMovingAverage { Length = MaPeriod };
// subscribe to candles
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 (_cooldown > 0)
{
_cooldown--;
return;
}
var price = candle.ClosePrice;
var offset = (Security.PriceStep ?? 1m) * Smoothing;
if (Position == 0)
{
if (price >= maValue + offset)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (price <= maValue - offset)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
else if (Position < 0 && price <= maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && price >= maValue)
{
SellMarket();
_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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class smoothing_average_strategy(Strategy):
def __init__(self):
super(smoothing_average_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 200) \
.SetDisplay("MA Period", "Moving average period", "MA")
self._smoothing = self.Param("Smoothing", 1400.0) \
.SetDisplay("Smoothing", "Price offset from moving average", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 36) \
.SetDisplay("Cooldown Bars", "Bars to wait between new signals", "General")
self._cooldown = 0
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def Smoothing(self):
return self._smoothing.Value
@Smoothing.setter
def Smoothing(self, value):
self._smoothing.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
def OnStarted2(self, time):
super(smoothing_average_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self.MaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(sma, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
price = float(candle.ClosePrice)
ma_val = float(ma_value)
step_raw = self.Security.PriceStep
step = float(step_raw) if step_raw is not None else 1.0
offset = step * float(self.Smoothing)
if self.Position == 0:
if price >= ma_val + offset:
self.SellMarket()
self._cooldown = self.CooldownBars
elif price <= ma_val - offset:
self.BuyMarket()
self._cooldown = self.CooldownBars
elif self.Position < 0 and price <= ma_val:
self.BuyMarket()
self._cooldown = self.CooldownBars
elif self.Position > 0 and price >= ma_val:
self.SellMarket()
self._cooldown = self.CooldownBars
def OnReseted(self):
super(smoothing_average_strategy, self).OnReseted()
self._cooldown = 0
def CreateClone(self):
return smoothing_average_strategy()