Стратегия XROC2 VG X2
Общее описание
XROC2 VG X2 — мульти-таймфреймовая стратегия, основанная на двух сглаженных потоках индикатора Rate of Change. Старший таймфрейм используется как фильтр направления, младший — для генерации конкретных сигналов входа и выхода. Исходный эксперт под MetaTrader 5 опирался на пользовательский индикатор XROC2_VG и библиотеку управления капиталом. В портированной версии для StockSharp сохранена логика сигналов и основные параметры вынесены в настройки стратегии.
Стратегия подписывается на две серии свечей:
- Старший таймфрейм (по умолчанию 6 часов) — определяет доминирующее направление рынка.
- Младший таймфрейм (по умолчанию 30 минут) — отслеживает пересечения двух ROC-линий и формирует торговые события.
Обе линии используют одинаковый режим расчёта ROC, но обладают независимыми методами сглаживания. По умолчанию применяется сглаживание Jurik, что обеспечивает поведение максимально близкое к оригиналу. Для методов, отсутствующих в StockSharp (JurX, ParMA, T3, VIDYA, AMA с управлением фазой), используется ближайший доступный тип скользящей средней, поэтому результаты могут незначительно отличаться.
Логика торговли
- Определение тренда (старший таймфрейм)
- Рассчитать две сглаженные ROC-линии с заданными периодами и методами сглаживания.
- На свече, указанной параметром
HigherSignalBar, сравнить значения: если быстрая линия выше медленной, считается, что тренд бычий, если ниже — медвежий. Равенство значений обнуляет тренд и блокирует торговлю.
- Формирование сигналов (младший таймфрейм)
- Повторить расчёт ROC-линий на младшем таймфрейме.
- Проанализировать последнюю закрытую свечу (смещение
LowerSignalBar) и предыдущую, чтобы определить факт пересечения. - Лонг сигнал появляется, когда старший тренд бычий, быстрая линия пересекает медленную сверху вниз и разрешено открывать покупки.
- Шорт сигнал возникает, когда старший тренд медвежий, быстрая линия пересекает медленную снизу вверх и разрешено открывать продажи.
- Управление позицией
- Длинные позиции закрываются при медвежьем пересечении на младшем ТФ (
CloseBuyOnLower) или при смене тренда старшего ТФ на медвежий (CloseBuyOnTrendFlip). - Короткие позиции закрываются при бычьем пересечении (
CloseSellOnLower) или при смене тренда старшего ТФ на бычий (CloseSellOnTrendFlip). - Новые сделки открываются только при отсутствии открытой позиции. Объём заявок задаётся параметром
Volumeстратегии.
- Длинные позиции закрываются при медвежьем пересечении на младшем ТФ (
Параметры
HigherCandleType— тип свечей для трендового фильтра (по умолчанию 6 часов).LowerCandleType— тип свечей для сигналов (по умолчанию 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). Для остальных выбирается ближайший аналог, поэтому возможны небольшие расхождения в поведении.
- Модуль управления капиталом, стоп-лоссы, тейк-профиты и параметры проскальзывания из
TradeAlgorithms.mqhне реализованы. Стратегия торгует фиксированным объёмомVolume. - Все сделки выполняются рыночными ордерами. При необходимости можно добавить защиту (стоп-лосс, трейлинг) через стандартные механизмы StockSharp.
- Торговая логика активируется только при готовности обеих подписок и выполнении условия
IsFormedAndOnlineAndAllowTrading().
Рекомендации по использованию
- Подбирайте таймфреймы в соответствии со стилем торговли (например, 6 часов / 30 минут для свинг-подхода). Возможны и другие комбинации.
- Настраивайте периоды ROC и методы сглаживания под требуемую чувствительность. Jurik обеспечивает поведение, максимально близкое к MQL-версии.
- При торговле на реальном счёте стоит дополнительно настроить риск-менеджмент (стоп-лоссы, контроль объёма), поскольку порт использует только рыночные выходы.
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()