XROC2 VG 时间过滤策略
该策略使用 StockSharp 高层 API 重建 MetaTrader 专家顾问 Exp_XROC2_VG_Tm。系统计算两条平滑后的价格变化率(ROC)曲线,当快速曲线与慢速曲线交叉时采取反向交易。可选的交易时段过滤器与止盈止损距离复现了原始 EA 的资金管理逻辑。
交易思路
- 以不同的周期从收盘价计算两条 ROC 序列。
- 每条 ROC 序列都通过可配置的移动平均方法进行平滑处理。
- 信号按照
SignalShift指定的历史柱索引进行评估,与 MQL 版本保持一致。 - 如果上一根柱中快速线在慢速线之上,而信号柱快速线跌破慢速线,则平掉任何空头仓位,并可选择开多。
- 如果上一根柱中快速线在慢速线之下,而信号柱快速线突破慢速线,则平掉任何多头仓位,并可选择开空。
- 可选的交易窗口会在禁止交易的时间段把仓位清零,之后才评估新的入场机会。
仓位方向只有在已有仓位完全平仓后才会切换,符合原始 TradeAlgorithms 模块的行为。
指标说明
- 快速 ROC:基于
RocPeriod1根 K 线的价格动量、百分比或比率,并使用SmoothMethod1、SmoothLength1进行平滑。 - 慢速 ROC:同样的计算方式,周期为
RocPeriod2,平滑参数为SmoothMethod2、SmoothLength2。 - 支持的平滑方法包括:简单、指数、平滑(RMA)以及加权移动平均。原始指标中的 JJMA、VIDYA、AMA 在此策略中用指数平滑近似实现。
风险控制
StopLoss与TakeProfit使用绝对价格距离定义可选的止损止盈,当触发任一阈值时立即平仓。OrderVolume指定每次新开仓的数量。- 指标信号也可能触发平仓,即使停损停利被禁用。
时间过滤
UseTimeFilter控制是否启用交易时段过滤。StartTime/EndTime定义允许交易的时间窗口;当结束时间早于开始时间时,窗口会跨越午夜,与 MQL 版本相同。- 若在窗口关闭时仍有持仓,会先以市价平仓,然后才评估新的入场信号。
参数列表
| 参数 | 说明 |
|---|---|
CandleType |
用于计算的 K 线类型(默认 4 小时)。 |
RocPeriod1, RocPeriod2 |
快速与慢速 ROC 的回溯周期。 |
SmoothLength1, SmoothLength2 |
每条 ROC 曲线的平滑长度。 |
SmoothMethod1, SmoothMethod2 |
ROC 输出采用的移动平均类型。 |
RocType |
ROC 计算公式:动量、百分比或比率。 |
SignalShift |
读取信号时回溯的柱数。 |
AllowBuyOpen, AllowSellOpen |
是否允许开多 / 开空。 |
AllowBuyClose, AllowSellClose |
是否允许由指标信号平多 / 平空。 |
UseTimeFilter |
是否启用交易时段过滤。 |
StartTime, EndTime |
交易窗口的起止时间。 |
OrderVolume |
每次新订单的交易量。 |
StopLoss, TakeProfit |
可选的绝对价差止损、止盈。 |
实现说明
- 策略使用短历史缓冲保存价格与平滑值,而不是访问完整指标缓冲区,从而在不调用
GetValue的情况下还原SignalShift逻辑。 - 由于 StockSharp 标准指标库限制,JJMA、VIDYA、AMA 被映射为指数移动平均。
- 代码中的注释全部为英文,并遵守仓库的命名空间规范。
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()