Стратегия Channels Envelope Cross
Общее описание
Channels Envelope Cross — это точный перенос советника MetaTrader «Channels» на платформу StockSharp. Стратегия работает на часовом таймфрейме и отслеживает двухпериодные экспоненциальные средние (EMA) по ценам открытия и закрытия относительно трёх оболочек (0.3%, 0.7% и 1.0%), построенных вокруг медленной EMA с периодом 220. Пробои быстрой EMA через границы оболочек формируют торговые сигналы, а опциональный фильтр по времени ограничивает торговлю заданным интервалом часов.
Логика работы
- Набор индикаторов
- Быстрая EMA (период 2) по ценам закрытия свечи.
- Быстрая EMA (период 2) по ценам открытия свечи.
- Медленная EMA (период 220) по ценам закрытия.
- Верхние и нижние границы оболочек, рассчитанные от медленной EMA с отклонениями 0.3%, 0.7% и 1.0%.
- Условия для покупки
- Срабатывают, когда быстрая EMA по закрытию пробивает снизу-вверх нижние границы оболочек 1.0% или 0.7%, остаётся ниже нижней границы 0.3% две свечи подряд, пересекает медленную EMA или пробивает верхние границы 0.3%/0.7%. Любое из условий открывает длинную позицию при отсутствии открытых сделок.
- Условия для продажи
- Срабатывают, когда быстрая EMA по открытию пробивает сверху-вниз любую из верхних оболочек, опускается ниже медленной EMA или пересекает нижние границы сверху-вниз. Любое из условий открывает короткую позицию, если нет активной позиции.
- Управление рисками
- Фиксированные стоп-лоссы и тейк-профиты задаются в пунктах отдельно для лонгов и шортов. Значение «0» отключает соответствующий уровень.
- Независимые трейлинг-стопы для длинных и коротких позиций подтягивают защитный стоп, когда прибыль превышает расстояние трейлинга плюс минимальный шаг.
- Фильтр по времени
- При включении стратегиия проверяет час открытия свечи и допускает новые входы только в пределах указанного диапазона (включительно). Управление открытыми позициями продолжается всегда.
Параметры
| Параметр | Значение |
|---|---|
OrderVolume |
Объём сделки (лоты/контракты). |
UseTradeHours |
Включает фильтр торговых часов. |
FromHour / ToHour |
Начальный и конечный час торгового окна (поддерживается переход через полночь). |
StopLossBuyPips / StopLossSellPips |
Размер стоп-лосса в пунктах для лонга/шорта. |
TakeProfitBuyPips / TakeProfitSellPips |
Размер тейк-профита в пунктах для лонга/шорта. |
TrailingStopBuyPips / TrailingStopSellPips |
Дистанция трейлинг-стопа в пунктах для лонга/шорта. |
TrailingStepPips |
Минимальный шаг изменения трейлинг-стопа (в пунктах). |
CandleType |
Тип свечей, используемых в расчётах (по умолчанию — часовые). |
Управление позициями
- При открытии позиции фиксируется цена входа, рассчитываются уровни стоп-лосса и тейк-профита в абсолютных ценах, трейлинг-стоп сбрасывается.
- Для длинных позиций стоп подтягивается выше, когда прибыль превышает сумму
TrailingStopBuyPips + TrailingStepPips. Закрытие происходит по стопу или тейк-профиту — что наступит раньше. - Для коротких позиций стоп опускается ниже при аналогичном условии, закрытие выполняется симметрично.
Дополнительные замечания
- Размер пункта определяется по шагу цены инструмента; для инструментов с тремя или пятью знаками после запятой пункт умножается на десять, как в оригинальном советнике.
- Стратегия работает только с одной позицией одновременно: новый вход возможен после полного закрытия предыдущего.
- Метод
StartProtection()уже активирован, что защищает от «зависших» позиций после перезапуска.
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()