Стратегия Trend Follower Rainbow
Обзор
Trend Follower Rainbow — порт советника MetaTrader 4 «TrendFollowerRainbowMethodkyast773» на C#. Стратегия использует несколько уровней подтверждения, чтобы торговать в направлении сильного тренда и избегать флэта. Входы формируются комбинацией перекрёста EMA, подтверждения MACD, гистограммы Лагерра, фильтра Money Flow Index и строгой структуры «радужных» скользящих средних.
Логика торговли
- Торговое окно – сигналы анализируются только тогда, когда время закрытия свечи строго между заданными часами начала и окончания. Это повторяет фильтр оригинального советника, пропускавшего крайние часы сессии.
- Пересечение EMA – для покупки быстрая EMA (по умолчанию 4) должна пересечь снизу вверх медленную EMA (по умолчанию 8). Для продажи требуется обратное пересечение.
- Подтверждение MACD – значения MACD и сигнальной линии (по умолчанию 5/35/5) должны быть одновременно выше нуля для сделок на покупку и ниже нуля для сделок на продажу.
- Фильтр Лагерра – значение индикатора обязано пересечь вверх порог 0,15, чтобы разрешить лонг, и пересечь вниз 0,75, чтобы разрешить шорт.
- Структура радуги – пять пакетов экспоненциальных скользящих (по четыре EMA в каждом) должны быть упорядочены: для лонга — невозрастающе, для шорта — неубывающе. Это имитирует состояние RainbowMMA индикаторов.
- Фильтр MFI – индикатор Money Flow Index (период 14) должен находиться ниже 40 для покупок и выше 60 для продаж, чтобы не идти против потока капитала.
- Управление позицией – используются рыночные ордера. При появлении противоположного сигнала текущая позиция закрывается и открывается новая в обратном направлении.
Управление рисками
Стратегия применяет StartProtection из StockSharp:
- Take Profit и Stop Loss задаются в шагах цены, как в оригинальном советнике.
- Trailing Stop также указывается в шагах и активируется сразу после запуска защиты.
Параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
OrderVolume |
Базовый объём заявки. | 1 |
TakeProfitPoints |
Дистанция до тейк-профита в шагах цены. | 17 |
StopLossPoints |
Дистанция до стоп-лосса в шагах цены. | 30 |
TrailingStopPoints |
Дистанция до трейлинг-стопа в шагах цены. | 45 |
TradingStartHour |
Час (включительно), до которого сигналы игнорируются. | 1 |
TradingEndHour |
Час (включительно), начиная с которого сигналы игнорируются. | 23 |
FastEmaLength |
Период быстрой EMA. | 4 |
SlowEmaLength |
Период медленной EMA. | 8 |
MacdFastLength |
Период быстрой EMA в MACD. | 5 |
MacdSlowLength |
Период медленной EMA в MACD. | 35 |
MacdSignalLength |
Период сигнальной EMA в MACD. | 5 |
LaguerreGamma |
Коэффициент сглаживания фильтра Лагерра. | 0.7 |
LaguerreBuyThreshold |
Порог для открытия лонга. | 0.15 |
LaguerreSellThreshold |
Порог для открытия шорта. | 0.75 |
MfiPeriod |
Период Money Flow Index. | 14 |
MfiBuyLevel |
Максимальное значение MFI для лонга. | 40 |
MfiSellLevel |
Минимальное значение MFI для шорта. | 60 |
RainbowGroup{1..5}Base |
Базовые периоды пакетов радуги. Четыре EMA формируются добавлением смещений 0/2/4/6. | 5 / 13 / 21 / 34 / 55 |
CandleType |
Тип свечей, используемых стратегией (по умолчанию 5-минутные). | 5 минут |
Визуализация
Стратегия автоматически рисует:
- свечи выбранного инструмента;
- быстрые и медленные 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()