趋势追随彩虹策略
概述
趋势追随彩虹策略是 MetaTrader 4 专家顾问“TrendFollowerRainbowMethodkyast773”的 C# 移植版本。策略通过多层确认来捕捉强趋势行情,同时过滤震荡阶段。信号由彩虹式指数移动平均线的排列、MACD 动量、拉盖尔滤波阈值、资金流量指标以及快/慢 EMA 金叉/死叉共同驱动。
交易逻辑
- 交易时段 – 只有当当前 K 线的收盘时间严格位于可配置的起止小时之间时才评估信号,以复刻原始 EA 避开交易日首尾时段的行为。
- EMA 交叉触发 – 多头需要快 EMA(默认长度 4)上穿慢 EMA(默认长度 8),空头则要求相反的下穿。
- MACD 确认 – MACD 线和信号线(默认 5/35/5)必须同时位于零轴上方才能做多,同时位于零轴下方才能做空,确认趋势方向的动量。
- 拉盖尔滤波 – 拉盖尔滤波值必须上穿 0.15 才允许做多,下穿 0.75 才允许做空,复现原指标的阈值判断。
- 彩虹排列 – 五组指数移动平均线(每组四条)必须保持单调排序:多头时要求不递增,空头时要求不递减,以确认彩虹结构的完整性。
- 资金流量指标过滤 – 资金流量指标(默认周期 14)需要低于 40 才能做多,高于 60 才能做空,避免与资金流向相反交易。
- 持仓管理 – 使用市价单入场。当出现反向信号时,先平掉当前持仓,再按新方向开仓。
风险管理
策略使用 StockSharp 的 StartProtection 工具提供内置保护:
- 止盈与止损距离以价格步长表示,与 EA 的点数配置保持一致。
- 跟踪止损同样以步长设置,一旦启动保护模块即开始工作。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
OrderVolume |
基础下单手数。 | 1 |
TakeProfitPoints |
止盈距离(价格步长)。 | 17 |
StopLossPoints |
止损距离(价格步长)。 | 30 |
TrailingStopPoints |
跟踪止损距离(价格步长)。 | 45 |
TradingStartHour |
每日跳过信号评估的开始小时。 | 1 |
TradingEndHour |
每日跳过信号评估的结束小时。 | 23 |
FastEmaLength |
快速 EMA 长度。 | 4 |
SlowEmaLength |
慢速 EMA 长度。 | 8 |
MacdFastLength |
MACD 快线 EMA 长度。 | 5 |
MacdSlowLength |
MACD 慢线 EMA 长度。 | 35 |
MacdSignalLength |
MACD 信号线 EMA 长度。 | 5 |
LaguerreGamma |
拉盖尔滤波平滑系数。 | 0.7 |
LaguerreBuyThreshold |
做多所需的拉盖尔上穿阈值。 | 0.15 |
LaguerreSellThreshold |
做空所需的拉盖尔下穿阈值。 | 0.75 |
MfiPeriod |
资金流量指标周期。 | 14 |
MfiBuyLevel |
做多允许的最大 MFI 值。 | 40 |
MfiSellLevel |
做空允许的最小 MFI 值。 | 60 |
RainbowGroup{1..5}Base |
每组彩虹 EMA 的基准长度。通过基准值加上 0/2/4/6 的偏移生成四条 EMA。 | 5 / 13 / 21 / 34 / 55 |
CandleType |
策略使用的主图 K 线类型,默认 5 分钟。 | 5 分钟 |
图表
策略会自动绘制:
- 订阅的价格 K 线;
- 快慢 EMA,用于直观观察交叉;
- 拉盖尔滤波曲线,用于跟踪阈值突破;
- 策略成交记录。
说明
- 彩虹逻辑使用可配置的 EMA 组合来近似原始的 RainbowMMA 自定义指标,可根据需要调整基准长度以贴合特定模板。
- 代码注释、日志及文档均使用英文,符合项目要求。
- 本次任务仅提供 C# 实现,未生成 Python 版本。
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>
/// Trend following strategy that combines EMA crossover, MACD confirmation,
/// Laguerre filter thresholds, rainbow moving average structure and MFI filter.
/// </summary>
public class TrendFollowerRainbowStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _tradingStartHour;
private readonly StrategyParam<int> _tradingEndHour;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<decimal> _laguerreGamma;
private readonly StrategyParam<decimal> _laguerreBuyThreshold;
private readonly StrategyParam<decimal> _laguerreSellThreshold;
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _mfiBuyLevel;
private readonly StrategyParam<decimal> _mfiSellLevel;
private readonly StrategyParam<int> _rainbowGroup1Base;
private readonly StrategyParam<int> _rainbowGroup2Base;
private readonly StrategyParam<int> _rainbowGroup3Base;
private readonly StrategyParam<int> _rainbowGroup4Base;
private readonly StrategyParam<int> _rainbowGroup5Base;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaFast = null!;
private ExponentialMovingAverage _emaSlow = null!;
private MovingAverageConvergenceDivergenceSignal _macd = null!;
private AdaptiveLaguerreFilter _laguerre = null!;
private MoneyFlowIndex _mfi = null!;
private ExponentialMovingAverage[][] _rainbowGroups = [];
private decimal? _previousFastEma;
private decimal? _previousSlowEma;
private decimal? _previousLaguerre;
private decimal _pointValue;
/// <summary>
/// Initializes a new instance of the <see cref="TrendFollowerRainbowStrategy"/> class.
/// </summary>
public TrendFollowerRainbowStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetDisplay("Order Volume", "Base order volume", "Trading")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 17m)
.SetDisplay("Take Profit (pts)", "Distance in price steps for take profit", "Risk Management")
;
_stopLossPoints = Param(nameof(StopLossPoints), 30m)
.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop loss", "Risk Management")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 45m)
.SetDisplay("Trailing Stop (pts)", "Distance in price steps for trailing stop", "Risk Management")
;
_tradingStartHour = Param(nameof(TradingStartHour), 1)
.SetDisplay("Start Hour", "Hour (0-23) when trading window opens", "Trading Schedule")
;
_tradingEndHour = Param(nameof(TradingEndHour), 23)
.SetDisplay("End Hour", "Hour (0-23) when trading window closes", "Trading Schedule")
;
_fastEmaLength = Param(nameof(FastEmaLength), 4)
.SetRange(2, 20)
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
;
_slowEmaLength = Param(nameof(SlowEmaLength), 8)
.SetRange(3, 50)
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
;
_macdFastLength = Param(nameof(MacdFastLength), 5)
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
;
_macdSlowLength = Param(nameof(MacdSlowLength), 35)
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
;
_macdSignalLength = Param(nameof(MacdSignalLength), 5)
.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
;
_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
.SetRange(0.1m, 0.9m)
.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre filter", "Indicators")
;
_laguerreBuyThreshold = Param(nameof(LaguerreBuyThreshold), 0.15m)
.SetDisplay("Laguerre Buy", "Threshold crossed upward for long signals", "Indicators")
;
_laguerreSellThreshold = Param(nameof(LaguerreSellThreshold), 0.75m)
.SetDisplay("Laguerre Sell", "Threshold crossed downward for short signals", "Indicators")
;
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetDisplay("MFI Period", "Money Flow Index calculation period", "Indicators")
;
_mfiBuyLevel = Param(nameof(MfiBuyLevel), 40m)
.SetDisplay("MFI Buy", "Upper bound for oversold check", "Indicators")
;
_mfiSellLevel = Param(nameof(MfiSellLevel), 60m)
.SetDisplay("MFI Sell", "Lower bound for overbought check", "Indicators")
;
_rainbowGroup1Base = Param(nameof(RainbowGroup1Base), 5)
.SetDisplay("Rainbow Group 1", "Base length for the fastest rainbow bundle", "Rainbow")
;
_rainbowGroup2Base = Param(nameof(RainbowGroup2Base), 13)
.SetDisplay("Rainbow Group 2", "Base length for the second rainbow bundle", "Rainbow")
;
_rainbowGroup3Base = Param(nameof(RainbowGroup3Base), 21)
.SetDisplay("Rainbow Group 3", "Base length for the middle rainbow bundle", "Rainbow")
;
_rainbowGroup4Base = Param(nameof(RainbowGroup4Base), 34)
.SetDisplay("Rainbow Group 4", "Base length for the fourth rainbow bundle", "Rainbow")
;
_rainbowGroup5Base = Param(nameof(RainbowGroup5Base), 55)
.SetDisplay("Rainbow Group 5", "Base length for the slowest rainbow bundle", "Rainbow")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// First hour (0-23) when the strategy can evaluate entries.
/// </summary>
public int TradingStartHour
{
get => _tradingStartHour.Value;
set => _tradingStartHour.Value = value;
}
/// <summary>
/// Last hour (0-23) when the strategy can evaluate entries.
/// </summary>
public int TradingEndHour
{
get => _tradingEndHour.Value;
set => _tradingEndHour.Value = value;
}
/// <summary>
/// Fast EMA length used for the crossover signal.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Slow EMA length used for the crossover signal.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// MACD fast EMA length.
/// </summary>
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
/// <summary>
/// MACD slow EMA length.
/// </summary>
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
/// <summary>
/// MACD signal EMA length.
/// </summary>
public int MacdSignalLength
{
get => _macdSignalLength.Value;
set => _macdSignalLength.Value = value;
}
/// <summary>
/// Laguerre filter smoothing factor.
/// </summary>
public decimal LaguerreGamma
{
get => _laguerreGamma.Value;
set => _laguerreGamma.Value = value;
}
/// <summary>
/// Laguerre threshold that needs to be crossed upward to allow long signals.
/// </summary>
public decimal LaguerreBuyThreshold
{
get => _laguerreBuyThreshold.Value;
set => _laguerreBuyThreshold.Value = value;
}
/// <summary>
/// Laguerre threshold that needs to be crossed downward to allow short signals.
/// </summary>
public decimal LaguerreSellThreshold
{
get => _laguerreSellThreshold.Value;
set => _laguerreSellThreshold.Value = value;
}
/// <summary>
/// Money Flow Index period.
/// </summary>
public int MfiPeriod
{
get => _mfiPeriod.Value;
set => _mfiPeriod.Value = value;
}
/// <summary>
/// Maximum MFI level that still allows long entries.
/// </summary>
public decimal MfiBuyLevel
{
get => _mfiBuyLevel.Value;
set => _mfiBuyLevel.Value = value;
}
/// <summary>
/// Minimum MFI level that still allows short entries.
/// </summary>
public decimal MfiSellLevel
{
get => _mfiSellLevel.Value;
set => _mfiSellLevel.Value = value;
}
/// <summary>
/// Base period for the fastest rainbow bundle.
/// </summary>
public int RainbowGroup1Base
{
get => _rainbowGroup1Base.Value;
set => _rainbowGroup1Base.Value = value;
}
/// <summary>
/// Base period for the second rainbow bundle.
/// </summary>
public int RainbowGroup2Base
{
get => _rainbowGroup2Base.Value;
set => _rainbowGroup2Base.Value = value;
}
/// <summary>
/// Base period for the third rainbow bundle.
/// </summary>
public int RainbowGroup3Base
{
get => _rainbowGroup3Base.Value;
set => _rainbowGroup3Base.Value = value;
}
/// <summary>
/// Base period for the fourth rainbow bundle.
/// </summary>
public int RainbowGroup4Base
{
get => _rainbowGroup4Base.Value;
set => _rainbowGroup4Base.Value = value;
}
/// <summary>
/// Base period for the fifth rainbow bundle.
/// </summary>
public int RainbowGroup5Base
{
get => _rainbowGroup5Base.Value;
set => _rainbowGroup5Base.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousFastEma = null;
_previousSlowEma = null;
_previousLaguerre = null;
_pointValue = 0m;
_rainbowGroups = [];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 0m;
Volume = OrderVolume;
var takeProfit = ToAbsoluteUnit(TakeProfitPoints);
var stopLoss = ToAbsoluteUnit(StopLossPoints);
if (takeProfit != null || stopLoss != null)
{
StartProtection(
takeProfit: takeProfit,
stopLoss: stopLoss,
isStopTrailing: TrailingStopPoints > 0m,
useMarketOrders: true);
}
_emaFast = new EMA { Length = FastEmaLength };
_emaSlow = new EMA { Length = SlowEmaLength };
_macd = new MovingAverageConvergenceDivergenceSignal();
_macd.Macd.ShortMa.Length = MacdFastLength;
_macd.Macd.LongMa.Length = MacdSlowLength;
_macd.SignalMa.Length = MacdSignalLength;
_laguerre = new AdaptiveLaguerreFilter { Gamma = LaguerreGamma };
_mfi = new MoneyFlowIndex { Length = MfiPeriod };
_rainbowGroups = BuildRainbowGroups();
var indicators = new List<IIndicator>
{
_emaFast,
_emaSlow,
_macd,
_laguerre,
_mfi
};
foreach (var group in _rainbowGroups)
{
indicators.AddRange(group);
}
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(indicators.ToArray(), ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaFast);
DrawIndicator(area, _emaSlow);
DrawIndicator(area, _laguerre);
DrawOwnTrades(area);
}
}
private ExponentialMovingAverage[][] BuildRainbowGroups()
{
var offsets = new[] { 0, 2, 4, 6 };
return new[]
{
offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup1Base + o) }).ToArray(),
offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup2Base + o) }).ToArray(),
offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup3Base + o) }).ToArray(),
offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup4Base + o) }).ToArray(),
offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup5Base + o) }).ToArray()
};
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.CloseTime.Hour;
if (hour <= TradingStartHour || hour >= TradingEndHour)
{
UpdatePreviousValues(values);
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
UpdatePreviousValues(values);
return;
}
var index = 0;
var hasFast = TryGetDecimal(values[index++], out var fastEma);
var hasSlow = TryGetDecimal(values[index++], out var slowEma);
if (!hasFast || !hasSlow)
{
UpdatePreviousValues(values, hasFast ? fastEma : null, hasSlow ? slowEma : null);
return;
}
var macdValue = values[index++];
if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData ||
macdData.Macd is not decimal macdMain || macdData.Signal is not decimal macdSignal)
{
UpdatePreviousValues(values, fastEma, slowEma);
return;
}
if (!TryGetDecimal(values[index++], out var laguerre))
{
UpdatePreviousValues(values, fastEma, slowEma);
return;
}
if (!TryGetDecimal(values[index++], out var mfi))
{
UpdatePreviousValues(values, fastEma, slowEma, laguerre);
return;
}
var rainbowValues = new List<decimal[]>(_rainbowGroups.Length);
for (var groupIndex = 0; groupIndex < _rainbowGroups.Length; groupIndex++)
{
var group = _rainbowGroups[groupIndex];
var decimals = new decimal[group.Length];
for (var i = 0; i < group.Length; i++)
{
if (!TryGetDecimal(values[index++], out var rainbow))
{
UpdatePreviousValues(values, fastEma, slowEma, laguerre);
return;
}
decimals[i] = rainbow;
}
rainbowValues.Add(decimals);
}
var rainbowBullish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: true));
var rainbowBearish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: false));
var emaCrossUp = _previousFastEma is decimal prevFast && _previousSlowEma is decimal prevSlow &&
prevFast < prevSlow && fastEma > slowEma;
var emaCrossDown = _previousFastEma is decimal prevFastDown && _previousSlowEma is decimal prevSlowDown &&
prevFastDown > prevSlowDown && fastEma < slowEma;
var laguerreBullish = _previousLaguerre is decimal prevLagBull &&
prevLagBull <= LaguerreBuyThreshold && laguerre > LaguerreBuyThreshold;
var laguerreBearish = _previousLaguerre is decimal prevLagBear &&
prevLagBear >= LaguerreSellThreshold && laguerre < LaguerreSellThreshold;
var macdBullish = macdMain > 0m && macdSignal > 0m;
var macdBearish = macdMain < 0m && macdSignal < 0m;
var mfiBullish = mfi < MfiBuyLevel;
var mfiBearish = mfi > MfiSellLevel;
if (emaCrossUp && macdBullish && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
}
else if (emaCrossDown && macdBearish && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
}
_previousFastEma = fastEma;
_previousSlowEma = slowEma;
_previousLaguerre = laguerre;
}
private void UpdatePreviousValues(IIndicatorValue[] values, decimal? fastEma = null, decimal? slowEma = null, decimal? laguerre = null)
{
var index = 0;
fastEma ??= TryGetDecimal(values[index++], out var fast) ? fast : null;
slowEma ??= TryGetDecimal(values[index++], out var slow) ? slow : null;
index++;
laguerre ??= TryGetDecimal(values[index++], out var lag) ? lag : null;
_previousFastEma = fastEma ?? _previousFastEma;
_previousSlowEma = slowEma ?? _previousSlowEma;
_previousLaguerre = laguerre ?? _previousLaguerre;
}
private bool IsMonotonic(decimal[] values, bool descending)
{
for (var i = 0; i < values.Length - 1; i++)
{
if (descending)
{
if (values[i] < values[i + 1])
return false;
}
else
{
if (values[i] > values[i + 1])
return false;
}
}
return true;
}
private static bool TryGetDecimal(IIndicatorValue value, out decimal result)
{
if (!value.IsFinal)
{
result = default;
return false;
}
result = value.ToDecimal();
return true;
}
private Unit ToAbsoluteUnit(decimal points)
{
if (points <= 0m || _pointValue <= 0m)
return null;
return new Unit(points * _pointValue, UnitTypes.Absolute);
}
}
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
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
ExponentialMovingAverage,
MovingAverageConvergenceDivergenceSignal,
AdaptiveLaguerreFilter,
MoneyFlowIndex,
)
class trend_follower_rainbow_strategy(Strategy):
def __init__(self):
super(trend_follower_rainbow_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetDisplay("Order Volume", "Base order volume", "Trading")
self._take_profit_points = self.Param("TakeProfitPoints", 17.0) \
.SetDisplay("Take Profit (pts)", "Distance in price steps for take profit", "Risk Management")
self._stop_loss_points = self.Param("StopLossPoints", 30.0) \
.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop loss", "Risk Management")
self._trailing_stop_points = self.Param("TrailingStopPoints", 45.0) \
.SetDisplay("Trailing Stop (pts)", "Distance in price steps for trailing stop", "Risk Management")
self._trading_start_hour = self.Param("TradingStartHour", 1) \
.SetDisplay("Start Hour", "Hour when trading window opens", "Trading Schedule")
self._trading_end_hour = self.Param("TradingEndHour", 23) \
.SetDisplay("End Hour", "Hour when trading window closes", "Trading Schedule")
self._fast_ema_length = self.Param("FastEmaLength", 4) \
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 8) \
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
self._macd_fast_length = self.Param("MacdFastLength", 5) \
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
self._macd_slow_length = self.Param("MacdSlowLength", 35) \
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
self._macd_signal_length = self.Param("MacdSignalLength", 5) \
.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Primary candle series", "General")
self._previous_fast_ema = None
self._previous_slow_ema = None
self._point_value = 0.0
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def TradingStartHour(self):
return self._trading_start_hour.Value
@property
def TradingEndHour(self):
return self._trading_end_hour.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def MacdFastLength(self):
return self._macd_fast_length.Value
@property
def MacdSlowLength(self):
return self._macd_slow_length.Value
@property
def MacdSignalLength(self):
return self._macd_signal_length.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(trend_follower_rainbow_strategy, self).OnStarted2(time)
ps = self.Security.PriceStep if self.Security is not None else None
self._point_value = float(ps) if ps is not None else 1.0
self.Volume = float(self.OrderVolume)
tp_pts = float(self.TakeProfitPoints)
sl_pts = float(self.StopLossPoints)
trailing_pts = float(self.TrailingStopPoints)
tp = Unit(tp_pts * self._point_value, UnitTypes.Absolute) if tp_pts > 0 and self._point_value > 0 else None
sl = Unit(sl_pts * self._point_value, UnitTypes.Absolute) if sl_pts > 0 and self._point_value > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl, isStopTrailing=(trailing_pts > 0), useMarketOrders=True)
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.FastEmaLength
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.SlowEmaLength
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.MacdFastLength
macd.Macd.LongMa.Length = self.MacdSlowLength
macd.SignalMa.Length = self.MacdSignalLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(ema_fast, ema_slow, macd, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, macd_val):
if candle.State != CandleStates.Finished:
return
hour = candle.CloseTime.Hour
if hour <= self.TradingStartHour or hour >= self.TradingEndHour:
self._update_prev(fast_val, slow_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._update_prev(fast_val, slow_val)
return
if fast_val.IsEmpty or slow_val.IsEmpty:
self._update_prev(fast_val, slow_val)
return
fast_ema = float(fast_val)
slow_ema = float(slow_val)
if not macd_val.IsFinal:
self._update_prev_values(fast_ema, slow_ema)
return
macd_main = macd_val.Macd
macd_signal = macd_val.Signal
if macd_main is None or macd_signal is None:
self._update_prev_values(fast_ema, slow_ema)
return
macd_main = float(macd_main)
macd_signal = float(macd_signal)
ema_cross_up = (self._previous_fast_ema is not None and self._previous_slow_ema is not None
and self._previous_fast_ema < self._previous_slow_ema and fast_ema > slow_ema)
ema_cross_down = (self._previous_fast_ema is not None and self._previous_slow_ema is not None
and self._previous_fast_ema > self._previous_slow_ema and fast_ema < slow_ema)
macd_bullish = macd_main > 0 and macd_signal > 0
macd_bearish = macd_main < 0 and macd_signal < 0
if ema_cross_up and macd_bullish and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
elif ema_cross_down and macd_bearish and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._previous_fast_ema = fast_ema
self._previous_slow_ema = slow_ema
def _update_prev(self, fast_val, slow_val):
if not fast_val.IsEmpty:
self._previous_fast_ema = float(fast_val)
if not slow_val.IsEmpty:
self._previous_slow_ema = float(slow_val)
def _update_prev_values(self, fast_ema, slow_ema):
self._previous_fast_ema = fast_ema
self._previous_slow_ema = slow_ema
def OnReseted(self):
super(trend_follower_rainbow_strategy, self).OnReseted()
self._previous_fast_ema = None
self._previous_slow_ema = None
def CreateClone(self):
return trend_follower_rainbow_strategy()