Estrategia de Cruce de Canales con Envolvente
Descripción general
La estrategia de Cruce de Canales con Envolvente es un port directo del asesor experto de MetaTrader "Channels". El sistema opera velas horarias y monitorea una media móvil exponencial (EMA) rápida de dos períodos en relación con tres envolventes basadas en EMA (desviaciones de 0.3%, 0.7% y 1.0%) que se calculan a partir de una EMA lenta de 220 períodos. Las rupturas de la EMA rápida a través de estas envolventes generan entradas direccionales, mientras que un filtro de tiempo opcional restringe el trading a horas específicas.
Lógica de trading
- Pila de indicadores
- EMA rápida (longitud 2) calculada sobre precios de cierre de vela.
- EMA rápida (longitud 2) calculada sobre precios de apertura de vela.
- EMA lenta (longitud 220) calculada sobre precios de cierre de vela.
- Tres niveles de envolvente derivados de la EMA lenta con desviaciones de 0.3%, 0.7% y 1.0%.
- Configuración larga
- Se activa cuando la EMA rápida de cierre cruza por encima de la envolvente inferior del 1.0% o 0.7%, permanece por debajo de la envolvente inferior del 0.3% durante dos barras consecutivas, cruza por encima de la EMA lenta, o rompe las envolventes superiores del 0.3% o 0.7%. Cualquiera de estas condiciones puede activar una entrada larga cuando no hay posición abierta.
- Configuración corta
- Se activa cuando la EMA rápida de apertura cruza por debajo de cualquiera de las envolventes superiores, cae por debajo de la EMA lenta, o perfora las envolventes inferiores desde arriba. Cualquiera de estas condiciones puede activar una entrada corta cuando no hay posición abierta.
- Gestión de riesgos
- Los niveles fijos de stop-loss y take-profit (por lado) se expresan en pips y se convierten a distancia de precio usando el tamaño de tick del instrumento. Si las entradas se establecen en cero, el nivel respectivo no se aplica.
- Los trailing stops independientes para posiciones largas y cortas mueven el stop de protección más cerca del precio de mercado cuando la ganancia supera la distancia de trailing más un incremento de paso configurable.
- Filtro de tiempo
- Cuando está habilitado, la estrategia solo procesa entradas durante el rango de horas inclusivo configurado. Las posiciones todavía se gestionan cuando el filtro está activo.
Parámetros
| Parámetro | Descripción |
|---|---|
OrderVolume |
Tamaño de la orden usado para entradas de mercado (lotes o contratos dependiendo del instrumento). |
UseTradeHours |
Habilita el filtro de tiempo para las entradas. |
FromHour / ToHour |
Horas de inicio y fin inclusivas para la ventana de trading (admite rangos nocturnos). |
StopLossBuyPips / StopLossSellPips |
Distancia del stop-loss para operaciones largas/cortas expresada en pips. |
TakeProfitBuyPips / TakeProfitSellPips |
Distancia del take-profit para operaciones largas/cortas expresada en pips. |
TrailingStopBuyPips / TrailingStopSellPips |
Distancia del trailing stop en pips para operaciones largas/cortas. |
TrailingStepPips |
Incremento mínimo (en pips) requerido para mover un trailing stop. |
CandleType |
Serie de velas usada para cálculos (por defecto marco temporal de 1 hora). |
Gestión de posiciones
- En la entrada, la estrategia almacena el precio de ejecución, calcula los objetivos de stop-loss y take-profit en unidades de precio absoluto y restablece los niveles de trailing.
- Mientras una posición larga está abierta, el stop-loss se sigue hacia arriba cada vez que la ganancia supera
TrailingStopBuyPips + TrailingStepPips. La estrategia sale en el stop-loss o take-profit, el que se alcance primero. - Mientras una posición corta está abierta, el stop-loss se sigue hacia abajo usando los parámetros de trailing del lado corto y las salidas se ejecutan simétricamente.
Notas
- El tamaño del pip se deriva del tamaño del tick del instrumento. Para instrumentos de tres o cinco decimales, el pip se multiplica por diez para emular la lógica de MetaTrader.
- La estrategia trabaja con una sola posición a la vez. Una nueva entrada solo se coloca después de que la posición existente se haya cerrado.
- Habilite
StartProtectionen la clase base para protegerse contra posiciones abiertas inesperadas después de reinicios (ya llamado en la implementación).
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>
/// Channels envelope crossover strategy converted from the MetaTrader Channels expert advisor.
/// The strategy monitors EMA based envelopes on hourly candles and trades breakouts of the fast EMA through the bands.
/// </summary>
public class ChannelsEnvelopeCrossStrategy : Strategy
{
private readonly StrategyParam<decimal> _envelope003;
private readonly StrategyParam<decimal> _envelope007;
private readonly StrategyParam<decimal> _envelope010;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<bool> _useTradeHours;
private readonly StrategyParam<int> _fromHour;
private readonly StrategyParam<int> _toHour;
private readonly StrategyParam<int> _stopLossBuyPips;
private readonly StrategyParam<int> _stopLossSellPips;
private readonly StrategyParam<int> _takeProfitBuyPips;
private readonly StrategyParam<int> _takeProfitSellPips;
private readonly StrategyParam<int> _trailingStopBuyPips;
private readonly StrategyParam<int> _trailingStopSellPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaFastClose;
private ExponentialMovingAverage _emaFastOpen;
private ExponentialMovingAverage _emaSlow;
private bool _hasPreviousValues;
private decimal _prevFastClose;
private decimal _prevFastOpen;
private decimal _prevSlow;
private decimal _prevEnvLower03;
private decimal _prevEnvUpper03;
private decimal _prevEnvLower07;
private decimal _prevEnvUpper07;
private decimal _prevEnvLower10;
private decimal _prevEnvUpper10;
private decimal? _entryPrice;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
/// <summary>
/// Order volume used for market entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Enable trading only within the configured time window.
/// </summary>
public bool UseTradeHours
{
get => _useTradeHours.Value;
set => _useTradeHours.Value = value;
}
/// <summary>
/// Start hour of the trading window (inclusive).
/// </summary>
public int FromHour
{
get => _fromHour.Value;
set => _fromHour.Value = value;
}
/// <summary>
/// End hour of the trading window (inclusive).
/// </summary>
public int ToHour
{
get => _toHour.Value;
set => _toHour.Value = value;
}
/// <summary>
/// Stop-loss distance for long positions expressed in pips.
/// </summary>
public int StopLossBuyPips
{
get => _stopLossBuyPips.Value;
set => _stopLossBuyPips.Value = value;
}
/// <summary>
/// Stop-loss distance for short positions expressed in pips.
/// </summary>
public int StopLossSellPips
{
get => _stopLossSellPips.Value;
set => _stopLossSellPips.Value = value;
}
/// <summary>
/// Take-profit distance for long positions expressed in pips.
/// </summary>
public int TakeProfitBuyPips
{
get => _takeProfitBuyPips.Value;
set => _takeProfitBuyPips.Value = value;
}
/// <summary>
/// Take-profit distance for short positions expressed in pips.
/// </summary>
public int TakeProfitSellPips
{
get => _takeProfitSellPips.Value;
set => _takeProfitSellPips.Value = value;
}
/// <summary>
/// Trailing-stop size for long positions expressed in pips.
/// </summary>
public int TrailingStopBuyPips
{
get => _trailingStopBuyPips.Value;
set => _trailingStopBuyPips.Value = value;
}
/// <summary>
/// Trailing-stop size for short positions expressed in pips.
/// </summary>
public int TrailingStopSellPips
{
get => _trailingStopSellPips.Value;
set => _trailingStopSellPips.Value = value;
}
/// <summary>
/// Minimum increment for trailing adjustments expressed in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Percentage width for the 0.3% envelope band.
/// </summary>
public decimal Envelope003
{
get => _envelope003.Value;
set => _envelope003.Value = value;
}
/// <summary>
/// Percentage width for the 0.7% envelope band.
/// </summary>
public decimal Envelope007
{
get => _envelope007.Value;
set => _envelope007.Value = value;
}
/// <summary>
/// Percentage width for the 1.0% envelope band.
/// </summary>
public decimal Envelope010
{
get => _envelope010.Value;
set => _envelope010.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ChannelsEnvelopeCrossStrategy"/>.
/// </summary>
public ChannelsEnvelopeCrossStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading");
_useTradeHours = Param(nameof(UseTradeHours), false)
.SetDisplay("Use Trade Hours", "Restrict trading to specified hours", "Trading");
_fromHour = Param(nameof(FromHour), 0)
.SetDisplay("From Hour", "Start hour for trading window", "Trading");
_toHour = Param(nameof(ToHour), 23)
.SetDisplay("To Hour", "End hour for trading window", "Trading");
_stopLossBuyPips = Param(nameof(StopLossBuyPips), 0)
.SetDisplay("SL BUY (pips)", "Stop loss distance for long positions", "Risk");
_stopLossSellPips = Param(nameof(StopLossSellPips), 0)
.SetDisplay("SL SELL (pips)", "Stop loss distance for short positions", "Risk");
_takeProfitBuyPips = Param(nameof(TakeProfitBuyPips), 0)
.SetDisplay("TP BUY (pips)", "Take profit distance for long positions", "Risk");
_takeProfitSellPips = Param(nameof(TakeProfitSellPips), 0)
.SetDisplay("TP SELL (pips)", "Take profit distance for short positions", "Risk");
_trailingStopBuyPips = Param(nameof(TrailingStopBuyPips), 30)
.SetDisplay("Trail BUY (pips)", "Trailing stop for long positions", "Risk");
_trailingStopSellPips = Param(nameof(TrailingStopSellPips), 30)
.SetDisplay("Trail SELL (pips)", "Trailing stop for short positions", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 1)
.SetDisplay("Trailing Step (pips)", "Minimum increment for trailing stop", "Risk");
_envelope003 = Param(nameof(Envelope003), 0.3m / 100m)
.SetGreaterThanZero()
.SetDisplay("Envelope 0.3%", "Width of the 0.3% envelope", "Indicators");
_envelope007 = Param(nameof(Envelope007), 0.7m / 100m)
.SetGreaterThanZero()
.SetDisplay("Envelope 0.7%", "Width of the 0.7% envelope", "Indicators");
_envelope010 = Param(nameof(Envelope010), 1.0m / 100m)
.SetGreaterThanZero()
.SetDisplay("Envelope 1.0%", "Width of the 1.0% envelope", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hasPreviousValues = false;
_prevFastClose = 0m;
_prevFastOpen = 0m;
_prevSlow = 0m;
_prevEnvLower03 = 0m;
_prevEnvUpper03 = 0m;
_prevEnvLower07 = 0m;
_prevEnvUpper07 = 0m;
_prevEnvLower10 = 0m;
_prevEnvUpper10 = 0m;
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_emaFastClose?.Reset();
_emaFastOpen?.Reset();
_emaSlow?.Reset();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaFastClose = new ExponentialMovingAverage { Length = 2 };
_emaFastOpen = new ExponentialMovingAverage { Length = 2 };
_emaSlow = new ExponentialMovingAverage { Length = 220 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (UseTradeHours && !IsWithinTradeHours(candle.OpenTime))
return;
if (candle.State != CandleStates.Finished)
return;
var fastCloseValue = _emaFastClose.Process(new DecimalIndicatorValue(_emaFastClose, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var fastOpenValue = _emaFastOpen.Process(new DecimalIndicatorValue(_emaFastOpen, candle.OpenPrice, candle.OpenTime) { IsFinal = true });
var slowValue = _emaSlow.Process(new DecimalIndicatorValue(_emaSlow, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var fastClose = fastCloseValue.GetValue<decimal>();
var fastOpen = fastOpenValue.GetValue<decimal>();
var slow = slowValue.GetValue<decimal>();
var envLower03 = slow * (1m - Envelope003);
var envUpper03 = slow * (1m + Envelope003);
var envLower07 = slow * (1m - Envelope007);
var envUpper07 = slow * (1m + Envelope007);
var envLower10 = slow * (1m - Envelope010);
var envUpper10 = slow * (1m + Envelope010);
if (!_emaSlow.IsFormed || !_emaFastClose.IsFormed || !_emaFastOpen.IsFormed)
{
UpdatePreviousValues(fastClose, fastOpen, slow, envLower03, envUpper03, envLower07, envUpper07, envLower10, envUpper10);
return;
}
if (!_hasPreviousValues)
{
UpdatePreviousValues(fastClose, fastOpen, slow, envLower03, envUpper03, envLower07, envUpper07, envLower10, envUpper10);
_hasPreviousValues = true;
return;
}
var buySignal =
(fastClose > envLower10 && _prevFastClose <= _prevEnvLower10) ||
(fastClose > envLower07 && _prevFastClose <= _prevEnvLower07) ||
(fastClose < envLower03 && _prevFastClose < _prevEnvLower03) ||
(fastClose > slow && _prevFastClose <= _prevSlow) ||
(fastClose > envUpper03 && _prevFastClose <= _prevEnvUpper03) ||
(fastClose > envUpper07 && _prevFastClose <= _prevEnvUpper07);
var sellSignal =
(fastOpen < envUpper10 && _prevFastOpen >= _prevEnvUpper10) ||
(fastOpen < envUpper07 && _prevFastOpen >= _prevEnvUpper07) ||
(fastOpen < envUpper03 && _prevFastOpen >= _prevEnvUpper03) ||
(fastOpen < slow && _prevFastOpen >= _prevSlow) ||
(fastOpen < envLower03 && _prevFastOpen >= _prevEnvLower03) ||
(fastOpen < envLower07 && _prevFastOpen >= _prevEnvLower07);
if (Position > 0)
{
ManageLongPosition(candle);
}
else if (Position < 0)
{
ManageShortPosition(candle);
}
if (Position == 0)
{
if (buySignal)
{
BuyMarket(OrderVolume);
SetEntryState(true, candle.ClosePrice);
}
else if (sellSignal)
{
SellMarket(OrderVolume);
SetEntryState(false, candle.ClosePrice);
}
}
UpdatePreviousValues(fastClose, fastOpen, slow, envLower03, envUpper03, envLower07, envUpper07, envLower10, envUpper10);
}
private void ManageLongPosition(ICandleMessage candle)
{
if (_entryPrice is null)
return;
var pip = GetPipSize();
var trailingDistance = TrailingStopBuyPips * pip;
var trailingStep = TrailingStepPips * pip;
var profit = candle.ClosePrice - _entryPrice.Value;
if (TrailingStopBuyPips > 0 && profit > trailingDistance + trailingStep)
{
var threshold = candle.ClosePrice - (trailingDistance + trailingStep);
if (!_stopLossPrice.HasValue || _stopLossPrice.Value < threshold)
_stopLossPrice = candle.ClosePrice - trailingDistance;
}
var exitVolume = Position;
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
{
SellMarket(exitVolume);
ResetPositionState();
return;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(exitVolume);
ResetPositionState();
}
}
private void ManageShortPosition(ICandleMessage candle)
{
if (_entryPrice is null)
return;
var pip = GetPipSize();
var trailingDistance = TrailingStopSellPips * pip;
var trailingStep = TrailingStepPips * pip;
var profit = _entryPrice.Value - candle.ClosePrice;
if (TrailingStopSellPips > 0 && profit > trailingDistance + trailingStep)
{
var threshold = candle.ClosePrice + (trailingDistance + trailingStep);
if (!_stopLossPrice.HasValue || _stopLossPrice.Value > threshold)
_stopLossPrice = candle.ClosePrice + trailingDistance;
}
var exitVolume = -Position;
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
{
BuyMarket(exitVolume);
ResetPositionState();
return;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(exitVolume);
ResetPositionState();
}
}
private void SetEntryState(bool isLong, decimal entryPrice)
{
_entryPrice = entryPrice;
var pip = GetPipSize();
_stopLossPrice = isLong && StopLossBuyPips > 0
? entryPrice - StopLossBuyPips * pip
: !isLong && StopLossSellPips > 0
? entryPrice + StopLossSellPips * pip
: null;
_takeProfitPrice = isLong && TakeProfitBuyPips > 0
? entryPrice + TakeProfitBuyPips * pip
: !isLong && TakeProfitSellPips > 0
? entryPrice - TakeProfitSellPips * pip
: null;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
}
private void UpdatePreviousValues(decimal fastClose, decimal fastOpen, decimal slow, decimal envLower03, decimal envUpper03, decimal envLower07, decimal envUpper07, decimal envLower10, decimal envUpper10)
{
_prevFastClose = fastClose;
_prevFastOpen = fastOpen;
_prevSlow = slow;
_prevEnvLower03 = envLower03;
_prevEnvUpper03 = envUpper03;
_prevEnvLower07 = envLower07;
_prevEnvUpper07 = envUpper07;
_prevEnvLower10 = envLower10;
_prevEnvUpper10 = envUpper10;
}
private bool IsWithinTradeHours(DateTimeOffset time)
{
var hour = time.Hour;
if (FromHour == ToHour)
return hour == FromHour;
if (FromHour < ToHour)
return hour >= FromHour && hour <= ToHour;
return hour >= FromHour || hour <= ToHour;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep ?? 0.0001m;
if (Security?.Decimals is int decimals && (decimals == 3 || decimals == 5))
return step * 10m;
return step;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class channels_envelope_cross_strategy(Strategy):
def __init__(self):
super(channels_envelope_cross_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1)
self._use_trade_hours = self.Param("UseTradeHours", False)
self._from_hour = self.Param("FromHour", 0)
self._to_hour = self.Param("ToHour", 23)
self._stop_loss_buy_pips = self.Param("StopLossBuyPips", 0)
self._stop_loss_sell_pips = self.Param("StopLossSellPips", 0)
self._take_profit_buy_pips = self.Param("TakeProfitBuyPips", 0)
self._take_profit_sell_pips = self.Param("TakeProfitSellPips", 0)
self._trailing_stop_buy_pips = self.Param("TrailingStopBuyPips", 30)
self._trailing_stop_sell_pips = self.Param("TrailingStopSellPips", 30)
self._trailing_step_pips = self.Param("TrailingStepPips", 1)
self._envelope003 = self.Param("Envelope003", 0.003)
self._envelope007 = self.Param("Envelope007", 0.007)
self._envelope010 = self.Param("Envelope010", 0.01)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._ema_fast_close = None
self._ema_fast_open = None
self._ema_slow = None
self._has_previous_values = False
self._prev_fast_close = 0.0
self._prev_fast_open = 0.0
self._prev_slow = 0.0
self._prev_env_lower03 = 0.0
self._prev_env_upper03 = 0.0
self._prev_env_lower07 = 0.0
self._prev_env_upper07 = 0.0
self._prev_env_lower10 = 0.0
self._prev_env_upper10 = 0.0
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def UseTradeHours(self):
return self._use_trade_hours.Value
@property
def FromHour(self):
return self._from_hour.Value
@property
def ToHour(self):
return self._to_hour.Value
@property
def StopLossBuyPips(self):
return self._stop_loss_buy_pips.Value
@property
def StopLossSellPips(self):
return self._stop_loss_sell_pips.Value
@property
def TakeProfitBuyPips(self):
return self._take_profit_buy_pips.Value
@property
def TakeProfitSellPips(self):
return self._take_profit_sell_pips.Value
@property
def TrailingStopBuyPips(self):
return self._trailing_stop_buy_pips.Value
@property
def TrailingStopSellPips(self):
return self._trailing_stop_sell_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def Envelope003(self):
return self._envelope003.Value
@property
def Envelope007(self):
return self._envelope007.Value
@property
def Envelope010(self):
return self._envelope010.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(channels_envelope_cross_strategy, self).OnStarted2(time)
self._ema_fast_close = ExponentialMovingAverage()
self._ema_fast_close.Length = 2
self._ema_fast_open = ExponentialMovingAverage()
self._ema_fast_open.Length = 2
self._ema_slow = ExponentialMovingAverage()
self._ema_slow.Length = 220
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if self.UseTradeHours and not self._is_within_trade_hours(candle.OpenTime):
return
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
fc_val = process_float(self._ema_fast_close, Decimal(float(candle.ClosePrice)), t, True)
fo_val = process_float(self._ema_fast_open, Decimal(float(candle.OpenPrice)), t, True)
sl_val = process_float(self._ema_slow, Decimal(float(candle.ClosePrice)), t, True)
fast_close = float(fc_val.Value)
fast_open = float(fo_val.Value)
slow = float(sl_val.Value)
env003 = float(self.Envelope003)
env007 = float(self.Envelope007)
env010 = float(self.Envelope010)
env_lower03 = slow * (1.0 - env003)
env_upper03 = slow * (1.0 + env003)
env_lower07 = slow * (1.0 - env007)
env_upper07 = slow * (1.0 + env007)
env_lower10 = slow * (1.0 - env010)
env_upper10 = slow * (1.0 + env010)
if not self._ema_slow.IsFormed or not self._ema_fast_close.IsFormed or not self._ema_fast_open.IsFormed:
self._update_prev(fast_close, fast_open, slow, env_lower03, env_upper03, env_lower07, env_upper07, env_lower10, env_upper10)
return
if not self._has_previous_values:
self._update_prev(fast_close, fast_open, slow, env_lower03, env_upper03, env_lower07, env_upper07, env_lower10, env_upper10)
self._has_previous_values = True
return
buy_signal = (
(fast_close > env_lower10 and self._prev_fast_close <= self._prev_env_lower10) or
(fast_close > env_lower07 and self._prev_fast_close <= self._prev_env_lower07) or
(fast_close < env_lower03 and self._prev_fast_close < self._prev_env_lower03) or
(fast_close > slow and self._prev_fast_close <= self._prev_slow) or
(fast_close > env_upper03 and self._prev_fast_close <= self._prev_env_upper03) or
(fast_close > env_upper07 and self._prev_fast_close <= self._prev_env_upper07)
)
sell_signal = (
(fast_open < env_upper10 and self._prev_fast_open >= self._prev_env_upper10) or
(fast_open < env_upper07 and self._prev_fast_open >= self._prev_env_upper07) or
(fast_open < env_upper03 and self._prev_fast_open >= self._prev_env_upper03) or
(fast_open < slow and self._prev_fast_open >= self._prev_slow) or
(fast_open < env_lower03 and self._prev_fast_open >= self._prev_env_lower03) or
(fast_open < env_lower07 and self._prev_fast_open >= self._prev_env_lower07)
)
pos = float(self.Position)
if pos > 0:
self._manage_long(candle)
elif pos < 0:
self._manage_short(candle)
if float(self.Position) == 0:
if buy_signal:
self.BuyMarket(float(self.OrderVolume))
self._set_entry_state(True, float(candle.ClosePrice))
elif sell_signal:
self.SellMarket(float(self.OrderVolume))
self._set_entry_state(False, float(candle.ClosePrice))
self._update_prev(fast_close, fast_open, slow, env_lower03, env_upper03, env_lower07, env_upper07, env_lower10, env_upper10)
def _manage_long(self, candle):
if self._entry_price is None:
return
pip = self._get_pip_size()
trail_dist = self.TrailingStopBuyPips * pip
trail_step = self.TrailingStepPips * pip
profit = float(candle.ClosePrice) - self._entry_price
if self.TrailingStopBuyPips > 0 and profit > trail_dist + trail_step:
threshold = float(candle.ClosePrice) - (trail_dist + trail_step)
if self._stop_loss_price is None or self._stop_loss_price < threshold:
self._stop_loss_price = float(candle.ClosePrice) - trail_dist
pos = float(self.Position)
if self._stop_loss_price is not None and float(candle.LowPrice) <= self._stop_loss_price:
self.SellMarket(pos)
self._reset_position_state()
return
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(pos)
self._reset_position_state()
def _manage_short(self, candle):
if self._entry_price is None:
return
pip = self._get_pip_size()
trail_dist = self.TrailingStopSellPips * pip
trail_step = self.TrailingStepPips * pip
profit = self._entry_price - float(candle.ClosePrice)
if self.TrailingStopSellPips > 0 and profit > trail_dist + trail_step:
threshold = float(candle.ClosePrice) + (trail_dist + trail_step)
if self._stop_loss_price is None or self._stop_loss_price > threshold:
self._stop_loss_price = float(candle.ClosePrice) + trail_dist
pos = abs(float(self.Position))
if self._stop_loss_price is not None and float(candle.HighPrice) >= self._stop_loss_price:
self.BuyMarket(pos)
self._reset_position_state()
return
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(pos)
self._reset_position_state()
def _set_entry_state(self, is_long, entry_price):
self._entry_price = entry_price
pip = self._get_pip_size()
if is_long and self.StopLossBuyPips > 0:
self._stop_loss_price = entry_price - self.StopLossBuyPips * pip
elif not is_long and self.StopLossSellPips > 0:
self._stop_loss_price = entry_price + self.StopLossSellPips * pip
else:
self._stop_loss_price = None
if is_long and self.TakeProfitBuyPips > 0:
self._take_profit_price = entry_price + self.TakeProfitBuyPips * pip
elif not is_long and self.TakeProfitSellPips > 0:
self._take_profit_price = entry_price - self.TakeProfitSellPips * pip
else:
self._take_profit_price = None
def _reset_position_state(self):
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
def _update_prev(self, fc, fo, sl, el03, eu03, el07, eu07, el10, eu10):
self._prev_fast_close = fc
self._prev_fast_open = fo
self._prev_slow = sl
self._prev_env_lower03 = el03
self._prev_env_upper03 = eu03
self._prev_env_lower07 = el07
self._prev_env_upper07 = eu07
self._prev_env_lower10 = el10
self._prev_env_upper10 = eu10
def _is_within_trade_hours(self, time):
hour = time.Hour
if self.FromHour == self.ToHour:
return hour == self.FromHour
if self.FromHour < self.ToHour:
return hour >= self.FromHour and hour <= self.ToHour
return hour >= self.FromHour or hour <= self.ToHour
def _get_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0001
if sec is not None and sec.Decimals is not None:
decimals = int(sec.Decimals)
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def OnReseted(self):
super(channels_envelope_cross_strategy, self).OnReseted()
self._has_previous_values = False
self._prev_fast_close = 0.0
self._prev_fast_open = 0.0
self._prev_slow = 0.0
self._prev_env_lower03 = 0.0
self._prev_env_upper03 = 0.0
self._prev_env_lower07 = 0.0
self._prev_env_upper07 = 0.0
self._prev_env_lower10 = 0.0
self._prev_env_upper10 = 0.0
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
def CreateClone(self):
return channels_envelope_cross_strategy()