XROC2 VG X2 策略
概览
XROC2 VG X2 是一个多周期策略,通过两条平滑后的动量变化曲线来判断行情。较高周期用于确认方向性过滤,较低周期负责给出具体的入场与离场信号。原始的 MetaTrader 5 版本依赖自定义的 XROC2_VG 指标以及一套资金管理模块。移植到 StockSharp 后,策略保留了核心信号逻辑,并将主要参数暴露为策略输入。
策略会订阅两组 K 线:
- 高周期(默认 6 小时)——用来判定当前的大趋势方向;
- 低周期(默认 30 分钟)——通过观察两条平滑 ROC 曲线的交叉生成交易信号。
两条曲线共用同一种 ROC 计算模式,但拥有独立的平滑方式。默认情况下使用 Jurik 平滑,以贴近 MQL 实现。对于 StockSharp 尚未直接提供的平滑方式(JurX、ParMA、T3、VIDYA、带相位控制的 AMA),策略会退化到最接近的可用移动平均,因此这些组合下的表现可能与原版略有差异。
交易逻辑
- 趋势识别(高周期)
- 按照设定的周期和平滑参数计算两条平滑 ROC。
- 在
HigherSignalBar指定的已收盘 K 线上比较两条曲线,快线在慢线上方视为多头趋势,反之为空头,若两者持平则保持趋势为零并暂停交易。
- 信号生成(低周期)
- 在低周期上重复计算同样的两条平滑 ROC。
- 取最近一个已收盘 K 线(偏移量
LowerSignalBar)及其前一根,根据两根柱子的相对位置判断是否刚发生交叉。 - 当高周期为多头,且快线刚刚从上方向下穿越慢线并允许做多时,触发做多信号。
- 当高周期为空头,且快线刚刚从下方向上穿越慢线并允许做空时,触发做空信号。
- 头寸管理
- 低周期出现向下交叉或高周期趋势转为空头时(
CloseBuyOnLower、CloseBuyOnTrendFlip),平掉多头。 - 低周期出现向上交叉或高周期趋势转为多头时(
CloseSellOnLower、CloseSellOnTrendFlip),平掉空头。 - 仅在没有持仓时开新单,委托数量由策略的
Volume属性控制。
- 低周期出现向下交叉或高周期趋势转为空头时(
参数说明
HigherCandleType:趋势过滤所用的 K 线类型(默认 6 小时)。LowerCandleType:信号生成所用的 K 线类型(默认 30 分钟)。HigherSignalBar:读取高周期数据时的偏移量(单位:已收盘柱数,默认 1)。LowerSignalBar:读取低周期数据时的偏移量(默认 1)。HigherRocMode/LowerRocMode:ROC 计算方式(Momentum、RateOfChange、RateOfChangePercent、RateOfChangeRatio、RateOfChangeRatioPercent)。HigherFastPeriod、HigherFastMethod、HigherFastLength、HigherFastPhase:高周期快线的设置。HigherSlowPeriod、HigherSlowMethod、HigherSlowLength、HigherSlowPhase:高周期慢线的设置。LowerFastPeriod、LowerFastMethod、LowerFastLength、LowerFastPhase:低周期快线的设置。LowerSlowPeriod、LowerSlowMethod、LowerSlowLength、LowerSlowPhase:低周期慢线的设置。AllowBuyOpen、AllowSellOpen:是否允许开多/开空。CloseBuyOnTrendFlip、CloseSellOnTrendFlip:高周期趋势反转时是否立即平仓。CloseBuyOnLower、CloseSellOnLower:低周期交叉与持仓方向相反时是否平仓。
实现注意事项
- 原策略使用的平滑算法库非常庞大。移植版本将已支持的选项映射到 StockSharp 自带指标(SMA、EMA、SMMA/RMA、LWMA、Jurik、Kaufman AMA)。未支持的模式(JurX、ParMA、T3、VIDYA)自动退化为最接近的移动平均,因此某些组合下会与 MQL 结果略有出入。
TradeAlgorithms.mqh中的资金管理、止损止盈及滑点控制未被复现,策略仅按Volume固定手数下单。- 订单均以市价成交,若需要保护性止损或跟踪止损,可通过 StockSharp 的保护模块自行添加。
- 只有在高低两个订阅都就绪并且
IsFormedAndOnlineAndAllowTrading()返回 true 时才会触发交易逻辑。
使用建议
- 根据交易风格选择合适的周期组合(例如 6 小时 / 30 分钟适合波段交易),也可以尝试其他搭配。
- 调整 ROC 周期和平滑方式,以获得更契合的响应速度。若希望最大程度贴近原脚本,推荐保留 Jurik 平滑。
- 在真实账户运行时建议增加明确的风控措施(止损、仓位管理等),因为移植版本仅使用简单的市价平仓。
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>
/// Multi-timeframe XROC2 VG strategy that combines two smoothed rate-of-change streams.
/// The higher timeframe defines the directional bias while the lower timeframe handles entries and exits.
/// </summary>
public class Xroc2VgX2Strategy : Strategy
{
/// <summary>
/// Available rate-of-change calculation modes.
/// </summary>
public enum RocModes
{
Momentum,
RateOfChange,
RateOfChangePercent,
RateOfChangeRatio,
RateOfChangeRatioPercent,
}
/// <summary>
/// Smoothing methods supported by the strategy.
/// </summary>
public enum SmoothingMethods
{
Sma,
Ema,
Smma,
Lwma,
Jurik,
Jurx,
Parma,
T3,
Vidya,
Ama,
}
private readonly StrategyParam<DataType> _higherCandleType;
private readonly StrategyParam<DataType> _lowerCandleType;
private readonly StrategyParam<int> _higherSignalBar;
private readonly StrategyParam<int> _lowerSignalBar;
private readonly StrategyParam<RocModes> _higherRocMode;
private readonly StrategyParam<int> _higherFastPeriod;
private readonly StrategyParam<SmoothingMethods> _higherFastMethod;
private readonly StrategyParam<int> _higherFastLength;
private readonly StrategyParam<int> _higherFastPhase;
private readonly StrategyParam<int> _higherSlowPeriod;
private readonly StrategyParam<SmoothingMethods> _higherSlowMethod;
private readonly StrategyParam<int> _higherSlowLength;
private readonly StrategyParam<int> _higherSlowPhase;
private readonly StrategyParam<RocModes> _lowerRocMode;
private readonly StrategyParam<int> _lowerFastPeriod;
private readonly StrategyParam<SmoothingMethods> _lowerFastMethod;
private readonly StrategyParam<int> _lowerFastLength;
private readonly StrategyParam<int> _lowerFastPhase;
private readonly StrategyParam<int> _lowerSlowPeriod;
private readonly StrategyParam<SmoothingMethods> _lowerSlowMethod;
private readonly StrategyParam<int> _lowerSlowLength;
private readonly StrategyParam<int> _lowerSlowPhase;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _closeBuyOnTrendFlip;
private readonly StrategyParam<bool> _closeSellOnTrendFlip;
private readonly StrategyParam<bool> _closeBuyOnLower;
private readonly StrategyParam<bool> _closeSellOnLower;
private Xroc2VgSeries _higherSeries = default!;
private Xroc2VgSeries _lowerSeries = default!;
private int _trend;
/// <summary>
/// Initializes a new instance of the <see cref="Xroc2VgX2Strategy"/> class.
/// </summary>
public Xroc2VgX2Strategy()
{
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Higher TF", "Higher timeframe candles", "General");
_lowerCandleType = Param(nameof(LowerCandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Lower TF", "Lower timeframe candles", "General");
_higherSignalBar = Param(nameof(HigherSignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Higher Signal Bar", "Shift used for trend evaluation", "General");
_lowerSignalBar = Param(nameof(LowerSignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Lower Signal Bar", "Shift used for lower timeframe signals", "General");
_higherRocMode = Param(nameof(HigherRocMode), RocModes.Momentum)
.SetDisplay("Higher ROC Mode", "ROC calculation mode for the bias", "Higher Timeframe");
_higherFastPeriod = Param(nameof(HigherFastPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Higher Fast ROC", "Fast ROC period for bias", "Higher Timeframe");
_higherFastMethod = Param(nameof(HigherFastMethod), SmoothingMethods.Jurik)
.SetDisplay("Higher Fast Method", "Smoother for fast ROC", "Higher Timeframe");
_higherFastLength = Param(nameof(HigherFastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Higher Fast Length", "Length of fast smoother", "Higher Timeframe");
_higherFastPhase = Param(nameof(HigherFastPhase), 15)
.SetDisplay("Higher Fast Phase", "Phase parameter for fast smoother", "Higher Timeframe");
_higherSlowPeriod = Param(nameof(HigherSlowPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Higher Slow ROC", "Slow ROC period for bias", "Higher Timeframe");
_higherSlowMethod = Param(nameof(HigherSlowMethod), SmoothingMethods.Jurik)
.SetDisplay("Higher Slow Method", "Smoother for slow ROC", "Higher Timeframe");
_higherSlowLength = Param(nameof(HigherSlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Higher Slow Length", "Length of slow smoother", "Higher Timeframe");
_higherSlowPhase = Param(nameof(HigherSlowPhase), 15)
.SetDisplay("Higher Slow Phase", "Phase parameter for slow smoother", "Higher Timeframe");
_lowerRocMode = Param(nameof(LowerRocMode), RocModes.Momentum)
.SetDisplay("Lower ROC Mode", "ROC calculation mode for entries", "Lower Timeframe");
_lowerFastPeriod = Param(nameof(LowerFastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Lower Fast ROC", "Fast ROC period for entries", "Lower Timeframe");
_lowerFastMethod = Param(nameof(LowerFastMethod), SmoothingMethods.Jurik)
.SetDisplay("Lower Fast Method", "Smoother for fast ROC", "Lower Timeframe");
_lowerFastLength = Param(nameof(LowerFastLength), 10)
.SetGreaterThanZero()
.SetDisplay("Lower Fast Length", "Length of fast smoother", "Lower Timeframe");
_lowerFastPhase = Param(nameof(LowerFastPhase), 15)
.SetDisplay("Lower Fast Phase", "Phase parameter for fast smoother", "Lower Timeframe");
_lowerSlowPeriod = Param(nameof(LowerSlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Lower Slow ROC", "Slow ROC period for entries", "Lower Timeframe");
_lowerSlowMethod = Param(nameof(LowerSlowMethod), SmoothingMethods.Jurik)
.SetDisplay("Lower Slow Method", "Smoother for slow ROC", "Lower Timeframe");
_lowerSlowLength = Param(nameof(LowerSlowLength), 20)
.SetGreaterThanZero()
.SetDisplay("Lower Slow Length", "Length of slow smoother", "Lower Timeframe");
_lowerSlowPhase = Param(nameof(LowerSlowPhase), 15)
.SetDisplay("Lower Slow Phase", "Phase parameter for slow smoother", "Lower Timeframe");
_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
.SetDisplay("Allow Long Entries", "Enable long entries", "Signals");
_allowSellOpen = Param(nameof(AllowSellOpen), true)
.SetDisplay("Allow Short Entries", "Enable short entries", "Signals");
_closeBuyOnTrendFlip = Param(nameof(CloseBuyOnTrendFlip), true)
.SetDisplay("Close Long On Trend", "Close longs when higher trend turns bearish", "Signals");
_closeSellOnTrendFlip = Param(nameof(CloseSellOnTrendFlip), true)
.SetDisplay("Close Short On Trend", "Close shorts when higher trend turns bullish", "Signals");
_closeBuyOnLower = Param(nameof(CloseBuyOnLower), true)
.SetDisplay("Close Long On Lower", "Close longs when lower ROC crosses down", "Signals");
_closeSellOnLower = Param(nameof(CloseSellOnLower), true)
.SetDisplay("Close Short On Lower", "Close shorts when lower ROC crosses up", "Signals");
}
/// <summary>
/// Higher timeframe candle type.
/// </summary>
public DataType HigherCandleType
{
get => _higherCandleType.Value;
set => _higherCandleType.Value = value;
}
/// <summary>
/// Lower timeframe candle type.
/// </summary>
public DataType LowerCandleType
{
get => _lowerCandleType.Value;
set => _lowerCandleType.Value = value;
}
/// <summary>
/// Number of bars to shift when reading higher timeframe values.
/// </summary>
public int HigherSignalBar
{
get => _higherSignalBar.Value;
set => _higherSignalBar.Value = value;
}
/// <summary>
/// Number of bars to shift when reading lower timeframe values.
/// </summary>
public int LowerSignalBar
{
get => _lowerSignalBar.Value;
set => _lowerSignalBar.Value = value;
}
/// <summary>
/// Rate-of-change mode for the higher timeframe stream.
/// </summary>
public RocModes HigherRocMode
{
get => _higherRocMode.Value;
set => _higherRocMode.Value = value;
}
/// <summary>
/// Fast ROC period for the higher timeframe.
/// </summary>
public int HigherFastPeriod
{
get => _higherFastPeriod.Value;
set => _higherFastPeriod.Value = value;
}
/// <summary>
/// Smoothing method for the higher timeframe fast line.
/// </summary>
public SmoothingMethods HigherFastMethod
{
get => _higherFastMethod.Value;
set => _higherFastMethod.Value = value;
}
/// <summary>
/// Smoothing length for the higher timeframe fast line.
/// </summary>
public int HigherFastLength
{
get => _higherFastLength.Value;
set => _higherFastLength.Value = value;
}
/// <summary>
/// Phase parameter for the higher timeframe fast smoother.
/// </summary>
public int HigherFastPhase
{
get => _higherFastPhase.Value;
set => _higherFastPhase.Value = value;
}
/// <summary>
/// Slow ROC period for the higher timeframe.
/// </summary>
public int HigherSlowPeriod
{
get => _higherSlowPeriod.Value;
set => _higherSlowPeriod.Value = value;
}
/// <summary>
/// Smoothing method for the higher timeframe slow line.
/// </summary>
public SmoothingMethods HigherSlowMethod
{
get => _higherSlowMethod.Value;
set => _higherSlowMethod.Value = value;
}
/// <summary>
/// Smoothing length for the higher timeframe slow line.
/// </summary>
public int HigherSlowLength
{
get => _higherSlowLength.Value;
set => _higherSlowLength.Value = value;
}
/// <summary>
/// Phase parameter for the higher timeframe slow smoother.
/// </summary>
public int HigherSlowPhase
{
get => _higherSlowPhase.Value;
set => _higherSlowPhase.Value = value;
}
/// <summary>
/// Rate-of-change mode for the lower timeframe stream.
/// </summary>
public RocModes LowerRocMode
{
get => _lowerRocMode.Value;
set => _lowerRocMode.Value = value;
}
/// <summary>
/// Fast ROC period for the lower timeframe.
/// </summary>
public int LowerFastPeriod
{
get => _lowerFastPeriod.Value;
set => _lowerFastPeriod.Value = value;
}
/// <summary>
/// Smoothing method for the lower timeframe fast line.
/// </summary>
public SmoothingMethods LowerFastMethod
{
get => _lowerFastMethod.Value;
set => _lowerFastMethod.Value = value;
}
/// <summary>
/// Smoothing length for the lower timeframe fast line.
/// </summary>
public int LowerFastLength
{
get => _lowerFastLength.Value;
set => _lowerFastLength.Value = value;
}
/// <summary>
/// Phase parameter for the lower timeframe fast smoother.
/// </summary>
public int LowerFastPhase
{
get => _lowerFastPhase.Value;
set => _lowerFastPhase.Value = value;
}
/// <summary>
/// Slow ROC period for the lower timeframe.
/// </summary>
public int LowerSlowPeriod
{
get => _lowerSlowPeriod.Value;
set => _lowerSlowPeriod.Value = value;
}
/// <summary>
/// Smoothing method for the lower timeframe slow line.
/// </summary>
public SmoothingMethods LowerSlowMethod
{
get => _lowerSlowMethod.Value;
set => _lowerSlowMethod.Value = value;
}
/// <summary>
/// Smoothing length for the lower timeframe slow line.
/// </summary>
public int LowerSlowLength
{
get => _lowerSlowLength.Value;
set => _lowerSlowLength.Value = value;
}
/// <summary>
/// Phase parameter for the lower timeframe slow smoother.
/// </summary>
public int LowerSlowPhase
{
get => _lowerSlowPhase.Value;
set => _lowerSlowPhase.Value = value;
}
/// <summary>
/// Allow long entries when signals align.
/// </summary>
public bool AllowBuyOpen
{
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
/// <summary>
/// Allow short entries when signals align.
/// </summary>
public bool AllowSellOpen
{
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
/// <summary>
/// Close long positions when the higher timeframe turns bearish.
/// </summary>
public bool CloseBuyOnTrendFlip
{
get => _closeBuyOnTrendFlip.Value;
set => _closeBuyOnTrendFlip.Value = value;
}
/// <summary>
/// Close short positions when the higher timeframe turns bullish.
/// </summary>
public bool CloseSellOnTrendFlip
{
get => _closeSellOnTrendFlip.Value;
set => _closeSellOnTrendFlip.Value = value;
}
/// <summary>
/// Close long positions when the lower timeframe shows a bearish cross.
/// </summary>
public bool CloseBuyOnLower
{
get => _closeBuyOnLower.Value;
set => _closeBuyOnLower.Value = value;
}
/// <summary>
/// Close short positions when the lower timeframe shows a bullish cross.
/// </summary>
public bool CloseSellOnLower
{
get => _closeSellOnLower.Value;
set => _closeSellOnLower.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, HigherCandleType);
yield return (Security, LowerCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_higherSeries = null!;
_lowerSeries = null!;
_trend = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_higherSeries = new Xroc2VgSeries(
HigherRocMode,
HigherFastPeriod,
HigherFastMethod,
HigherFastLength,
HigherFastPhase,
HigherSlowPeriod,
HigherSlowMethod,
HigherSlowLength,
HigherSlowPhase);
_lowerSeries = new Xroc2VgSeries(
LowerRocMode,
LowerFastPeriod,
LowerFastMethod,
LowerFastLength,
LowerFastPhase,
LowerSlowPeriod,
LowerSlowMethod,
LowerSlowLength,
LowerSlowPhase);
_trend = 0;
var higherSubscription = SubscribeCandles(HigherCandleType);
higherSubscription.Bind(ProcessHigherCandle).Start();
var lowerSubscription = SubscribeCandles(LowerCandleType);
lowerSubscription.Bind(ProcessLowerCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, lowerSubscription);
DrawOwnTrades(area);
}
}
private void ProcessHigherCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_higherSeries.Process(candle))
return;
if (_higherSeries.TryGetValue(HigherSignalBar, out var value))
_trend = value.up > value.down ? 1 : value.up < value.down ? -1 : 0;
}
private void ProcessLowerCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_lowerSeries.Process(candle))
return;
if (!_lowerSeries.TryGetPair(LowerSignalBar, out var current, out var previous))
return;
if (_trend == 0)
return;
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
var buyClose = CloseBuyOnLower && current.up < current.down && previous.up >= previous.down;
var sellClose = CloseSellOnLower && current.up > current.down && previous.up <= previous.down;
if (_trend < 0 && CloseBuyOnTrendFlip)
buyClose = true;
if (_trend > 0 && CloseSellOnTrendFlip)
sellClose = true;
var buyOpen = _trend > 0 && AllowBuyOpen && current.up > current.down && previous.up <= previous.down;
var sellOpen = _trend < 0 && AllowSellOpen && current.up < current.down && previous.up >= previous.down;
ExecuteSignals(buyOpen, sellOpen, buyClose, sellClose);
}
private void ExecuteSignals(bool buyOpen, bool sellOpen, bool buyClose, bool sellClose)
{
var position = Position;
if (buyClose && position > 0m)
{
var volume = position.Abs();
if (volume > 0m)
SellMarket();
position = Position;
}
if (sellClose && position < 0m)
{
var volume = position.Abs();
if (volume > 0m)
BuyMarket();
position = Position;
}
if (buyOpen && position == 0m)
{
var volume = Volume;
if (volume > 0m)
BuyMarket();
return;
}
if (sellOpen && position == 0m)
{
var volume = Volume;
if (volume > 0m)
SellMarket();
}
}
private sealed class Xroc2VgSeries
{
private readonly RocSmoother _fast;
private readonly RocSmoother _slow;
private readonly List<(decimal up, decimal down)> _history = new();
private readonly int _maxHistory;
public Xroc2VgSeries(
RocModes mode,
int fastPeriod,
SmoothingMethods fastMethod,
int fastLength,
int fastPhase,
int slowPeriod,
SmoothingMethods slowMethod,
int slowLength,
int slowPhase,
int maxHistory = 1024)
{
_fast = new RocSmoother(mode, fastPeriod, fastMethod, fastLength, fastPhase);
_slow = new RocSmoother(mode, slowPeriod, slowMethod, slowLength, slowPhase);
_maxHistory = maxHistory;
}
public bool Process(ICandleMessage candle)
{
var fast = _fast.Process(candle.ClosePrice, candle.OpenTime);
var slow = _slow.Process(candle.ClosePrice, candle.OpenTime);
if (!fast.HasValue || !slow.HasValue)
return false;
_history.Add((fast.Value, slow.Value));
while (_history.Count > _maxHistory)
try { _history.RemoveAt(0); } catch { break; }
return true;
}
public bool TryGetValue(int signalBar, out (decimal up, decimal down) value)
{
value = default;
if (signalBar <= 0)
return false;
var index = _history.Count - signalBar;
if (index < 0 || index >= _history.Count)
return false;
value = _history[index];
return true;
}
public bool TryGetPair(int signalBar, out (decimal up, decimal down) current, out (decimal up, decimal down) previous)
{
current = default;
previous = default;
if (signalBar <= 0)
return false;
var index = _history.Count - signalBar;
if (index < 1 || index >= _history.Count)
return false;
current = _history[index];
previous = _history[index - 1];
return true;
}
}
private sealed class RocSmoother
{
private readonly RocModes _mode;
private readonly int _period;
private readonly IIndicator _smoother;
private readonly List<decimal> _window = new();
public RocSmoother(RocModes mode, int period, SmoothingMethods method, int length, int phase)
{
_mode = mode;
_period = Math.Max(1, period);
_smoother = CreateSmoother(method, length, phase);
}
public decimal? Process(decimal close, DateTimeOffset time)
{
_window.Add(close);
if (_window.Count < _period + 1)
return null;
while (_window.Count > _period + 1)
try { _window.RemoveAt(0); } catch { break; }
var prev = _window[0];
decimal roc;
switch (_mode)
{
case RocModes.Momentum:
roc = close - prev;
break;
case RocModes.RateOfChange:
if (prev == 0m)
return null;
roc = (close / prev - 1m) * 100m;
break;
case RocModes.RateOfChangePercent:
if (prev == 0m)
return null;
roc = (close - prev) / prev;
break;
case RocModes.RateOfChangeRatio:
if (prev == 0m)
return null;
roc = close / prev;
break;
case RocModes.RateOfChangeRatioPercent:
if (prev == 0m)
return null;
roc = (close / prev) * 100m;
break;
default:
roc = close - prev;
break;
}
var indicatorValue = _smoother.Process(new DecimalIndicatorValue(_smoother, roc, time.UtcDateTime) { IsFinal = true });
return indicatorValue switch
{
DecimalIndicatorValue { IsFinal: true } decimalValue => decimalValue.Value,
{ IsFinal: true } value => value.GetValue<decimal?>(),
_ => null,
};
}
}
private static IIndicator CreateSmoother(SmoothingMethods method, int length, int phase)
{
var len = Math.Max(1, length);
return method switch
{
SmoothingMethods.Sma => new SMA { Length = len },
SmoothingMethods.Ema => new EMA { Length = len },
SmoothingMethods.Smma => new EMA { Length = len },
SmoothingMethods.Lwma => new SMA { Length = len },
SmoothingMethods.Jurik => new EMA { Length = len },
SmoothingMethods.Jurx => new EMA { Length = len },
SmoothingMethods.Ama => new EMA { Length = len },
_ => new EMA { Length = len },
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from indicator_extensions import *
class _RocSmoother(object):
"""Computes ROC (momentum mode) then smooths with an indicator."""
def __init__(self, period, length):
self._period = max(1, period)
self._smoother = ExponentialMovingAverage()
self._smoother.Length = max(1, length)
self._window = []
def process(self, close, time):
self._window.append(close)
if len(self._window) < self._period + 1:
return None
while len(self._window) > self._period + 1:
self._window.pop(0)
prev = self._window[0]
roc = Decimal.Subtract(close, prev)
out = process_float(self._smoother, roc, time, True)
if out.IsFinal:
try:
return out.Value
except Exception:
pass
return None
class _Xroc2VgSeries(object):
"""Manages fast/slow ROC smoothers and their signal history."""
def __init__(self, fast_period, fast_length, slow_period, slow_length):
self._fast = _RocSmoother(fast_period, fast_length)
self._slow = _RocSmoother(slow_period, slow_length)
self._history = []
def process(self, candle):
close = candle.ClosePrice
t = candle.OpenTime
fast = self._fast.process(close, t)
slow = self._slow.process(close, t)
if fast is None or slow is None:
return False
self._history.append((fast, slow))
if len(self._history) > 1024:
self._history.pop(0)
return True
def try_get_value(self, signal_bar):
if signal_bar <= 0:
return None
idx = len(self._history) - signal_bar
if idx < 0 or idx >= len(self._history):
return None
return self._history[idx]
def try_get_pair(self, signal_bar):
if signal_bar <= 0:
return None, None
idx = len(self._history) - signal_bar
if idx < 1 or idx >= len(self._history):
return None, None
return self._history[idx], self._history[idx - 1]
class xroc2_vg_x2_strategy(Strategy):
"""Multi-timeframe XROC2 VG: higher TF for bias, lower TF for entries."""
def __init__(self):
super(xroc2_vg_x2_strategy, self).__init__()
self._higher_candle_type = self.Param("HigherCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Higher TF", "Higher timeframe candles", "General")
self._lower_candle_type = self.Param("LowerCandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Lower TF", "Lower timeframe candles", "General")
self._higher_signal_bar = self.Param("HigherSignalBar", 1) \
.SetGreaterThanZero() \
.SetDisplay("Higher Signal Bar", "Shift for trend evaluation", "General")
self._lower_signal_bar = self.Param("LowerSignalBar", 1) \
.SetGreaterThanZero() \
.SetDisplay("Lower Signal Bar", "Shift for lower TF signals", "General")
self._higher_fast_period = self.Param("HigherFastPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("Higher Fast ROC", "Fast ROC period for bias", "Higher Timeframe")
self._higher_fast_length = self.Param("HigherFastLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Higher Fast Length", "Length of fast smoother", "Higher Timeframe")
self._higher_slow_period = self.Param("HigherSlowPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Higher Slow ROC", "Slow ROC period for bias", "Higher Timeframe")
self._higher_slow_length = self.Param("HigherSlowLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Higher Slow Length", "Length of slow smoother", "Higher Timeframe")
self._lower_fast_period = self.Param("LowerFastPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("Lower Fast ROC", "Fast ROC period for entries", "Lower Timeframe")
self._lower_fast_length = self.Param("LowerFastLength", 10) \
.SetGreaterThanZero() \
.SetDisplay("Lower Fast Length", "Length of fast smoother", "Lower Timeframe")
self._lower_slow_period = self.Param("LowerSlowPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Lower Slow ROC", "Slow ROC period for entries", "Lower Timeframe")
self._lower_slow_length = self.Param("LowerSlowLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lower Slow Length", "Length of slow smoother", "Lower Timeframe")
self._allow_buy = self.Param("AllowBuyOpen", True) \
.SetDisplay("Allow Long Entries", "Enable long entries", "Signals")
self._allow_sell = self.Param("AllowSellOpen", True) \
.SetDisplay("Allow Short Entries", "Enable short entries", "Signals")
self._close_buy_trend = self.Param("CloseBuyOnTrendFlip", True) \
.SetDisplay("Close Long On Trend", "Close longs when higher trend turns bearish", "Signals")
self._close_sell_trend = self.Param("CloseSellOnTrendFlip", True) \
.SetDisplay("Close Short On Trend", "Close shorts when higher trend turns bullish", "Signals")
self._close_buy_lower = self.Param("CloseBuyOnLower", True) \
.SetDisplay("Close Long On Lower", "Close longs when lower ROC crosses down", "Signals")
self._close_sell_lower = self.Param("CloseSellOnLower", True) \
.SetDisplay("Close Short On Lower", "Close shorts when lower ROC crosses up", "Signals")
self._higher_series = None
self._lower_series = None
self._trend = 0
@property
def HigherCandleType(self):
return self._higher_candle_type.Value
@property
def LowerCandleType(self):
return self._lower_candle_type.Value
@property
def HigherSignalBar(self):
return self._higher_signal_bar.Value
@property
def LowerSignalBar(self):
return self._lower_signal_bar.Value
@property
def HigherFastPeriod(self):
return self._higher_fast_period.Value
@property
def HigherFastLength(self):
return self._higher_fast_length.Value
@property
def HigherSlowPeriod(self):
return self._higher_slow_period.Value
@property
def HigherSlowLength(self):
return self._higher_slow_length.Value
@property
def LowerFastPeriod(self):
return self._lower_fast_period.Value
@property
def LowerFastLength(self):
return self._lower_fast_length.Value
@property
def LowerSlowPeriod(self):
return self._lower_slow_period.Value
@property
def LowerSlowLength(self):
return self._lower_slow_length.Value
@property
def AllowBuyOpen(self):
return self._allow_buy.Value
@property
def AllowSellOpen(self):
return self._allow_sell.Value
@property
def CloseBuyOnTrendFlip(self):
return self._close_buy_trend.Value
@property
def CloseSellOnTrendFlip(self):
return self._close_sell_trend.Value
@property
def CloseBuyOnLower(self):
return self._close_buy_lower.Value
@property
def CloseSellOnLower(self):
return self._close_sell_lower.Value
def OnStarted2(self, time):
super(xroc2_vg_x2_strategy, self).OnStarted2(time)
self._higher_series = _Xroc2VgSeries(
self.HigherFastPeriod, self.HigherFastLength,
self.HigherSlowPeriod, self.HigherSlowLength)
self._lower_series = _Xroc2VgSeries(
self.LowerFastPeriod, self.LowerFastLength,
self.LowerSlowPeriod, self.LowerSlowLength)
self._trend = 0
higher_sub = self.SubscribeCandles(self.HigherCandleType)
higher_sub.Bind(self._process_higher).Start()
lower_sub = self.SubscribeCandles(self.LowerCandleType)
lower_sub.Bind(self._process_lower).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, lower_sub)
self.DrawOwnTrades(area)
def _process_higher(self, candle):
if candle.State != CandleStates.Finished:
return
if not self._higher_series.process(candle):
return
val = self._higher_series.try_get_value(self.HigherSignalBar)
if val is not None:
if val[0] > val[1]:
self._trend = 1
elif val[0] < val[1]:
self._trend = -1
else:
self._trend = 0
def _process_lower(self, candle):
if candle.State != CandleStates.Finished:
return
if not self._lower_series.process(candle):
return
current, previous = self._lower_series.try_get_pair(self.LowerSignalBar)
if current is None or previous is None:
return
if self._trend == 0:
return
buy_close = self.CloseBuyOnLower and current[0] < current[1] and previous[0] >= previous[1]
sell_close = self.CloseSellOnLower and current[0] > current[1] and previous[0] <= previous[1]
if self._trend < 0 and self.CloseBuyOnTrendFlip:
buy_close = True
if self._trend > 0 and self.CloseSellOnTrendFlip:
sell_close = True
buy_open = self._trend > 0 and self.AllowBuyOpen and current[0] > current[1] and previous[0] <= previous[1]
sell_open = self._trend < 0 and self.AllowSellOpen and current[0] < current[1] and previous[0] >= previous[1]
pos = self.Position
if buy_close and pos > 0:
self.SellMarket()
pos = self.Position
if sell_close and pos < 0:
self.BuyMarket()
pos = self.Position
if buy_open and pos == 0:
self.BuyMarket()
return
if sell_open and pos == 0:
self.SellMarket()
def OnReseted(self):
super(xroc2_vg_x2_strategy, self).OnReseted()
self._higher_series = None
self._lower_series = None
self._trend = 0
def CreateClone(self):
return xroc2_vg_x2_strategy()