Стратегия MA Crossover Multi Timeframe
Стратегия повторяет идею оригинального советника MA Crossover для MetaTrader 4. Сравниваются две скользящие средние, которые могут строиться по разным таймфреймам. Пересечение быстрый MA выше медленного открывает длинную позицию, пересечение вниз – короткую. Дополнительные фильтры управляют допустимым направлением торговли, торговым расписанием и защитой по просадке. Стоп-лосс, тейк-профит и трейлинг реализованы "скрыто", как и в версии MQL.
Логика работы
- Подписка на два потока свечей (текущий и предыдущий таймфреймы) и расчёт выбранного типа скользящих средних.
- Применение настроенных сдвигов по количеству завершённых баров перед сравнением значений.
- Игнорирование незавершённых свечей и ожидание формирования обоих индикаторов.
- Пропуск торговли вне заданного окна по дням и времени или при срабатывании защиты по equity.
- При бычьем пересечении:
- Опционально закрыть короткую позицию, если
ClosePositionsOnCross = true. - Открыть длинную позицию, если разрешена торговля в лонг.
- Опционально закрыть короткую позицию, если
- При медвежьем пересечении:
- Опционально закрыть длинную позицию, если
ClosePositionsOnCross = true. - Открыть короткую позицию, если разрешена торговля в шорт.
- Опционально закрыть длинную позицию, если
- Управление открытой позицией с помощью стоп-лосса, тейк-профита и трейлинга, заданных в процентах от цены входа.
Параметры
| Параметр | Описание |
|---|---|
AllowedDirection |
Ограничение направления торговли (LongOnly, ShortOnly, LongAndShort). |
ClosePositionsOnCross |
Закрывать противоположную позицию при новом пересечении. |
MaType |
Тип вычисления скользящей средней (Simple, Exponential, Smoothed, Weighted). |
CurrentMaPeriod |
Период быстрой скользящей. |
PreviousPeriodAddition |
Добавка к периоду медленной скользящей (PreviousMaPeriod = CurrentMaPeriod + addition). |
CurrentShift / PreviousShift |
Сдвиг значений скользящих по числу завершённых баров. |
CurrentCandleType / PreviousCandleType |
Свечные данные для расчёта быстрой и медленной скользящих. |
StopLossPercent |
Размер стоп-лосса в процентах от цены входа (скрытое закрытие). |
TrailingStopPercent |
Процент трейлинга, рассчитываемый от лучшей достигнутой цены. |
TakeProfitPercent |
Размер тейк-профита в процентах от цены входа (скрытое закрытие). |
StartDay / EndDay |
Фильтр по дням недели. |
StartTime / EndTime |
Временное окно внутри дня для открытия новых сделок. |
ClosePositionsOnMinEquity |
Закрывать позиции при срабатывании защиты по equity. |
MinimumEquityPercent |
Минимально допустимый процент от стартовой стоимости портфеля. |
Управление рисками
- Стоп-лосс, тейк-профит и трейлинг рассчитываются внутри стратегии и исполняются рыночными заявками, что повторяет скрытую защиту из MQL-версии.
MinimumEquityPercentфиксирует первоначальную стоимость портфеля и может вынудить закрыть все позиции при падении equity ниже порога.- Размер позиции задаётся свойством
Strategy.Volume. По умолчанию объём равен1.
Практические рекомендации
- Необходимо обеспечить поставку свечей для обоих выбранных таймфреймов.
- Даже при совпадении таймфреймов стратегия создаёт две подписки для симметричной обработки сигналов.
- Защитные заявки не выставляются в стакан, так как выходы исполняются рыночными приказами.
- Параметры соответствуют ключевым настройкам советника в MQL; функции усреднения и хеджирования не перенесены из-за особенностей API StockSharp.
Отличия от исходного MQL-кода
- Не реализованы опции усреднения (
Average_Up,Average_Down) и хеджирование. - Защита по equity основана на значении портфеля в StockSharp, а не на свободной марже брокера.
- Выходы по риску выполняются на закрытии свечи рыночными приказами и не видны в заявках.
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;
public class MaCrossoverMultiTimeframeStrategy : Strategy
{
private readonly StrategyParam<TradeDirectionOptions> _allowedDirection;
private readonly StrategyParam<bool> _closeOnCross;
private readonly StrategyParam<MovingAverageTypeOptions> _maType;
private readonly StrategyParam<int> _currentPeriod;
private readonly StrategyParam<int> _previousPeriodAdd;
private readonly StrategyParam<int> _currentShift;
private readonly StrategyParam<int> _previousShift;
private readonly StrategyParam<DataType> _currentCandleType;
private readonly StrategyParam<DataType> _previousCandleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _trailingStopPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DayOfWeek> _startDay;
private readonly StrategyParam<DayOfWeek> _endDay;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _endTime;
private readonly StrategyParam<bool> _closeOnMinEquity;
private readonly StrategyParam<decimal> _minimumEquityPercent;
private IIndicator _currentMaIndicator;
private IIndicator _previousMaIndicator;
private readonly Queue<decimal> _currentShiftBuffer = new();
private readonly Queue<decimal> _previousShiftBuffer = new();
private decimal? _currentMaValue;
private decimal? _previousMaValue;
private bool? _wasCurrentAbovePrevious;
private decimal _entryPrice;
private decimal _highestPrice;
private decimal _lowestPrice;
private decimal _previousPosition;
private decimal? _initialPortfolioValue;
/// <summary>
/// Allowed trade direction.
/// </summary>
public TradeDirectionOptions AllowedDirection
{
get => _allowedDirection.Value;
set => _allowedDirection.Value = value;
}
/// <summary>
/// Close opposite positions when a crossover happens.
/// </summary>
public bool ClosePositionsOnCross
{
get => _closeOnCross.Value;
set => _closeOnCross.Value = value;
}
/// <summary>
/// Moving average calculation type.
/// </summary>
public MovingAverageTypeOptions MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Period for the current timeframe moving average.
/// </summary>
public int CurrentMaPeriod
{
get => _currentPeriod.Value;
set => _currentPeriod.Value = value;
}
/// <summary>
/// Additional length added to the previous moving average.
/// </summary>
public int PreviousPeriodAddition
{
get => _previousPeriodAdd.Value;
set => _previousPeriodAdd.Value = value;
}
/// <summary>
/// Shift applied to the current moving average.
/// </summary>
public int CurrentShift
{
get => _currentShift.Value;
set => _currentShift.Value = value;
}
/// <summary>
/// Shift applied to the previous moving average.
/// </summary>
public int PreviousShift
{
get => _previousShift.Value;
set => _previousShift.Value = value;
}
/// <summary>
/// Candle type for the current moving average.
/// </summary>
public DataType CurrentCandleType
{
get => _currentCandleType.Value;
set => _currentCandleType.Value = value;
}
/// <summary>
/// Candle type for the previous moving average.
/// </summary>
public DataType PreviousCandleType
{
get => _previousCandleType.Value;
set => _previousCandleType.Value = value;
}
/// <summary>
/// Stop-loss percentage relative to the entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Trailing stop percentage.
/// </summary>
public decimal TrailingStopPercent
{
get => _trailingStopPercent.Value;
set => _trailingStopPercent.Value = value;
}
/// <summary>
/// Take-profit percentage relative to the entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// First trading day of the schedule.
/// </summary>
public DayOfWeek StartDay
{
get => _startDay.Value;
set => _startDay.Value = value;
}
/// <summary>
/// Last trading day of the schedule.
/// </summary>
public DayOfWeek EndDay
{
get => _endDay.Value;
set => _endDay.Value = value;
}
/// <summary>
/// Start time of the trading window.
/// </summary>
public TimeSpan StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// End time of the trading window.
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Close all positions when the equity guard is triggered.
/// </summary>
public bool ClosePositionsOnMinEquity
{
get => _closeOnMinEquity.Value;
set => _closeOnMinEquity.Value = value;
}
/// <summary>
/// Minimum equity percentage relative to the initial portfolio value.
/// </summary>
public decimal MinimumEquityPercent
{
get => _minimumEquityPercent.Value;
set => _minimumEquityPercent.Value = value;
}
/// <summary>
/// Period calculated for the previous moving average.
/// </summary>
public int PreviousMaPeriod => Math.Max(1, CurrentMaPeriod + PreviousPeriodAddition);
/// <summary>
/// Initializes the strategy parameters.
/// </summary>
public MaCrossoverMultiTimeframeStrategy()
{
Volume = 1;
_allowedDirection = Param(nameof(AllowedDirection), TradeDirectionOptions.LongAndShort)
.SetDisplay("Trade Direction", "Allowed direction for opening positions", "Trading");
_closeOnCross = Param(nameof(ClosePositionsOnCross), true)
.SetDisplay("Close on Cross", "Close existing opposite positions when moving averages cross", "Trading");
_maType = Param(nameof(MaType), MovingAverageTypeOptions.Exponential)
.SetDisplay("MA Type", "Moving average calculation method", "Indicators");
_currentPeriod = Param(nameof(CurrentMaPeriod), 42)
.SetGreaterThanZero()
.SetDisplay("Current MA Period", "Length of the faster moving average", "Indicators")
.SetOptimize(10, 120, 5);
_previousPeriodAdd = Param(nameof(PreviousPeriodAddition), 10)
.SetNotNegative()
.SetDisplay("Previous MA Extra Length", "Additional length added to the slower moving average", "Indicators")
.SetOptimize(0, 50, 5);
_currentShift = Param(nameof(CurrentShift), 0)
.SetNotNegative()
.SetDisplay("Current MA Shift", "Number of bars to shift the faster moving average", "Indicators");
_previousShift = Param(nameof(PreviousShift), 2)
.SetNotNegative()
.SetDisplay("Previous MA Shift", "Number of bars to shift the slower moving average", "Indicators");
_currentCandleType = Param(nameof(CurrentCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Current Candle", "Timeframe used for the faster moving average", "Data");
_previousCandleType = Param(nameof(PreviousCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Previous Candle", "Timeframe used for the slower moving average", "Data");
_stopLossPercent = Param(nameof(StopLossPercent), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "Risk")
.SetOptimize(0m, 10m, 1m);
_trailingStopPercent = Param(nameof(TrailingStopPercent), 0m)
.SetNotNegative()
.SetDisplay("Trailing Stop %", "Trailing stop percentage applied to the best price", "Risk")
.SetOptimize(0m, 10m, 1m);
_takeProfitPercent = Param(nameof(TakeProfitPercent), 0m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take-profit percentage from the entry price", "Risk")
.SetOptimize(0m, 20m, 1m);
_startDay = Param(nameof(StartDay), DayOfWeek.Monday)
.SetDisplay("Start Day", "First day when trading is allowed", "Schedule");
_endDay = Param(nameof(EndDay), DayOfWeek.Friday)
.SetDisplay("End Day", "Last day when trading is allowed", "Schedule");
_startTime = Param(nameof(StartTime), TimeSpan.Zero)
.SetDisplay("Start Time", "Daily time when the strategy begins trading", "Schedule");
_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
.SetDisplay("End Time", "Daily time when the strategy stops opening new trades", "Schedule");
_closeOnMinEquity = Param(nameof(ClosePositionsOnMinEquity), true)
.SetDisplay("Close on Equity Guard", "Close positions when equity drops below the threshold", "Risk");
_minimumEquityPercent = Param(nameof(MinimumEquityPercent), 0m)
.SetNotNegative()
.SetDisplay("Minimum Equity %", "Minimum equity percentage relative to the initial value", "Risk")
.SetOptimize(0m, 100m, 5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security == null)
yield break;
yield return (Security, CurrentCandleType);
if (!Equals(PreviousCandleType, CurrentCandleType))
yield return (Security, PreviousCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentMaIndicator?.Reset();
_previousMaIndicator?.Reset();
_currentShiftBuffer.Clear();
_previousShiftBuffer.Clear();
_currentMaValue = null;
_previousMaValue = null;
_wasCurrentAbovePrevious = null;
ResetPositionState();
_previousPosition = 0m;
_initialPortfolioValue = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_initialPortfolioValue = Portfolio?.CurrentValue;
// Remember the starting equity for the guard logic.
_currentMaIndicator = CreateMovingAverage(MaType, CurrentMaPeriod);
_previousMaIndicator = CreateMovingAverage(MaType, PreviousMaPeriod);
var currentSubscription = SubscribeCandles(CurrentCandleType);
// Bind the fast moving average to the current timeframe.
currentSubscription.Bind(_currentMaIndicator, OnCurrentCandle).Start();
var previousSubscription = SubscribeCandles(PreviousCandleType);
// Bind the slow moving average to the configured timeframe.
previousSubscription.Bind(_previousMaIndicator, OnPreviousCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, currentSubscription);
DrawIndicator(area, _currentMaIndicator);
DrawIndicator(area, _previousMaIndicator);
DrawOwnTrades(area);
}
}
private void OnCurrentCandle(ICandleMessage candle, decimal maValue)
{
// Process only completed candles to avoid premature reactions.
if (candle.State != CandleStates.Finished)
return;
if (!_currentMaIndicator.IsFormed)
return;
var shifted = ApplyShift(CurrentShift, _currentShiftBuffer, maValue);
if (shifted == null)
return;
_currentMaValue = shifted;
if (!CheckFreeEquityGuard())
return;
ManagePosition(candle);
TryProcessSignal(candle);
}
private void OnPreviousCandle(ICandleMessage candle, decimal maValue)
{
// Update the reference moving average from the second timeframe.
if (candle.State != CandleStates.Finished)
return;
if (!_previousMaIndicator.IsFormed)
return;
var shifted = ApplyShift(PreviousShift, _previousShiftBuffer, maValue);
if (shifted == null)
return;
_previousMaValue = shifted;
}
private void TryProcessSignal(ICandleMessage candle)
{
// Ensure that both moving averages are available and trading is allowed.
if (_currentMaValue == null || _previousMaValue == null)
return;
if (!IsWithinTradingWindow(candle.OpenTime))
return;
var isCurrentAbove = _currentMaValue.Value > _previousMaValue.Value;
if (_wasCurrentAbovePrevious == null)
{
_wasCurrentAbovePrevious = isCurrentAbove;
return;
}
if (_wasCurrentAbovePrevious == isCurrentAbove)
return;
if (isCurrentAbove)
{
HandleBullishCross(candle);
}
else
{
HandleBearishCross(candle);
}
_wasCurrentAbovePrevious = isCurrentAbove;
}
private void HandleBullishCross(ICandleMessage candle)
{
// Prevent duplicate entries and respect direction filters.
if (!IsLongAllowed())
return;
if (Position > 0)
return;
var volume = Volume;
if (volume <= 0m)
volume = 1m;
if (Position < 0)
{
if (!ClosePositionsOnCross)
return;
volume += Math.Abs(Position);
}
BuyMarket(volume);
}
private void HandleBearishCross(ICandleMessage candle)
{
// Prevent duplicate entries and respect direction filters.
if (!IsShortAllowed())
return;
if (Position < 0)
return;
var volume = Volume;
if (volume <= 0m)
volume = 1m;
if (Position > 0)
{
if (!ClosePositionsOnCross)
return;
volume += Math.Abs(Position);
}
SellMarket(volume);
}
private void ManagePosition(ICandleMessage candle)
{
// Translate percentage-based risk settings into market exits.
if (Position == 0 || _entryPrice <= 0m)
return;
var stopLoss = StopLossPercent / 100m;
var takeProfit = TakeProfitPercent / 100m;
var trailing = TrailingStopPercent / 100m;
var closePrice = candle.ClosePrice;
if (Position > 0)
{
if (closePrice > _highestPrice)
_highestPrice = closePrice;
if (stopLoss > 0m)
{
var stopPrice = _entryPrice * (1m - stopLoss);
if (closePrice <= stopPrice)
{
SellMarket(Math.Abs(Position));
return;
}
}
if (takeProfit > 0m)
{
var targetPrice = _entryPrice * (1m + takeProfit);
if (closePrice >= targetPrice)
{
SellMarket(Math.Abs(Position));
return;
}
}
if (trailing > 0m && _highestPrice > 0m)
{
var trailingPrice = _highestPrice * (1m - trailing);
if (closePrice <= trailingPrice)
{
SellMarket(Math.Abs(Position));
return;
}
}
}
else if (Position < 0)
{
if (_lowestPrice == 0m || closePrice < _lowestPrice)
_lowestPrice = closePrice;
if (stopLoss > 0m)
{
var stopPrice = _entryPrice * (1m + stopLoss);
if (closePrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
return;
}
}
if (takeProfit > 0m)
{
var targetPrice = _entryPrice * (1m - takeProfit);
if (closePrice <= targetPrice)
{
BuyMarket(Math.Abs(Position));
return;
}
}
if (trailing > 0m && _lowestPrice > 0m)
{
var trailingPrice = _lowestPrice * (1m + trailing);
if (closePrice >= trailingPrice)
{
BuyMarket(Math.Abs(Position));
return;
}
}
}
}
private bool CheckFreeEquityGuard()
{
// Abort new trades if the equity guard has been triggered.
var threshold = MinimumEquityPercent;
if (threshold <= 0m)
return true;
if (_initialPortfolioValue == null || _initialPortfolioValue <= 0m)
return true;
var currentValue = Portfolio?.CurrentValue;
if (currentValue == null)
return true;
var minimumEquity = _initialPortfolioValue.Value * (threshold / 100m);
if (currentValue.Value > minimumEquity)
return true;
if (ClosePositionsOnMinEquity && Position != 0)
{
CloseAllPositions();
}
return false;
}
private void CloseAllPositions()
{
// Exit using market orders because the protection stays hidden.
if (Position > 0)
SellMarket(Math.Abs(Position));
else if (Position < 0)
BuyMarket(Math.Abs(Position));
}
private bool IsWithinTradingWindow(DateTimeOffset time)
{
var day = time.DayOfWeek;
var startDay = StartDay;
var endDay = EndDay;
var withinDays = startDay <= endDay
? day >= startDay && day <= endDay
: day >= startDay || day <= endDay;
if (!withinDays)
return false;
var startTime = StartTime;
var endTime = EndTime;
var timeOfDay = time.TimeOfDay;
return startTime <= endTime
? timeOfDay >= startTime && timeOfDay <= endTime
: timeOfDay >= startTime || timeOfDay <= endTime;
}
private static decimal? ApplyShift(int shift, Queue<decimal> buffer, decimal value)
{
// Maintain a small buffer to emulate the MQL shift parameter.
if (shift <= 0)
{
buffer.Clear();
return value;
}
buffer.Enqueue(value);
while (buffer.Count > shift + 1)
buffer.Dequeue();
return buffer.Count == shift + 1 ? buffer.Peek() : null;
}
private static IIndicator CreateMovingAverage(MovingAverageTypeOptions type, int length)
{
return type switch
{
MovingAverageTypeOptions.Simple => new SimpleMovingAverage { Length = length },
MovingAverageTypeOptions.Exponential => new ExponentialMovingAverage { Length = length },
MovingAverageTypeOptions.Smoothed => new SmoothedMovingAverage { Length = length },
MovingAverageTypeOptions.Weighted => new WeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
private bool IsLongAllowed() => AllowedDirection != TradeDirectionOptions.ShortOnly;
private bool IsShortAllowed() => AllowedDirection != TradeDirectionOptions.LongOnly;
private void ResetPositionState()
{
_entryPrice = 0m;
_highestPrice = 0m;
_lowestPrice = 0m;
_previousPosition = 0m;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
// Update the average entry price once fills arrive.
base.OnOwnTradeReceived(trade);
if (trade.Order.Security != Security)
return;
var currentPosition = Position;
if (_previousPosition <= 0m && currentPosition > 0m)
{
_entryPrice = trade.Trade.Price;
_highestPrice = trade.Trade.Price;
_lowestPrice = trade.Trade.Price;
}
else if (_previousPosition >= 0m && currentPosition < 0m)
{
_entryPrice = trade.Trade.Price;
_highestPrice = trade.Trade.Price;
_lowestPrice = trade.Trade.Price;
}
if (currentPosition > 0m && trade.Order.Side == Sides.Buy)
{
var totalVolume = Math.Abs(currentPosition);
var previousVolume = Math.Abs(_previousPosition > 0m ? _previousPosition : 0m);
var tradeVolume = trade.Trade.Volume;
if (totalVolume > 0m)
{
var weighted = (_entryPrice * previousVolume) + (trade.Trade.Price * tradeVolume);
_entryPrice = weighted / totalVolume;
}
if (trade.Trade.Price > _highestPrice)
_highestPrice = trade.Trade.Price;
}
else if (currentPosition < 0m && trade.Order.Side == Sides.Sell)
{
var totalVolume = Math.Abs(currentPosition);
var previousVolume = Math.Abs(_previousPosition < 0m ? _previousPosition : 0m);
var tradeVolume = trade.Trade.Volume;
if (totalVolume > 0m)
{
var weighted = (_entryPrice * previousVolume) + (trade.Trade.Price * tradeVolume);
_entryPrice = weighted / totalVolume;
}
if (_lowestPrice == 0m || trade.Trade.Price < _lowestPrice)
_lowestPrice = trade.Trade.Price;
}
_previousPosition = currentPosition;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position == 0m)
ResetPositionState();
}
public enum TradeDirectionOptions
{
LongOnly,
ShortOnly,
LongAndShort
}
public enum MovingAverageTypeOptions
{
Simple,
Exponential,
Smoothed,
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_crossover_multi_timeframe_strategy(Strategy):
"""
MA crossover with EMA fast/slow and cooldown.
Simplified single-timeframe version.
"""
def __init__(self):
super(ma_crossover_multi_timeframe_strategy, self).__init__()
self._fast_period = self.Param("CurrentMaPeriod", 42).SetDisplay("Fast MA", "Fast MA period", "Indicators")
self._slow_period = self.Param("SlowMaPeriod", 52).SetDisplay("Slow MA", "Slow MA period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 20).SetDisplay("Cooldown", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_crossover_multi_timeframe_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(ma_crossover_multi_timeframe_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_period.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
f = float(fast_val)
s = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = f
self._prev_slow = s
self._has_prev = True
return
if not self._has_prev:
self._prev_fast = f
self._prev_slow = s
self._has_prev = True
return
is_above = f > s
was_above = self._prev_fast > self._prev_slow
if is_above != was_above and self._cooldown == 0:
if is_above and self.Position <= 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif not is_above and self.Position >= 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_crossover_multi_timeframe_strategy()