Estratégia de Cruzamento de Canais com Envelope
Visão geral
A estratégia de Cruzamento de Canais com Envelope é um port direto do consultor especialista MetaTrader "Channels". O sistema opera velas horárias e monitora uma média móvel exponencial (EMA) rápida de dois períodos em relação a três envelopes baseados em EMA (desvios de 0.3%, 0.7% e 1.0%) calculados a partir de uma EMA lenta de 220 períodos. Rompimentos da EMA rápida através desses envelopes geram entradas direcionais, enquanto um filtro de tempo opcional restringe o trading a horas específicas.
Lógica de trading
- Pilha de indicadores
- EMA rápida (comprimento 2) calculada sobre preços de fechamento de vela.
- EMA rápida (comprimento 2) calculada sobre preços de abertura de vela.
- EMA lenta (comprimento 220) calculada sobre preços de fechamento de vela.
- Três níveis de envelope derivados da EMA lenta com desvios de 0.3%, 0.7% e 1.0%.
- Configuração comprada
- Ativada quando a EMA rápida de fechamento cruza acima do envelope inferior de 1.0% ou 0.7%, permanece abaixo do envelope inferior de 0.3% por duas barras consecutivas, cruza acima da EMA lenta, ou rompe os envelopes superiores de 0.3% ou 0.7%. Qualquer uma dessas condições pode disparar uma entrada comprada quando não há posição aberta.
- Configuração vendida
- Ativada quando a EMA rápida de abertura cruza abaixo de qualquer um dos envelopes superiores, cai abaixo da EMA lenta, ou perfura os envelopes inferiores de cima. Qualquer uma dessas condições pode disparar uma entrada vendida quando não há posição aberta.
- Gerenciamento de risco
- Níveis fixos de stop-loss e take-profit (por lado) são expressos em pips e convertidos para distância de preço usando o tamanho de tick do instrumento. Se as entradas forem definidas como zero, o nível respectivo não é aplicado.
- Trailing stops independentes para posições compradas e vendidas movem o stop de proteção para mais perto do preço de mercado quando o lucro excede a distância de trailing mais um incremento de passo configurável.
- Filtro de tempo
- Quando habilitado, a estratégia processa entradas apenas durante o intervalo de horas inclusivo configurado. As posições ainda são gerenciadas quando o filtro está ativo.
Parâmetros
| Parâmetro | Descrição |
|---|---|
OrderVolume |
Tamanho da ordem usado para entradas de mercado (lotes ou contratos dependendo do instrumento). |
UseTradeHours |
Habilita o filtro de tempo para entradas. |
FromHour / ToHour |
Horas de início e fim inclusivas para a janela de trading (suporta intervalos noturnos). |
StopLossBuyPips / StopLossSellPips |
Distância do stop-loss para operações compradas/vendidas expressa em pips. |
TakeProfitBuyPips / TakeProfitSellPips |
Distância do take-profit para operações compradas/vendidas expressa em pips. |
TrailingStopBuyPips / TrailingStopSellPips |
Distância do trailing stop em pips para operações compradas/vendidas. |
TrailingStepPips |
Incremento mínimo (em pips) necessário para mover um trailing stop. |
CandleType |
Série de velas usada para cálculos (padrão é período de 1 hora). |
Gerenciamento de posições
- Na entrada, a estratégia armazena o preço de execução, calcula os alvos de stop-loss e take-profit em unidades de preço absoluto e redefine os níveis de trailing.
- Enquanto uma posição comprada estiver aberta, o stop-loss é seguido para cima sempre que o lucro exceder
TrailingStopBuyPips + TrailingStepPips. A estratégia sai no stop-loss ou take-profit, o que for atingido primeiro. - Enquanto uma posição vendida estiver aberta, o stop-loss é seguido para baixo usando os parâmetros de trailing do lado vendido e as saídas são executadas simetricamente.
Notas
- O tamanho do pip é derivado do tamanho do tick do instrumento. Para instrumentos de três ou cinco decimais, o pip é multiplicado por dez para emular a lógica do MetaTrader.
- A estratégia trabalha com uma única posição por vez. Uma nova entrada só é colocada depois que a posição existente foi fechada.
- Habilite
StartProtectionna classe base para proteção contra posições abertas inesperadas após reinicializações (já chamado na implementação).
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()