Стратегия XROC2 VG с временным фильтром
Стратегия реализует эксперта MetaTrader Exp_XROC2_VG_Tm на базе высокоуровневого API StockSharp. Она строит две сглаженные кривые изменения цены (ROC) и открывает обратные позиции при их пересечениях. Дополнительно предусмотрены ограничения по времени торговли и фиксированные уровни риска, как в оригинальной реализации.
Логика торговли
- Из цены закрытия рассчитываются два значения ROC с независимыми периодами.
- Каждое значение сглаживается выбранным типом скользящей средней.
- Сигналы оцениваются по смещённому индексу бара (
SignalShift), что полностью повторяет поведение MQL-версии. - Если на предыдущем баре быстрая линия была выше медленной, а на сигнальном баре опустилась ниже, стратегия закрывает короткую позицию и при необходимости открывает длинную.
- Если на предыдущем баре быстрая линия была ниже медленной, а на сигнальном баре поднялась выше, стратегия закрывает длинную позицию и может открыть короткую.
- Вне разрешённого торгового окна система принудительно закрывает позицию и не открывает новые сделки, пока торговля запрещена.
Переход между направлениями происходит только после полного закрытия предыдущей позиции, что соответствует логике TradeAlgorithms.mqh.
Индикаторы
- Быстрый ROC — изменение цены за
RocPeriod1баров, сглаженное методомSmoothMethod1с длинойSmoothLength1. - Медленный ROC — аналогичный расчёт за
RocPeriod2баров с параметрамиSmoothMethod2иSmoothLength2. - Доступные типы сглаживания: простая, экспоненциальная, сглаженная (RMA) и взвешенная скользящие средние. Методики JJMA, VIDYA и AMA заменены экспоненциальным сглаживанием из стандартной библиотеки StockSharp.
Управление рисками
- Параметры
StopLossиTakeProfitзадают необязательные уровни выхода в абсолютных ценовых единицах. При достижении уровня позиция закрывается рыночной заявкой. OrderVolumeопределяет объём каждой новой позиции.- Закрытие по индикатору активно даже при отключённых защитных уровнях.
Временной фильтр
UseTimeFilterвключает или выключает контроль торговой сессии.StartTimeиEndTimeзадают временные границы. Если окончание раньше начала, окно разбивается на два участка по разные стороны от полуночи — так же, как в MQL.- При выходе из торгового окна стратегия закрывает позицию до того, как проверит новые сигналы.
Параметры
| Параметр | Описание |
|---|---|
CandleType |
Тип свечей для расчётов (по умолчанию 4 часа). |
RocPeriod1, RocPeriod2 |
Периоды быстрого и медленного ROC. |
SmoothLength1, SmoothLength2 |
Длины сглаживания для каждой линии. |
SmoothMethod1, SmoothMethod2 |
Типы скользящих средних. |
RocType |
Формула расчёта ROC: моментум, процентное изменение или отношение. |
SignalShift |
Количество баров назад, из которых берутся сигнальные значения. |
AllowBuyOpen, AllowSellOpen |
Разрешение на открытие длинных и коротких позиций. |
AllowBuyClose, AllowSellClose |
Разрешение на закрытие длинных и коротких позиций по сигналу. |
UseTimeFilter |
Включение временного фильтра. |
StartTime, EndTime |
Начало и конец торговой сессии. |
OrderVolume |
Объём новой позиции. |
StopLoss, TakeProfit |
Необязательные абсолютные уровни стоп-лосса и тейк-профита. |
Особенности реализации
- Вместо обращения к буферам индикатора хранятся короткие истории цен и сглаженных значений, что позволяет воспроизводить
SignalShiftбез использованияGetValue. - Режимы JJMA, VIDYA и AMA заменены на экспоненциальное сглаживание, чтобы опираться только на стандартные индикаторы 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;
/// <summary>
/// XROC2 VG with time filter strategy converted from MetaTrader 5.
/// </summary>
public class Xroc2VgTmStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rocPeriod1;
private readonly StrategyParam<int> _rocPeriod2;
private readonly StrategyParam<int> _smoothLength1;
private readonly StrategyParam<int> _smoothLength2;
private readonly StrategyParam<SmoothingMethods> _smoothMethod1;
private readonly StrategyParam<SmoothingMethods> _smoothMethod2;
private readonly StrategyParam<RocCalculationTypes> _rocType;
private readonly StrategyParam<int> _signalShift;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _allowBuyClose;
private readonly StrategyParam<bool> _allowSellClose;
private readonly StrategyParam<bool> _useTimeFilter;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _endTime;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly List<decimal> _closeHistory = new();
private readonly List<decimal> _fastHistory = new();
private readonly List<decimal> _slowHistory = new();
private IIndicator _smoothFast;
private IIndicator _smoothSlow;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
/// <summary>
/// Rate-of-change calculation mode.
/// </summary>
public enum RocCalculationTypes
{
/// <summary>Momentum (difference between closes).</summary>
Momentum,
/// <summary>Rate of change in percent.</summary>
RateOfChange,
/// <summary>Relative rate of change (fraction).</summary>
Percent,
/// <summary>Price ratio.</summary>
Ratio,
/// <summary>Price ratio scaled by 100.</summary>
RatioPercent
}
/// <summary>
/// Smoothing method used for ROC lines.
/// </summary>
public enum SmoothingMethods
{
/// <summary>Simple moving average.</summary>
Simple,
/// <summary>Exponential moving average.</summary>
Exponential,
/// <summary>Smoothed moving average.</summary>
Smoothed,
/// <summary>Weighted moving average.</summary>
Weighted
}
/// <summary>
/// Initializes a new instance of the <see cref="Xroc2VgTmStrategy"/> class.
/// </summary>
public Xroc2VgTmStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_rocPeriod1 = Param(nameof(RocPeriod1), 5)
.SetGreaterThanZero()
.SetDisplay("Fast ROC Period", "Lookback for the first ROC line", "Indicator")
;
_rocPeriod2 = Param(nameof(RocPeriod2), 10)
.SetGreaterThanZero()
.SetDisplay("Slow ROC Period", "Lookback for the second ROC line", "Indicator")
;
_smoothLength1 = Param(nameof(SmoothLength1), 5)
.SetGreaterThanZero()
.SetDisplay("Fast Smoothing", "Smoothing length for the first line", "Indicator");
_smoothLength2 = Param(nameof(SmoothLength2), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Smoothing", "Smoothing length for the second line", "Indicator");
_smoothMethod1 = Param(nameof(SmoothMethod1), SmoothingMethods.Exponential)
.SetDisplay("Fast Method", "Smoothing method for the first line", "Indicator");
_smoothMethod2 = Param(nameof(SmoothMethod2), SmoothingMethods.Exponential)
.SetDisplay("Slow Method", "Smoothing method for the second line", "Indicator");
_rocType = Param(nameof(RocType), RocCalculationTypes.Momentum)
.SetDisplay("ROC Mode", "Calculation used for rate of change", "Indicator");
_signalShift = Param(nameof(SignalShift), 0)
.SetNotNegative()
.SetDisplay("Signal Shift", "Bars back to read the signals", "Logic");
_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
.SetDisplay("Allow Long Entry", "Enable opening long positions", "Trading");
_allowSellOpen = Param(nameof(AllowSellOpen), true)
.SetDisplay("Allow Short Entry", "Enable opening short positions", "Trading");
_allowBuyClose = Param(nameof(AllowBuyClose), true)
.SetDisplay("Allow Long Exit", "Enable closing long positions by indicator", "Trading");
_allowSellClose = Param(nameof(AllowSellClose), true)
.SetDisplay("Allow Short Exit", "Enable closing short positions by indicator", "Trading");
_useTimeFilter = Param(nameof(UseTimeFilter), false)
.SetDisplay("Use Time Filter", "Restrict trading to a time window", "Timing");
_startTime = Param(nameof(StartTime), TimeSpan.Zero)
.SetDisplay("Start Time", "Session start time", "Timing");
_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
.SetDisplay("End Time", "Session end time", "Timing");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume for new positions", "Trading");
_stopLoss = Param(nameof(StopLoss), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Protective stop distance in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 0m)
.SetNotNegative()
.SetDisplay("Take Profit", "Target distance in price units", "Risk");
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Lookback of the first ROC line.
/// </summary>
public int RocPeriod1
{
get => _rocPeriod1.Value;
set => _rocPeriod1.Value = value;
}
/// <summary>
/// Lookback of the second ROC line.
/// </summary>
public int RocPeriod2
{
get => _rocPeriod2.Value;
set => _rocPeriod2.Value = value;
}
/// <summary>
/// Smoothing length applied to the first line.
/// </summary>
public int SmoothLength1
{
get => _smoothLength1.Value;
set => _smoothLength1.Value = value;
}
/// <summary>
/// Smoothing length applied to the second line.
/// </summary>
public int SmoothLength2
{
get => _smoothLength2.Value;
set => _smoothLength2.Value = value;
}
/// <summary>
/// Smoothing method for the first line.
/// </summary>
public SmoothingMethods SmoothMethod1
{
get => _smoothMethod1.Value;
set => _smoothMethod1.Value = value;
}
/// <summary>
/// Smoothing method for the second line.
/// </summary>
public SmoothingMethods SmoothMethod2
{
get => _smoothMethod2.Value;
set => _smoothMethod2.Value = value;
}
/// <summary>
/// Type of ROC calculation.
/// </summary>
public RocCalculationTypes RocType
{
get => _rocType.Value;
set => _rocType.Value = value;
}
/// <summary>
/// Number of bars back used for signal evaluation.
/// </summary>
public int SignalShift
{
get => _signalShift.Value;
set => _signalShift.Value = value;
}
/// <summary>
/// Enables long entries.
/// </summary>
public bool AllowBuyOpen
{
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
/// <summary>
/// Enables short entries.
/// </summary>
public bool AllowSellOpen
{
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
/// <summary>
/// Enables closing long positions by indicator signals.
/// </summary>
public bool AllowBuyClose
{
get => _allowBuyClose.Value;
set => _allowBuyClose.Value = value;
}
/// <summary>
/// Enables closing short positions by indicator signals.
/// </summary>
public bool AllowSellClose
{
get => _allowSellClose.Value;
set => _allowSellClose.Value = value;
}
/// <summary>
/// Turns the time filter on or off.
/// </summary>
public bool UseTimeFilter
{
get => _useTimeFilter.Value;
set => _useTimeFilter.Value = value;
}
/// <summary>
/// Trading session start time.
/// </summary>
public TimeSpan StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Trading session end time.
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Order volume used for new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Protective stop distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take-profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeHistory.Clear();
_fastHistory.Clear();
_slowHistory.Clear();
_smoothFast = null;
_smoothSlow = null;
_longEntryPrice = null;
_shortEntryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_smoothFast = CreateSmoothingIndicator(SmoothMethod1, SmoothLength1);
_smoothSlow = CreateSmoothingIndicator(SmoothMethod2, SmoothLength2);
_closeHistory.Clear();
_fastHistory.Clear();
_slowHistory.Clear();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smoothFast);
DrawIndicator(area, _smoothSlow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var capacity = Math.Max(Math.Max(RocPeriod1, RocPeriod2) + SignalShift + 5, 8);
UpdateHistory(_closeHistory, candle.ClosePrice, capacity);
var fastRoc = CalculateRoc(RocPeriod1);
var slowRoc = CalculateRoc(RocPeriod2);
if (fastRoc is null || slowRoc is null)
return;
var fastValue = _smoothFast.Process(new DecimalIndicatorValue(_smoothFast, fastRoc.Value, candle.OpenTime) { IsFinal = true });
var slowValue = _smoothSlow.Process(new DecimalIndicatorValue(_smoothSlow, slowRoc.Value, candle.OpenTime) { IsFinal = true });
// Skip until we have enough data for both smoothing indicators
var fastDecimal = fastValue.GetValue<decimal>();
var slowDecimal = slowValue.GetValue<decimal>();
var historyCapacity = SignalShift + 3;
UpdateHistory(_fastHistory, fastDecimal, historyCapacity);
UpdateHistory(_slowHistory, slowDecimal, historyCapacity);
if (_fastHistory.Count <= SignalShift + 1 || _slowHistory.Count <= SignalShift + 1)
return;
var fastCurrent = _fastHistory[SignalShift];
var fastPrevious = _fastHistory[SignalShift + 1];
var slowCurrent = _slowHistory[SignalShift];
var slowPrevious = _slowHistory[SignalShift + 1];
var buyOpenSignal = AllowBuyOpen && fastPrevious <= slowPrevious && fastCurrent > slowCurrent;
var sellOpenSignal = AllowSellOpen && fastPrevious >= slowPrevious && fastCurrent < slowCurrent;
var buyCloseSignal = AllowBuyClose && fastCurrent < slowCurrent;
var sellCloseSignal = AllowSellClose && fastCurrent > slowCurrent;
var tradeAllowed = !UseTimeFilter || IsWithinTradeWindow(candle.OpenTime);
if (UseTimeFilter && !tradeAllowed && Position != 0)
{
if (Position > 0)
SellMarket();
else
BuyMarket();
ResetPositionState();
return;
}
if (TryApplyRiskManagement(candle))
return;
if (sellCloseSignal && Position < 0)
{
BuyMarket();
ResetPositionState();
return;
}
if (buyCloseSignal && Position > 0)
{
SellMarket();
ResetPositionState();
return;
}
if (!tradeAllowed)
return;
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
if (Position != 0)
return;
if (buyOpenSignal)
{
BuyMarket();
_longEntryPrice = candle.ClosePrice;
_shortEntryPrice = null;
}
else if (sellOpenSignal)
{
SellMarket();
_shortEntryPrice = candle.ClosePrice;
_longEntryPrice = null;
}
}
private bool TryApplyRiskManagement(ICandleMessage candle)
{
if (StopLoss <= 0m && TakeProfit <= 0m)
return false;
if (Position > 0 && _longEntryPrice is decimal longEntry)
{
if (StopLoss > 0m)
{
var stopLevel = longEntry - StopLoss;
if (candle.LowPrice <= stopLevel)
{
SellMarket();
ResetPositionState();
return true;
}
}
if (TakeProfit > 0m)
{
var targetLevel = longEntry + TakeProfit;
if (candle.HighPrice >= targetLevel)
{
SellMarket();
ResetPositionState();
return true;
}
}
}
else if (Position < 0 && _shortEntryPrice is decimal shortEntry)
{
if (StopLoss > 0m)
{
var stopLevel = shortEntry + StopLoss;
if (candle.HighPrice >= stopLevel)
{
BuyMarket();
ResetPositionState();
return true;
}
}
if (TakeProfit > 0m)
{
var targetLevel = shortEntry - TakeProfit;
if (candle.LowPrice <= targetLevel)
{
BuyMarket();
ResetPositionState();
return true;
}
}
}
return false;
}
private decimal? CalculateRoc(int period)
{
if (period <= 0 || _closeHistory.Count <= period)
return null;
var current = _closeHistory[0];
var previous = _closeHistory[period];
if (previous == 0m && (RocType == RocCalculationTypes.RateOfChange || RocType == RocCalculationTypes.Percent || RocType == RocCalculationTypes.Ratio || RocType == RocCalculationTypes.RatioPercent))
return null;
return RocType switch
{
RocCalculationTypes.Momentum => current - previous,
RocCalculationTypes.RateOfChange => previous == 0m ? null : (decimal?)((current / previous) - 1m) * 100m,
RocCalculationTypes.Percent => previous == 0m ? null : (decimal?)((current - previous) / previous),
RocCalculationTypes.Ratio => previous == 0m ? null : (decimal?)(current / previous),
RocCalculationTypes.RatioPercent => previous == 0m ? null : (decimal?)(current / previous * 100m),
_ => current - previous
};
}
private bool IsWithinTradeWindow(DateTimeOffset time)
{
var currentMinutes = time.TimeOfDay.TotalMinutes;
var startMinutes = StartTime.TotalMinutes;
var endMinutes = EndTime.TotalMinutes;
if (startMinutes < endMinutes)
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
if (startMinutes > endMinutes)
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
return false;
}
private static void UpdateHistory(List<decimal> history, decimal value, int capacity)
{
history.Insert(0, value);
if (history.Count > capacity)
history.RemoveAt(history.Count - 1);
}
private void ResetPositionState()
{
_longEntryPrice = null;
_shortEntryPrice = null;
}
private static IIndicator CreateSmoothingIndicator(SmoothingMethods method, int length)
{
IIndicator indicator = method switch
{
SmoothingMethods.Simple => new SMA { Length = length },
SmoothingMethods.Smoothed => new EMA { Length = length },
SmoothingMethods.Weighted => new SMA { Length = length },
_ => new EMA { Length = length }
};
return indicator;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage
)
from indicator_extensions import *
class xroc2_vg_tm_strategy(Strategy):
"""XROC2 VG with time filter: dual smoothed ROC crossover strategy."""
def __init__(self):
super(xroc2_vg_tm_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe", "General")
self._roc_period1 = self.Param("RocPeriod1", 5) \
.SetGreaterThanZero() \
.SetDisplay("Fast ROC Period", "Lookback for fast ROC", "Indicator")
self._roc_period2 = self.Param("RocPeriod2", 10) \
.SetGreaterThanZero() \
.SetDisplay("Slow ROC Period", "Lookback for slow ROC", "Indicator")
self._smooth_len1 = self.Param("SmoothLength1", 5) \
.SetGreaterThanZero() \
.SetDisplay("Fast Smoothing", "Smoothing for fast line", "Indicator")
self._smooth_len2 = self.Param("SmoothLength2", 5) \
.SetGreaterThanZero() \
.SetDisplay("Slow Smoothing", "Smoothing for slow line", "Indicator")
self._signal_shift = self.Param("SignalShift", 0) \
.SetDisplay("Signal Shift", "Bars back to read signals", "Logic")
self._allow_buy_open = self.Param("AllowBuyOpen", True) \
.SetDisplay("Allow Long Entry", "Enable long positions", "Trading")
self._allow_sell_open = self.Param("AllowSellOpen", True) \
.SetDisplay("Allow Short Entry", "Enable short positions", "Trading")
self._allow_buy_close = self.Param("AllowBuyClose", True) \
.SetDisplay("Allow Long Exit", "Enable closing longs", "Trading")
self._allow_sell_close = self.Param("AllowSellClose", True) \
.SetDisplay("Allow Short Exit", "Enable closing shorts", "Trading")
self._use_time_filter = self.Param("UseTimeFilter", False) \
.SetDisplay("Use Time Filter", "Restrict trading to time window", "Timing")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Session start hour", "Timing")
self._start_minute = self.Param("StartMinute", 0) \
.SetDisplay("Start Minute", "Session start minute", "Timing")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("End Hour", "Session end hour", "Timing")
self._end_minute = self.Param("EndMinute", 59) \
.SetDisplay("End Minute", "Session end minute", "Timing")
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Volume for new positions", "Trading")
self._stop_loss = self.Param("StopLoss", 0.0) \
.SetDisplay("Stop Loss", "Stop distance in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 0.0) \
.SetDisplay("Take Profit", "Target distance in price units", "Risk")
self._close_history = []
self._fast_history = []
self._slow_history = []
self._smooth_fast = None
self._smooth_slow = None
self._long_entry = None
self._short_entry = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def RocPeriod1(self):
return self._roc_period1.Value
@property
def RocPeriod2(self):
return self._roc_period2.Value
@property
def SmoothLength1(self):
return self._smooth_len1.Value
@property
def SmoothLength2(self):
return self._smooth_len2.Value
@property
def SignalShift(self):
return self._signal_shift.Value
@property
def AllowBuyOpen(self):
return self._allow_buy_open.Value
@property
def AllowSellOpen(self):
return self._allow_sell_open.Value
@property
def AllowBuyClose(self):
return self._allow_buy_close.Value
@property
def AllowSellClose(self):
return self._allow_sell_close.Value
@property
def UseTimeFilter(self):
return self._use_time_filter.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def StartMinute(self):
return self._start_minute.Value
@property
def EndHour(self):
return self._end_hour.Value
@property
def EndMinute(self):
return self._end_minute.Value
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def StopLoss(self):
return self._stop_loss.Value
@property
def TakeProfit(self):
return self._take_profit.Value
def OnStarted2(self, time):
super(xroc2_vg_tm_strategy, self).OnStarted2(time)
self.Volume = self.OrderVolume
self._smooth_fast = ExponentialMovingAverage()
self._smooth_fast.Length = self.SmoothLength1
self._smooth_slow = ExponentialMovingAverage()
self._smooth_slow.Length = self.SmoothLength2
self._close_history = []
self._fast_history = []
self._slow_history = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
capacity = max(max(self.RocPeriod1, self.RocPeriod2) + self.SignalShift + 5, 8)
self._close_history.insert(0, close)
while len(self._close_history) > capacity:
self._close_history.pop()
fast_roc = self._calc_roc(self.RocPeriod1)
slow_roc = self._calc_roc(self.RocPeriod2)
if fast_roc is None or slow_roc is None:
return
fast_out = process_float(self._smooth_fast, fast_roc, candle.OpenTime, True)
slow_out = process_float(self._smooth_slow, slow_roc, candle.OpenTime, True)
fast_val = float(fast_out)
slow_val = float(slow_out)
hist_cap = self.SignalShift + 3
self._fast_history.insert(0, fast_val)
while len(self._fast_history) > hist_cap:
self._fast_history.pop()
self._slow_history.insert(0, slow_val)
while len(self._slow_history) > hist_cap:
self._slow_history.pop()
ss = self.SignalShift
if len(self._fast_history) <= ss + 1 or len(self._slow_history) <= ss + 1:
return
fc = self._fast_history[ss]
fp = self._fast_history[ss + 1]
sc = self._slow_history[ss]
sp = self._slow_history[ss + 1]
buy_open = self.AllowBuyOpen and fp <= sp and fc > sc
sell_open = self.AllowSellOpen and fp >= sp and fc < sc
buy_close = self.AllowBuyClose and fc < sc
sell_close = self.AllowSellClose and fc > sc
in_window = (not self.UseTimeFilter) or self._in_trade_window(candle.OpenTime)
if self.UseTimeFilter and not in_window and self.Position != 0:
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._reset_state()
return
if self._try_risk(candle):
return
if sell_close and self.Position < 0:
self.BuyMarket()
self._reset_state()
return
if buy_close and self.Position > 0:
self.SellMarket()
self._reset_state()
return
if not in_window:
return
if self.Position != 0:
return
if buy_open:
self.BuyMarket()
self._long_entry = close
self._short_entry = None
elif sell_open:
self.SellMarket()
self._short_entry = close
self._long_entry = None
def _calc_roc(self, period):
if period <= 0 or len(self._close_history) <= period:
return None
current = self._close_history[0]
previous = self._close_history[period]
# Momentum mode (default)
return current - previous
def _try_risk(self, candle):
sl = float(self.StopLoss)
tp = float(self.TakeProfit)
if sl <= 0 and tp <= 0:
return False
if self.Position > 0 and self._long_entry is not None:
if sl > 0 and float(candle.LowPrice) <= self._long_entry - sl:
self.SellMarket()
self._reset_state()
return True
if tp > 0 and float(candle.HighPrice) >= self._long_entry + tp:
self.SellMarket()
self._reset_state()
return True
elif self.Position < 0 and self._short_entry is not None:
if sl > 0 and float(candle.HighPrice) >= self._short_entry + sl:
self.BuyMarket()
self._reset_state()
return True
if tp > 0 and float(candle.LowPrice) <= self._short_entry - tp:
self.BuyMarket()
self._reset_state()
return True
return False
def _in_trade_window(self, time):
start = TimeSpan(self.StartHour, self.StartMinute, 0)
end = TimeSpan(self.EndHour, self.EndMinute, 0)
current = time.TimeOfDay
if start < end:
return current >= start and current < end
if start > end:
return current >= start or current < end
return False
def _reset_state(self):
self._long_entry = None
self._short_entry = None
def OnReseted(self):
super(xroc2_vg_tm_strategy, self).OnReseted()
self._close_history = []
self._fast_history = []
self._slow_history = []
self._smooth_fast = None
self._smooth_slow = None
self._reset_state()
def CreateClone(self):
return xroc2_vg_tm_strategy()