Corrected Average Breakout Strategy
This strategy trades breakouts relative to a Corrected Average moving average. The indicator smooths the price using a moving average and adjusts the smoothing factor based on the standard deviation of price changes.
When the price closes above the corrected average by a specified number of points and then pulls back to the breakout level, the strategy opens a long position. The opposite logic is used for short trades. Stop-loss and take-profit are applied in absolute price points.
Parameters
Candle Type – timeframe of candles used for calculations.
Length – period for the moving average and standard deviation.
MA Type – type of moving average (SMA, EMA, SMMA, LWMA).
Level Points – breakout distance from the corrected average in price steps.
Stop Loss Points – stop-loss distance from entry price in price steps.
Take Profit Points – take-profit distance from entry price in price steps.
Enable Long – allow opening long positions.
Enable Short – allow opening short positions.
Trading Logic
- Calculate the moving average and standard deviation.
- Build the corrected average using previous values and the ratio of variance to smooth sudden jumps.
- Detect breakouts when the previous bar closes beyond the corrected average plus or minus the configured level.
- After a breakout, wait for the next bar to return to the breakout level and open a position in the direction of the breakout.
- Close opposite positions when a new breakout signal appears.
- Apply stop-loss and take-profit protections.
Notes
This strategy is a conversion from the MQL script Exp_CorrectedAverage.mq5. It is intended for educational purposes and requires further testing before use in live trading.
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 based on the Corrected Average breakout.
/// Monitors price relative to a corrected moving average and trades on breakouts.
/// </summary>
public class CorrectedAverageBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _levelPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private decimal _prevCorrected;
private decimal _prevPrevCorrected;
private decimal _prevClose;
private decimal _prevPrevClose;
private bool _isInitialized;
private decimal _level;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Length { get => _length.Value; set => _length.Value = value; }
public int LevelPoints { get => _levelPoints.Value; set => _levelPoints.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public CorrectedAverageBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_length = Param(nameof(Length), 12)
.SetDisplay("Length", "Period of moving average", "Indicator");
_levelPoints = Param(nameof(LevelPoints), 300)
.SetDisplay("Level Points", "Breakout distance in price steps", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetDisplay("Stop Loss Points", "Stop loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetDisplay("Take Profit Points", "Take profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCorrected = default;
_prevPrevCorrected = default;
_prevClose = default;
_prevPrevClose = default;
_isInitialized = default;
_level = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var step = Security.PriceStep ?? 1m;
_level = LevelPoints * step;
var ma = new ExponentialMovingAverage { Length = Length };
var std = new StandardDeviation { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ma, std, ProcessCandle).Start();
StartProtection(
new Unit(StopLossPoints * step, UnitTypes.Absolute),
new Unit(TakeProfitPoints * step, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal stdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
decimal corrected;
if (!_isInitialized)
{
corrected = maValue;
_isInitialized = true;
}
else
{
var v1 = stdValue * stdValue;
var v2 = (_prevCorrected - maValue) * (_prevCorrected - maValue);
var k = (v2 < v1 || v2 == 0m) ? 0m : 1m - (v1 / v2);
corrected = _prevCorrected + k * (maValue - _prevCorrected);
}
var buySignal = _prevPrevClose > _prevPrevCorrected + _level && _prevClose <= _prevCorrected + _level;
var sellSignal = _prevPrevClose < _prevPrevCorrected - _level && _prevClose >= _prevCorrected - _level;
if (buySignal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (sellSignal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevPrevCorrected = _prevCorrected;
_prevPrevClose = _prevClose;
_prevCorrected = corrected;
_prevClose = candle.ClosePrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class corrected_average_breakout_strategy(Strategy):
"""
Strategy based on the Corrected Average breakout.
Monitors price relative to a corrected moving average and trades on breakouts.
"""
def __init__(self):
super(corrected_average_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for calculations", "General")
self._length = self.Param("Length", 12) \
.SetDisplay("Length", "Period of moving average", "Indicator")
self._level_points = self.Param("LevelPoints", 300) \
.SetDisplay("Level Points", "Breakout distance in price steps", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 1000) \
.SetDisplay("Stop Loss Points", "Stop loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000) \
.SetDisplay("Take Profit Points", "Take profit in price steps", "Risk")
self._prev_corrected = 0.0
self._prev_prev_corrected = 0.0
self._prev_close = 0.0
self._prev_prev_close = 0.0
self._is_initialized = False
self._level = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(corrected_average_breakout_strategy, self).OnReseted()
self._prev_corrected = 0.0
self._prev_prev_corrected = 0.0
self._prev_close = 0.0
self._prev_prev_close = 0.0
self._is_initialized = False
self._level = 0.0
def OnStarted2(self, time):
super(corrected_average_breakout_strategy, self).OnStarted2(time)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
self._level = self._level_points.Value * step
ma = ExponentialMovingAverage()
ma.Length = self._length.Value
std = StandardDeviation()
std.Length = self._length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, std, self.on_process).Start()
sl_dist = self._stop_loss_points.Value * step
tp_dist = self._take_profit_points.Value * step
self.StartProtection(
Unit(float(tp_dist), UnitTypes.Absolute),
Unit(float(sl_dist), UnitTypes.Absolute)
)
def on_process(self, candle, ma_val, std_val):
if candle.State != CandleStates.Finished:
return
ma_val = float(ma_val)
std_val = float(std_val)
if not self._is_initialized:
corrected = ma_val
self._is_initialized = True
else:
v1 = std_val * std_val
diff = self._prev_corrected - ma_val
v2 = diff * diff
if v2 < v1 or v2 == 0:
k = 0.0
else:
k = 1.0 - (v1 / v2)
corrected = self._prev_corrected + k * (ma_val - self._prev_corrected)
buy_signal = (self._prev_prev_close > self._prev_prev_corrected + self._level and
self._prev_close <= self._prev_corrected + self._level)
sell_signal = (self._prev_prev_close < self._prev_prev_corrected - self._level and
self._prev_close >= self._prev_corrected - self._level)
if buy_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif sell_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_prev_corrected = self._prev_corrected
self._prev_prev_close = self._prev_close
self._prev_corrected = corrected
self._prev_close = float(candle.ClosePrice)
def CreateClone(self):
return corrected_average_breakout_strategy()