XROC2 VG Time Filter Strategy
This strategy replicates the MetaTrader expert advisor Exp_XROC2_VG_Tm using the StockSharp high-level API. It builds two smoothed rate-of-change (ROC) curves and opens contrarian trades when the faster curve crosses the slower one. A trading session filter and optional protective targets reproduce the original money-management settings.
Trading logic
- Two ROC values are calculated from the closing price using independent lookbacks.
- Each ROC stream is smoothed with a configurable moving-average method.
- Signals are evaluated on a shifted bar index, matching the original
SignalBarbehavior. - When the fast line was above the slow line on the prior bar but falls below it on the signal bar, the strategy closes any short position and may open a long position.
- When the fast line was below the slow line on the prior bar but rises above it on the signal bar, the strategy closes any long position and may open a short position.
- An optional trading window can flat all positions outside the allowed session before any new trades are placed.
The order side switches only after the previous position is fully closed, mimicking the MetaTrader trade algorithms.
Indicators
- Fast ROC – Momentum, percent or ratio of price change over
RocPeriod1bars, smoothed withSmoothMethod1and lengthSmoothLength1. - Slow ROC – Same calculation over
RocPeriod2bars, smoothed withSmoothMethod2and lengthSmoothLength2. - Supported smoothing methods: Simple, Exponential, Smoothed (RMA) and Weighted moving averages. The original JJMA/VIDYA/AMA options are approximated by exponential smoothing.
Risk management
StopLossandTakeProfitspecify optional fixed-distance exits in absolute price units. When either threshold is hit the position is closed immediately.OrderVolumedefines the size of all new positions.- Indicator-based exits may also flatten positions even if the protective targets are disabled.
Session filter
UseTimeFiltertoggles the time-of-day window.StartTime/EndTimespecify the session bounds. When the interval wraps around midnight the window is treated as two segments, exactly as in the MQL version.- If a position is still open when the window closes it is liquidated at market before the strategy evaluates fresh entries.
Parameters
| Parameter | Description |
|---|---|
CandleType |
Candle data type used for calculations (default: 4-hour candles). |
RocPeriod1, RocPeriod2 |
Lookback lengths for the fast and slow ROC streams. |
SmoothLength1, SmoothLength2 |
Smoothing lengths for each stream. |
SmoothMethod1, SmoothMethod2 |
Moving-average types applied to the ROC outputs. |
RocType |
ROC calculation formula: momentum, percent change or ratio. |
SignalShift |
Number of completed bars back used to read signal values. |
AllowBuyOpen, AllowSellOpen |
Enable or disable opening long/short positions. |
AllowBuyClose, AllowSellClose |
Enable or disable indicator-based exits for long/short positions. |
UseTimeFilter |
Activates the trading session window. |
StartTime, EndTime |
Session start and end times. |
OrderVolume |
Volume for each new trade. |
StopLoss, TakeProfit |
Optional absolute distances for protective exits. |
Implementation notes
- The strategy keeps short histories of prices and smoothed values instead of using indicator buffers, which reproduces the original
SignalBaroffset without relying onGetValue. - JJMA, VIDYA and AMA smoothing from the MQL indicator are mapped to exponential smoothing to stay within the StockSharp standard indicator set.
- All comments in the code are in English and the namespace follows the repository guidelines.
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()