Channels Envelope Cross Strategy
Overview
The Channels Envelope Cross strategy is a direct port of the MetaTrader "Channels" expert advisor. The system trades hourly candles and monitors a fast two-period exponential moving average (EMA) relative to three EMA-based envelopes (0.3%, 0.7% and 1.0% deviations) that are calculated from a slow 220-period EMA. Breakouts of the fast EMA through these envelopes generate directional entries, while an optional time filter restricts trading to specific hours.
Trading Logic
- Indicator stack
- Fast EMA (length 2) calculated on candle close prices.
- Fast EMA (length 2) calculated on candle open prices.
- Slow EMA (length 220) calculated on candle close prices.
- Three envelope levels derived from the slow EMA with 0.3%, 0.7% and 1.0% deviations.
- Long setup
- Triggered when the fast close EMA crosses above either the 1.0% or 0.7% lower envelope, remains below the 0.3% lower envelope for two consecutive bars, crosses above the slow EMA, or breaks through the 0.3% or 0.7% upper envelope. Any of these conditions can fire a long entry when no position is open.
- Short setup
- Triggered when the fast open EMA crosses below any of the upper envelopes, drops below the slow EMA, or pierces the lower envelopes from above. Any of these conditions can fire a short entry when no position is open.
- Risk management
- Fixed stop-loss and take-profit levels (per side) are expressed in pips and converted to price distance by using the instrument tick size. If the inputs are set to zero, the respective level is not applied.
- Independent trailing stops for long and short positions move the protective stop closer to market price when the profit exceeds the trailing distance plus a configurable step increment.
- Time filter
- When enabled, the strategy only processes entries during the configured inclusive hour range. Positions are still managed when the filter is active.
Parameters
| Parameter | Description |
|---|---|
OrderVolume |
Order size used for market entries (lots or contracts depending on the security). |
UseTradeHours |
Enables the time filter for entries. |
FromHour / ToHour |
Inclusive start and end hours for the trading window (supports overnight ranges). |
StopLossBuyPips / StopLossSellPips |
Stop-loss distance for long/short trades expressed in pips. |
TakeProfitBuyPips / TakeProfitSellPips |
Take-profit distance for long/short trades expressed in pips. |
TrailingStopBuyPips / TrailingStopSellPips |
Trailing stop distance in pips for long/short trades. |
TrailingStepPips |
Minimum increment (in pips) required to move a trailing stop. |
CandleType |
Candle series used for calculations (default is 1-hour time frame). |
Position Management
- On entry the strategy stores the fill price, calculates stop-loss and take-profit targets in absolute price units, and resets trailing levels.
- While a long position is open, the stop-loss is trailed upward whenever profit exceeds
TrailingStopBuyPips + TrailingStepPips. The strategy exits at the stop-loss or take-profit whichever is hit first. - While a short position is open, the stop-loss is trailed downward using the short-side trailing parameters and exits are executed symmetrically.
Notes
- The pip size is derived from the security tick size. For three- or five-decimal instruments the pip is multiplied by ten to emulate the MetaTrader logic.
- The strategy works with a single position at a time. A new entry is only placed after the existing position has been closed.
- Enable
StartProtectionin the base class to guard against unexpected open positions after restarts (already called in the implementation).
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()