Corrected Average Channel Strategy
Overview
The Corrected Average Channel Strategy is a C# port of the MetaTrader expert advisor e-CA-5. The system rebuilds the "Corrected Average" (CA) indicator every time a candle closes and opens a position when price crosses the corrected moving average by a configurable sigma offset. The converted implementation relies on StockSharp's high-level candle API, uses market orders, and manages protective exits (stop-loss, take-profit, trailing stop) internally to mirror the behaviour of the original Expert Advisor.
Corrected Average indicator
The CA filter combines a moving average with volatility feedback. The MQL version exposes three inputs: moving-average length, averaging method, and applied price. In the StockSharp port:
- The moving average type is selected via the
MaTypeOption parameter (SMA, EMA, SMMA, LWMA) and the MaPeriod length.
- A
StandardDeviation indicator with the same period measures current volatility.
- For each finished candle the corrected value is computed iteratively:
- Let
M_t be the MA value on the latest bar and CA_{t-1} the corrected value from the previous bar.
- Compute
v1 = StdDev_t^2 and v2 = (CA_{t-1} - M_t)^2.
- If
v2 <= 0 or v2 < v1, keep the correction factor k = 0. Otherwise set k = 1 - v1 / v2.
- Update
CA_t = CA_{t-1} + k * (M_t - CA_{t-1}).
- The very first corrected value defaults to the moving average itself.
This feedback loop dampens the MA during quiet periods and allows rapid adjustments when price diverges beyond the current volatility estimate.
Trading logic
- The strategy subscribes to the configured candle type (
CandleType) and waits until both the moving average and the standard deviation are fully formed.
- Once a candle finishes, the algorithm calculates the new corrected value and compares the previous candle close against the previous corrected level.
- Two sigma offsets,
SigmaBuyPoints and SigmaSellPoints, are converted into price distances using the instrument's PriceStep.
- Entry rules use the previous candle close and the freshly computed corrected level:
- Buy if the previous close was below the corrected average plus the buy sigma, and the current close finishes above that upper boundary.
- Sell if the previous close was above the corrected average minus the sell sigma, and the current close finishes below that lower boundary.
- Only one net position is allowed. A new trade is submitted only when no exposure is present.
Because the StockSharp version operates on finished candles, the breakout confirmation happens once per bar instead of on every tick, providing deterministic behaviour suitable for backtesting and live automation with candle data.
Risk management
The port reproduces all three protective mechanics from the original Expert Advisor:
- Fixed stop-loss:
StopLossPoints multiplied by the price step defines the distance between the entry price and the protective stop. A triggered stop closes the entire position with a market order.
- Fixed take-profit:
TakeProfitPoints converts to a profit target distance. When price reaches the level during a candle, the position is closed with a market order.
- Trailing stop: When
TrailingPoints is greater than zero the strategy tracks unrealised profit and, once the price has advanced by at least that distance, stores a trailing level behind the latest close. The trailing stop only moves forward and honours TrailingStepPoints, which represents the minimum improvement before a new trailing level is accepted. Trailing levels are rounded with Security.ShrinkPrice so they align with the instrument's tick size.
All exits reset the internal risk state. When the next signal appears the stop, target, and trailing levels are recalculated from the new fill price, ensuring behaviour close to the MQL version that modifies the original order protections.
Parameters
| Parameter |
Description |
OrderVolume |
Quantity used for market entries. Must be positive. |
TakeProfitPoints |
Profit target in price steps (0 disables the take-profit). |
StopLossPoints |
Stop-loss distance in price steps (0 disables the stop-loss). |
TrailingPoints |
Profit distance (in price steps) required before the trailing stop activates. |
TrailingStepPoints |
Minimum extra distance that must be captured before moving the trailing stop again. |
MaPeriod |
Period of both the moving average and the standard deviation. |
MaTypeOption |
Moving average type: SMA, EMA, SMMA, or LWMA. |
SigmaBuyPoints |
Sigma offset added above the corrected average before opening a long position. |
SigmaSellPoints |
Sigma offset subtracted below the corrected average before opening a short position. |
CandleType |
Candle series used for indicator calculations and signal evaluation. |
All numeric parameters support optimisation through SetCanOptimize(true) so the strategy can be calibrated directly inside the StockSharp environment.
Usage notes
- The default candle type is one hour. Adjust it to match the timeframe that was used when optimising the original MetaTrader strategy.
Security.PriceStep is used to translate all "points" inputs to actual price distances. Instruments without a configured step fall back to 1, preserving sensible behaviour for indexes or cryptocurrencies.
- The strategy executes only on finished candles. If intrabar precision is required, lower the timeframe to the desired granularity.
- Trailing stops are implemented with market orders when violated, mimicking the original EA that modified stop-loss prices. This approach avoids placing additional stop orders and keeps risk management contained within the strategy itself.
- No Python version is provided for this conversion, per the task requirements.
Differences from the original EA
- StockSharp's candle-based API replaces tick-level processing; all decisions are taken when a candle closes.
- Order management is netted: opposing positions are not held simultaneously, matching the single-order logic of the MetaTrader version.
- Protective stops and trailing exits are executed via market orders instead of modifying existing order tickets. This behaviour is equivalent on netting accounts while keeping the implementation consistent with other StockSharp strategies.
These adaptations preserve the trading idea of e-CA-5 while aligning the logic with StockSharp best practices and the high-level API conventions described in the repository guidelines.
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>
/// Port of the MetaTrader expert e-CA-5 that trades breakouts around the Corrected Average indicator.
/// The strategy subscribes to candles, rebuilds the indicator and places market orders when price crosses
/// the corrected moving average by the configured sigma offsets.
/// </summary>
public class CorrectedAverageChannelStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _trailingPoints;
private readonly StrategyParam<int> _trailingStepPoints;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<MaTypes> _maType;
private readonly StrategyParam<int> _sigmaBuyPoints;
private readonly StrategyParam<int> _sigmaSellPoints;
private readonly StrategyParam<DataType> _candleType;
private DecimalLengthIndicator _ma;
private StandardDeviation _std;
private decimal _priceStep;
private decimal _sigmaBuyOffset;
private decimal _sigmaSellOffset;
private decimal _stopLossDistance;
private decimal _takeProfitDistance;
private decimal _trailingDistance;
private decimal _trailingStepDistance;
private decimal? _previousCorrected;
private decimal? _previousClose;
private decimal? _entryPrice;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private decimal _previousPosition;
private decimal? _lastTradePrice;
private Sides? _lastTradeSide;
/// <summary>
/// Order size used for market entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop trigger expressed in price steps.
/// </summary>
public int TrailingPoints
{
get => _trailingPoints.Value;
set => _trailingPoints.Value = value;
}
/// <summary>
/// Minimum increment required to advance the trailing stop in price steps.
/// </summary>
public int TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Moving average period used by the Corrected Average filter.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Moving average type replicated from the MetaTrader input.
/// </summary>
public MaTypes MaTypesOption
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Buy-side sigma expressed in price steps.
/// </summary>
public int SigmaBuyPoints
{
get => _sigmaBuyPoints.Value;
set => _sigmaBuyPoints.Value = value;
}
/// <summary>
/// Sell-side sigma expressed in price steps.
/// </summary>
public int SigmaSellPoints
{
get => _sigmaSellPoints.Value;
set => _sigmaSellPoints.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations and signal evaluation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CorrectedAverageChannelStrategy"/> class.
/// </summary>
public CorrectedAverageChannelStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Market order size used for entries", "Trading")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 60)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Distance from entry to the profit target in price steps", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 40)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Distance from entry to the protective stop in price steps", "Risk")
;
_trailingPoints = Param(nameof(TrailingPoints), 0)
.SetNotNegative()
.SetDisplay("Trailing Trigger (points)", "Profit distance required before the trailing stop activates", "Risk")
;
_trailingStepPoints = Param(nameof(TrailingStepPoints), 0)
.SetNotNegative()
.SetDisplay("Trailing Step (points)", "Minimum advance in price steps before the trailing stop moves", "Risk")
;
_maPeriod = Param(nameof(MaPeriod), 35)
.SetRange(2, 500)
.SetDisplay("MA Period", "Period of the moving average and standard deviation", "Indicator")
;
_maType = Param(nameof(MaTypesOption), MaTypes.Sma)
.SetDisplay("MA Type", "Moving average type used inside the Corrected Average", "Indicator");
_sigmaBuyPoints = Param(nameof(SigmaBuyPoints), 5)
.SetNotNegative()
.SetDisplay("Sigma BUY (points)", "Offset added above the corrected average before buying", "Signal")
;
_sigmaSellPoints = Param(nameof(SigmaSellPoints), 5)
.SetNotNegative()
.SetDisplay("Sigma SELL (points)", "Offset subtracted from the corrected average before selling", "Signal")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for calculations", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = null;
_std = null;
_priceStep = 0m;
_sigmaBuyOffset = 0m;
_sigmaSellOffset = 0m;
_stopLossDistance = 0m;
_takeProfitDistance = 0m;
_trailingDistance = 0m;
_trailingStepDistance = 0m;
_previousCorrected = null;
_previousClose = null;
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_previousPosition = 0m;
_lastTradePrice = null;
_lastTradeSide = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = CreateMa(MaTypesOption, MaPeriod);
_std = new StandardDeviation
{
Length = MaPeriod
};
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
{
_priceStep = 1m;
}
_sigmaBuyOffset = GetPriceOffset(SigmaBuyPoints);
_sigmaSellOffset = GetPriceOffset(SigmaSellPoints);
_stopLossDistance = GetPriceOffset(StopLossPoints);
_takeProfitDistance = GetPriceOffset(TakeProfitPoints);
_trailingDistance = GetPriceOffset(TrailingPoints);
_trailingStepDistance = GetPriceOffset(TrailingStepPoints);
Volume = OrderVolume;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_ma, _std, ProcessCandle).Start();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Trade != null)
{
_lastTradePrice = trade.Trade.Price;
}
_lastTradeSide = trade.Order.Side;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (_previousPosition == 0m && Position != 0m)
{
var entryPrice = _lastTradePrice ?? _previousClose;
if (entryPrice is decimal price)
{
if (Position > 0m && _lastTradeSide == Sides.Buy)
{
InitializeRiskState(price, true);
}
else if (Position < 0m && _lastTradeSide == Sides.Sell)
{
InitializeRiskState(price, false);
}
}
}
else if (Position == 0m && _previousPosition != 0m)
{
ResetRiskState();
}
_previousPosition = Position;
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal stdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_ma is null || _std is null)
return;
if (!_ma.IsFormed || !_std.IsFormed)
{
_previousCorrected = maValue;
_previousClose = candle.ClosePrice;
return;
}
var previousCorrected = _previousCorrected;
var previousClose = _previousClose;
decimal corrected;
if (previousCorrected is not decimal prevCorrected)
{
corrected = maValue;
}
else
{
var diff = prevCorrected - maValue;
var v2 = diff * diff;
var v1 = stdValue * stdValue;
var k = (v2 <= 0m || v2 < v1) ? 0m : 1m - (v1 / v2);
corrected = prevCorrected + k * (maValue - prevCorrected);
}
if (HandleTrailing(candle))
{
_previousCorrected = corrected;
_previousClose = candle.ClosePrice;
return;
}
if (HandleRiskExit(candle))
{
_previousCorrected = corrected;
_previousClose = candle.ClosePrice;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousCorrected = corrected;
_previousClose = candle.ClosePrice;
return;
}
if (Position == 0m && previousCorrected is decimal prevCorr && previousClose is decimal prevCls)
{
var buyThreshold = corrected + _sigmaBuyOffset;
var sellThreshold = corrected - _sigmaSellOffset;
var buySignal = prevCls < prevCorr + _sigmaBuyOffset && candle.ClosePrice >= buyThreshold;
var sellSignal = prevCls > prevCorr - _sigmaSellOffset && candle.ClosePrice <= sellThreshold;
if (buySignal)
{
BuyMarket();
}
else if (sellSignal)
{
SellMarket();
}
}
_previousCorrected = corrected;
_previousClose = candle.ClosePrice;
}
private bool HandleTrailing(ICandleMessage candle)
{
if (_trailingDistance <= 0m || _entryPrice is null)
return false;
var volume = Math.Abs(Position);
if (volume <= 0m)
return false;
if (Position > 0m)
{
var moved = candle.ClosePrice - _entryPrice.Value;
if (moved > _trailingDistance)
{
var candidate = candle.ClosePrice - _trailingDistance;
if (_longTrailingStop is null || candidate - _longTrailingStop.Value >= _trailingStepDistance)
{
_longTrailingStop = Security?.ShrinkPrice(candidate) ?? candidate;
}
}
if (_longTrailingStop is decimal trailing && candle.LowPrice <= trailing)
{
SellMarket(volume);
ResetRiskState();
return true;
}
}
else if (Position < 0m)
{
var moved = _entryPrice.Value - candle.ClosePrice;
if (moved > _trailingDistance)
{
var candidate = candle.ClosePrice + _trailingDistance;
if (_shortTrailingStop is null || _shortTrailingStop.Value - candidate >= _trailingStepDistance)
{
_shortTrailingStop = Security?.ShrinkPrice(candidate) ?? candidate;
}
}
if (_shortTrailingStop is decimal trailing && candle.HighPrice >= trailing)
{
BuyMarket(volume);
ResetRiskState();
return true;
}
}
return false;
}
private bool HandleRiskExit(ICandleMessage candle)
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return false;
if (Position > 0m)
{
if (_stopLossPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(volume);
ResetRiskState();
return true;
}
if (_takeProfitPrice is decimal target && candle.HighPrice >= target)
{
SellMarket(volume);
ResetRiskState();
return true;
}
}
else if (Position < 0m)
{
if (_stopLossPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(volume);
ResetRiskState();
return true;
}
if (_takeProfitPrice is decimal target && candle.LowPrice <= target)
{
BuyMarket(volume);
ResetRiskState();
return true;
}
}
return false;
}
private void InitializeRiskState(decimal entryPrice, bool isLong)
{
_entryPrice = entryPrice;
_stopLossPrice = null;
_takeProfitPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
if (_stopLossDistance > 0m)
{
var rawPrice = isLong ? entryPrice - _stopLossDistance : entryPrice + _stopLossDistance;
_stopLossPrice = Security?.ShrinkPrice(rawPrice) ?? rawPrice;
}
if (_takeProfitDistance > 0m)
{
var rawPrice = isLong ? entryPrice + _takeProfitDistance : entryPrice - _takeProfitDistance;
_takeProfitPrice = Security?.ShrinkPrice(rawPrice) ?? rawPrice;
}
}
private void ResetRiskState()
{
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
}
private decimal GetPriceOffset(int points)
{
if (points <= 0 || _priceStep <= 0m)
return 0m;
return points * _priceStep;
}
private static DecimalLengthIndicator CreateMa(MaTypes type, int length)
{
return type switch
{
MaTypes.Sma => new SMA { Length = length },
MaTypes.Ema => new EMA { Length = length },
MaTypes.Smma => new SmoothedMovingAverage { Length = length },
MaTypes.Lwma => new WeightedMovingAverage { Length = length },
_ => throw new ArgumentOutOfRangeException(nameof(type))
};
}
/// <summary>
/// Supported moving average types.
/// </summary>
public enum MaTypes
{
Sma,
Ema,
Smma,
Lwma
}
}
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, Sides
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SimpleMovingAverage,
ExponentialMovingAverage,
SmoothedMovingAverage,
WeightedMovingAverage,
StandardDeviation,
)
class corrected_average_channel_strategy(Strategy):
def __init__(self):
super(corrected_average_channel_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetDisplay("Order Volume", "Market order size used for entries", "Trading")
self._take_profit_points = self.Param("TakeProfitPoints", 60) \
.SetDisplay("Take Profit (points)", "Distance from entry to the profit target in price steps", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 40) \
.SetDisplay("Stop Loss (points)", "Distance from entry to the protective stop in price steps", "Risk")
self._trailing_points = self.Param("TrailingPoints", 0) \
.SetDisplay("Trailing Trigger (points)", "Profit distance required before the trailing stop activates", "Risk")
self._trailing_step_points = self.Param("TrailingStepPoints", 0) \
.SetDisplay("Trailing Step (points)", "Minimum advance in price steps before the trailing stop moves", "Risk")
self._ma_period = self.Param("MaPeriod", 35) \
.SetDisplay("MA Period", "Period of the moving average and standard deviation", "Indicator")
self._ma_type = self.Param("MaType", 0) \
.SetDisplay("MA Type", "0=SMA, 1=EMA, 2=SMMA, 3=LWMA", "Indicator")
self._sigma_buy_points = self.Param("SigmaBuyPoints", 5) \
.SetDisplay("Sigma BUY (points)", "Offset added above the corrected average before buying", "Signal")
self._sigma_sell_points = self.Param("SigmaSellPoints", 5) \
.SetDisplay("Sigma SELL (points)", "Offset subtracted from the corrected average before selling", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe used for calculations", "Data")
self._ma = None
self._std = None
self._price_step = 0.0
self._sigma_buy_offset = 0.0
self._sigma_sell_offset = 0.0
self._stop_loss_distance = 0.0
self._take_profit_distance = 0.0
self._trailing_distance = 0.0
self._trailing_step_distance = 0.0
self._previous_corrected = None
self._previous_close = None
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
self._last_trade_side = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingPoints(self):
return self._trailing_points.Value
@property
def TrailingStepPoints(self):
return self._trailing_step_points.Value
@property
def MaPeriod(self):
return self._ma_period.Value
@property
def MaType(self):
return self._ma_type.Value
@property
def SigmaBuyPoints(self):
return self._sigma_buy_points.Value
@property
def SigmaSellPoints(self):
return self._sigma_sell_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def _create_ma(self, ma_type, length):
if ma_type == 1:
ind = ExponentialMovingAverage()
elif ma_type == 2:
ind = SmoothedMovingAverage()
elif ma_type == 3:
ind = WeightedMovingAverage()
else:
ind = SimpleMovingAverage()
ind.Length = length
return ind
def _get_price_offset(self, points):
pts = int(points)
if pts <= 0 or self._price_step <= 0:
return 0.0
return pts * self._price_step
def OnStarted2(self, time):
super(corrected_average_channel_strategy, self).OnStarted2(time)
self._ma = self._create_ma(self.MaType, self.MaPeriod)
self._std = StandardDeviation()
self._std.Length = self.MaPeriod
self._price_step = 0.0
if self.Security is not None and self.Security.PriceStep is not None:
self._price_step = float(self.Security.PriceStep)
if self._price_step <= 0:
self._price_step = 1.0
self._sigma_buy_offset = self._get_price_offset(self.SigmaBuyPoints)
self._sigma_sell_offset = self._get_price_offset(self.SigmaSellPoints)
self._stop_loss_distance = self._get_price_offset(self.StopLossPoints)
self._take_profit_distance = self._get_price_offset(self.TakeProfitPoints)
self._trailing_distance = self._get_price_offset(self.TrailingPoints)
self._trailing_step_distance = self._get_price_offset(self.TrailingStepPoints)
self.Volume = float(self.OrderVolume)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ma, self._std, self.ProcessCandle).Start()
def OnOwnTradeReceived(self, trade):
super(corrected_average_channel_strategy, self).OnOwnTradeReceived(trade)
if trade is None or trade.Order is None:
return
if trade.Trade is not None:
self._last_trade_price = float(trade.Trade.Price)
self._last_trade_side = trade.Order.Side
prev_pos = self._previous_position
cur_pos = self.Position
if prev_pos == 0 and cur_pos != 0:
entry_price = self._last_trade_price if self._last_trade_price is not None else self._previous_close
if entry_price is not None:
if cur_pos > 0 and self._last_trade_side == Sides.Buy:
self._initialize_risk_state(entry_price, True)
elif cur_pos < 0 and self._last_trade_side == Sides.Sell:
self._initialize_risk_state(entry_price, False)
elif cur_pos == 0 and prev_pos != 0:
self._reset_risk_state()
self._previous_position = cur_pos
def ProcessCandle(self, candle, ma_value, std_value):
if candle.State != CandleStates.Finished:
return
ma_value = float(ma_value)
std_value = float(std_value)
if self._ma is None or self._std is None:
return
if not self._ma.IsFormed or not self._std.IsFormed:
self._previous_corrected = ma_value
self._previous_close = float(candle.ClosePrice)
return
previous_corrected = self._previous_corrected
previous_close = self._previous_close
if previous_corrected is None:
corrected = ma_value
else:
diff = previous_corrected - ma_value
v2 = diff * diff
v1 = std_value * std_value
if v2 <= 0 or v2 < v1:
k = 0.0
else:
k = 1.0 - (v1 / v2)
corrected = previous_corrected + k * (ma_value - previous_corrected)
if self._handle_trailing(candle):
self._previous_corrected = corrected
self._previous_close = float(candle.ClosePrice)
return
if self._handle_risk_exit(candle):
self._previous_corrected = corrected
self._previous_close = float(candle.ClosePrice)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._previous_corrected = corrected
self._previous_close = float(candle.ClosePrice)
return
if self.Position == 0 and previous_corrected is not None and previous_close is not None:
buy_threshold = corrected + self._sigma_buy_offset
sell_threshold = corrected - self._sigma_sell_offset
close_price = float(candle.ClosePrice)
buy_signal = previous_close < previous_corrected + self._sigma_buy_offset and close_price >= buy_threshold
sell_signal = previous_close > previous_corrected - self._sigma_sell_offset and close_price <= sell_threshold
if buy_signal:
self.BuyMarket()
elif sell_signal:
self.SellMarket()
self._previous_corrected = corrected
self._previous_close = float(candle.ClosePrice)
def _handle_trailing(self, candle):
if self._trailing_distance <= 0 or self._entry_price is None:
return False
volume = abs(self.Position)
if volume <= 0:
return False
close_price = float(candle.ClosePrice)
if self.Position > 0:
moved = close_price - self._entry_price
if moved > self._trailing_distance:
candidate = close_price - self._trailing_distance
if self._long_trailing_stop is None or candidate - self._long_trailing_stop >= self._trailing_step_distance:
self._long_trailing_stop = candidate
if self._long_trailing_stop is not None and float(candle.LowPrice) <= self._long_trailing_stop:
self.SellMarket(volume)
self._reset_risk_state()
return True
elif self.Position < 0:
moved = self._entry_price - close_price
if moved > self._trailing_distance:
candidate = close_price + self._trailing_distance
if self._short_trailing_stop is None or self._short_trailing_stop - candidate >= self._trailing_step_distance:
self._short_trailing_stop = candidate
if self._short_trailing_stop is not None and float(candle.HighPrice) >= self._short_trailing_stop:
self.BuyMarket(volume)
self._reset_risk_state()
return True
return False
def _handle_risk_exit(self, candle):
volume = abs(self.Position)
if volume <= 0:
return False
if self.Position > 0:
if self._stop_loss_price is not None and float(candle.LowPrice) <= self._stop_loss_price:
self.SellMarket(volume)
self._reset_risk_state()
return True
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(volume)
self._reset_risk_state()
return True
elif self.Position < 0:
if self._stop_loss_price is not None and float(candle.HighPrice) >= self._stop_loss_price:
self.BuyMarket(volume)
self._reset_risk_state()
return True
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(volume)
self._reset_risk_state()
return True
return False
def _initialize_risk_state(self, entry_price, is_long):
self._entry_price = entry_price
self._stop_loss_price = None
self._take_profit_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
if self._stop_loss_distance > 0:
if is_long:
self._stop_loss_price = entry_price - self._stop_loss_distance
else:
self._stop_loss_price = entry_price + self._stop_loss_distance
if self._take_profit_distance > 0:
if is_long:
self._take_profit_price = entry_price + self._take_profit_distance
else:
self._take_profit_price = entry_price - self._take_profit_distance
def _reset_risk_state(self):
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
def OnReseted(self):
super(corrected_average_channel_strategy, self).OnReseted()
self._ma = None
self._std = None
self._price_step = 0.0
self._sigma_buy_offset = 0.0
self._sigma_sell_offset = 0.0
self._stop_loss_distance = 0.0
self._take_profit_distance = 0.0
self._trailing_distance = 0.0
self._trailing_step_distance = 0.0
self._previous_corrected = None
self._previous_close = None
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
self._last_trade_side = None
def CreateClone(self):
return corrected_average_channel_strategy()