Channels Envelope Cross 策略
概述
Channels Envelope Cross 策略是 MetaTrader 平台上 "Channels" 智能交易系统的完全移植版本。策略在 1 小时 K 线上运行,计算两条周期为 2 的指数移动平均线(EMA),分别基于开盘价和收盘价,并与由 220 周期 EMA 构建的三条价格通道(偏离度 0.3%、0.7%、1.0%)进行比较。当快速 EMA 突破这些通道时产生交易信号,另外可以启用时间过滤器,将交易限制在指定的小时范围内。
交易逻辑
- 指标体系
- 快速 EMA(周期 2,使用收盘价)。
- 快速 EMA(周期 2,使用开盘价)。
- 慢速 EMA(周期 220,使用收盘价)。
- 以慢速 EMA 为基准的三组包络线(±0.3%、±0.7%、±1.0%)。
- 做多条件
- 当收盘价 EMA 向上突破 1.0% 或 0.7% 的下轨、连续两根 K 线位于 0.3% 下轨之下、向上突破慢速 EMA,或突破 0.3%/0.7% 上轨时,在当前没有持仓的情况下建立多头。
- 做空条件
- 当开盘价 EMA 向下突破任意一条上轨、跌破慢速 EMA,或从上向下穿越下轨时,在没有持仓时建立空头。
- 风控管理
- 多头和空头可分别设置固定的止损和止盈,单位为点(pip)。当参数为 0 时,表示不启用该水平。
- 多、空分别拥有独立的追踪止损,只有当浮动利润超过“追踪距离 + 最小步长”时才会上调/下调保护性止损。
- 时间过滤
- 启用后,策略仅在设定的小时区间(包含端点,支持跨日)内产生新的入场信号;已开仓的头寸仍会被持续管理。
参数说明
| 参数 | 说明 |
|---|---|
OrderVolume |
每次下单的数量(手数或合约数)。 |
UseTradeHours |
是否启用交易时间过滤。 |
FromHour / ToHour |
允许交易的起始和结束小时(可跨越午夜)。 |
StopLossBuyPips / StopLossSellPips |
多/空仓位的止损距离(点)。 |
TakeProfitBuyPips / TakeProfitSellPips |
多/空仓位的止盈距离(点)。 |
TrailingStopBuyPips / TrailingStopSellPips |
多/空追踪止损的基础距离(点)。 |
TrailingStepPips |
调整追踪止损所需的最小点数增量。 |
CandleType |
计算所使用的蜡烛类型(默认 1 小时)。 |
仓位管理
- 建仓时记录进场价格,换算出止损和止盈的绝对价格,并重置追踪止损。
- 多头持仓时,如果利润超过
TrailingStopBuyPips + TrailingStepPips,止损会向上移动;若价格回落到止损或上涨至止盈则平仓。 - 空头持仓时采用对称逻辑:利润达到阈值后向下移动止损,触发止损或止盈即退出。
其他说明
- 策略根据品种的最小价格跳动计算点值。若证券价格保留 3 或 5 位小数,则点值乘以 10,以匹配原始 MQL 实现。
- 同一时间只持有一个方向的仓位。必须先平仓,才会发出新的入场信号。
- 代码中已调用
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()