Estrategia de canal promedio corregida
Descripción general
La Estrategia de canal promedio corregida es una adaptación de C# del asesor experto MetaTrader e-CA-5. El sistema reconstruye el indicador "Promedio Corregido" (CA) cada vez que se cierra una vela y abre una posición cuando el precio cruza el promedio móvil corregido por un desplazamiento sigma configurable. La implementación convertida se basa en la vela de alto nivel API de StockSharp, utiliza órdenes de mercado y gestiona salidas protectoras (stop-loss, take-profit, trailing stop) internamente para reflejar el comportamiento del Asesor Experto original.
Indicador de promedio corregido
El filtro CA combina una media móvil con retroalimentación de volatilidad. La versión MQL expone tres entradas: longitud del promedio móvil, método de promedio y precio aplicado. En el puerto StockSharp:
- El tipo de media móvil se selecciona mediante el parámetro
MaTypeOption (SMA, EMA, SMMA, LWMA) y la longitud MaPeriod.
- Un indicador
StandardDeviation con el mismo período mide la volatilidad actual.
- Para cada vela terminada, el valor corregido se calcula de forma iterativa:
- Sea
M_t el valor MA en la última barra y CA_{t-1} el valor corregido de la barra anterior.
- Calcule
v1 = StdDev_t^2 y v2 = (CA_{t-1} - M_t)^2.
- Si es
v2 <= 0 o v2 < v1, mantenga el factor de corrección k = 0. De lo contrario, configure k = 1 - v1 / v2.
- Actualización
CA_t = CA_{t-1} + k * (M_t - CA_{t-1}).
- El primer valor corregido por defecto es la propia media móvil.
Este circuito de retroalimentación amortigua la MA durante los períodos de calma y permite ajustes rápidos cuando el precio diverge más allá de la estimación de volatilidad actual.
Lógica comercial
- La estrategia se suscribe al tipo de vela configurado (
CandleType) y espera hasta que tanto la media móvil como la desviación estándar estén completamente formadas.
- Una vez que termina una vela, el algoritmo calcula el nuevo valor corregido y compara el cierre de la vela anterior con el nivel corregido anterior.
- Dos compensaciones sigma,
SigmaBuyPoints y SigmaSellPoints, se convierten en distancias de precios utilizando el PriceStep del instrumento.
- Las reglas de entrada utilizan el cierre de la vela anterior y el nivel corregido recién calculado:
- Comprar si el cierre anterior estuvo por debajo del promedio corregido más la sigma de compra, y el cierre actual termina por encima de ese límite superior.
- Vender si el cierre anterior estuvo por encima del promedio corregido menos el sigma de venta y el cierre actual termina por debajo de ese límite inferior.
- Sólo se permite una posición neta. Se envía una nueva operación solo cuando no hay exposición presente.
Debido a que la versión StockSharp opera en velas terminadas, la confirmación de ruptura ocurre una vez por barra en lugar de cada tick, lo que proporciona un comportamiento determinista adecuado para pruebas retrospectivas y automatización en vivo con datos de velas.
Gestión de riesgos
El puerto reproduce los tres mecanismos de protección del Asesor Experto original:
- Stop-loss fijo:
StopLossPoints multiplicado por el paso del precio define la distancia entre el precio de entrada y el stop de protección. Un stop activado cierra toda la posición con una orden de mercado.
- Obtención de ganancias fija:
TakeProfitPoints se convierte en una distancia objetivo de ganancias. Cuando el precio alcanza el nivel durante una vela, la posición se cierra con una orden de mercado.
- Tope dinámico: cuando
TrailingPoints es mayor que cero, la estrategia rastrea las ganancias no realizadas y, una vez que el precio ha avanzado al menos esa distancia, almacena un nivel dinámico detrás del último cierre. El trailing stop solo avanza y respeta TrailingStepPoints, lo que representa la mejora mínima antes de que se acepte un nuevo nivel de seguimiento. Los niveles finales se redondean con Security.ShrinkPrice para que se alineen con el tamaño de tick del instrumento.
Todas las salidas restablecen el estado de riesgo interno. Cuando aparece la siguiente señal, los niveles de parada, objetivo y seguimiento se recalculan a partir del nuevo precio de ejecución, lo que garantiza un comportamiento cercano a la versión MQL que modifica las protecciones de la orden original.
Parámetros
| Parámetro |
Descripción |
OrderVolume |
Cantidad utilizada para las entradas al mercado. Debe ser positivo. |
TakeProfitPoints |
Objetivo de beneficio en pasos de precio (0 desactiva la toma de beneficios). |
StopLossPoints |
Distancia del stop-loss en pasos de precio (0 desactiva el stop-loss). |
TrailingPoints |
Distancia de beneficio (en pasos de precio) requerida antes de que se active el trailing stop. |
TrailingStepPoints |
Distancia mínima extra que se debe capturar antes de volver a mover el trailing stop. |
MaPeriod |
Período tanto de la media móvil como de la desviación estándar. |
MaTypeOption |
Tipo de media móvil: SMA, EMA, SMMA o LWMA. |
SigmaBuyPoints |
La compensación de Sigma se agregó por encima del promedio corregido antes de abrir una posición larga. |
SigmaSellPoints |
El desplazamiento de Sigma se restó por debajo del promedio corregido antes de abrir una posición corta. |
CandleType |
Serie de velas utilizadas para cálculos de indicadores y evaluación de señales. |
Todos los parámetros numéricos admiten la optimización a través de SetCanOptimize(true), por lo que la estrategia se puede calibrar directamente dentro del entorno StockSharp.
Notas de uso
- El tipo de vela predeterminado es de una hora. Ajústelo para que coincida con el período de tiempo que se utilizó al optimizar la estrategia MetaTrader original.
Security.PriceStep se utiliza para traducir todas las entradas de "puntos" a distancias de precios reales. Los instrumentos sin un paso configurado vuelven a 1, preservando el comportamiento sensato para índices o criptomonedas.
- La estrategia se ejecuta sólo en velas terminadas. Si se requiere precisión intrabarra, reduzca el período de tiempo a la granularidad deseada.
- Los trailingstops se implementan con órdenes de mercado cuando se violan, imitando el EA original que modificó los precios de stop-loss. Este enfoque evita colocar órdenes stop adicionales y mantiene la gestión de riesgos contenida dentro de la propia estrategia.
- No se proporciona ninguna versión de Python para esta conversión, según los requisitos de la tarea.
Diferencias con el EA original
- El API basado en velas de StockSharp reemplaza el procesamiento a nivel de tick; Todas las decisiones se toman cuando se cierra una vela.
- La gestión de órdenes se compensa: las posiciones opuestas no se mantienen simultáneamente, lo que coincide con la lógica de orden única de la versión MetaTrader.
- Las paradas protectoras y las salidas dinámicas se ejecutan mediante órdenes de mercado en lugar de modificar los tickets de órdenes existentes. Este comportamiento es equivalente en la compensación de cuentas y al mismo tiempo mantiene la implementación coherente con otras estrategias StockSharp.
Estas adaptaciones preservan la idea comercial de e-CA-5 al tiempo que alinean la lógica con las mejores prácticas de StockSharp y las convenciones de alto nivel API descritas en las pautas del repositorio.
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()