XROC2 VG X2戦略
概要
XROC2 VG X2戦略は、2本の平滑化された価格変化率ストリームを組み合わせたマルチ時間軸システムです。上位時間軸が方向性フィルターとして機能し、下位時間軸が具体的なエントリーおよびエグジットシグナルを生成します。元のMetaTrader 5エキスパートアドバイザーは、柔軟な平滑化オプションとマネー管理モジュールを持つカスタムXROC2_VGインジケーターに依存していました。StockSharpポートはシグナルロジックをそのまま維持し、主要なパラメーターを戦略入力として公開します。
戦略は2つのローソク足シリーズにサブスクライブします:
- 上位時間軸(デフォルト6時間)– 支配的なトレンド方向を確立します。
- 下位時間軸(デフォルト30分)– 2本の平滑化されたROC線がどのように交差するかを監視してエントリーとエグジットを生成します。
両ストリームは同じ価格変化率計算モードを共有しますが、個別の平滑化設定を使用します。デフォルトでは戦略はJurik移動平均を適用し、MQLバージョンを模倣します。StockSharpが直接サポートしていない高度な平滑化タイプ(JurX、ParMA、T3、VIDYA、フェーズ制御付きAMA)は、最も近い利用可能な移動平均実装にフォールバックします。
トレードロジック
- トレンド検出(上位時間軸)
- 設定された期間と平滑化方法を使用して2つの平滑化ROC値を計算する。
HigherSignalBarで定義されたバーでラインペアを評価する。速い線が遅い線より上にあればトレンドは強気、そうでなければ弱気。中立の読みは現在のトレンドをゼロに保ちトレーディングを無効にする。
- シグナル生成(下位時間軸)
- 下位時間軸で同じ平滑化ROC値のペアを計算する。
- 最新の完成バー(シフト
LowerSignalBar)とその前のバーを見る。この2本のバーの組み合わせが直前にクロスが発生したかどうかを決定する。 - ロングセットアップは上位時間軸が強気で、速い線が遅い線を下方にクロスし(下方クロス)、ロングが有効な場合に現れる。
- ショートセットアップは上位時間軸が弱気で、速い線が遅い線を上方にクロスし(上方クロス)、ショートが有効な場合に現れる。
- ポジション管理
- 下位時間軸のクロスが弱気を示すとき(
CloseBuyOnLower)または上位時間軸のトレンドが弱気に転換するとき(CloseBuyOnTrendFlip)にロングポジションを閉じる。 - 下位時間軸のクロスが強気になるとき(
CloseSellOnLower)または上位時間軸のトレンドが強気に転換するとき(CloseSellOnTrendFlip)にショートポジションを閉じる。 - 新規トレードはアクティブなポジションがない場合にのみ開かれる。注文サイズは戦略の
Volumeプロパティで制御される。
- 下位時間軸のクロスが弱気を示すとき(
パラメーター
HigherCandleType– トレンドフィルター用のローソク足タイプ(デフォルト6時間足)。LowerCandleType– シグナル生成用のローソク足タイプ(デフォルト30分足)。HigherSignalBar– 上位時間軸の値を読み取る際にシフトする閉じたバーの数(デフォルト1)。LowerSignalBar– 下位時間軸の値を読み取る際にシフトする閉じたバーの数(デフォルト1)。HigherRocMode/LowerRocMode– 価格変化率計算バリアント(Momentum、RateOfChange、RateOfChangePercent、RateOfChangeRatio、RateOfChangeRatioPercent)。HigherFastPeriod,HigherFastMethod,HigherFastLength,HigherFastPhase– 上位時間軸の高速ROC設定。HigherSlowPeriod,HigherSlowMethod,HigherSlowLength,HigherSlowPhase– 上位時間軸の低速ROC設定。LowerFastPeriod,LowerFastMethod,LowerFastLength,LowerFastPhase– 下位時間軸の高速ROC設定。LowerSlowPeriod,LowerSlowMethod,LowerSlowLength,LowerSlowPhase– 下位時間軸の低速ROC設定。AllowBuyOpen,AllowSellOpen– ロングおよびショートの開設を有効/無効にする。CloseBuyOnTrendFlip,CloseSellOnTrendFlip– 上位時間軸が方向を変えるときに強制終了する。CloseBuyOnLower,CloseSellOnLower– 下位時間軸のクロスがポジションに反する場合に終了する。
実装上の注意
- 元のMQL戦略は大規模な平滑化ライブラリを使用していました。StockSharpバージョンはサポートされているオプションを組み込みインジケーター(SMA、EMA、SMMA/RMA、LWMA、Jurik、Kaufman AMA)にマッピングします。サポートされていないモード(JurX、ParMA、T3、VIDYA)は最も近い利用可能な移動平均で近似されるため、これらの組み合わせでは動作が異なる場合があります。
TradeAlgorithms.mqhからのマネー管理機能、ストップロス、テイクプロフィット、スリッページ設定は再現されていません。代わりに、戦略設定で指定された固定Volumeでトレードします。- 注文は成行注文で実行されます。必要に応じてStockSharpの保護モジュールを通じてストップロスやトレーリングストップなどの保護ロジックを追加できます。
- 戦略は両方のローソク足サブスクリプションが完全に形成され、
IsFormedAndOnlineAndAllowTrading()が真を返す場合にのみトレードします。
使用上のヒント
- 元のトレードスタイルに対応するローソク足タイプを選ぶ(例:スイングトレーディングには6h/30m)。他の組み合わせも可能です。
- 好みのレスポンスに合わせて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()