XDidi Index Cloud Duplex Strategy
Overview
The XDidi Index Cloud Duplex strategy replicates the dual long/short signalling logic of the original MQL5 expert Exp_XDidi_Index_Cloud_Duplex. Two independent XDidi index configurations are evaluated on configurable timeframes. Each configuration computes a ratio between fast/medium and slow/medium moving averages. Crossings between these ratios trigger market entries while persistent divergences trigger exits.
Trading Logic
- Indicator calculation
- Three moving averages are calculated for each block (fast, medium, slow) on a selected price source.
- The XDidi ratios are derived as
fast / mediumandslow / medium. Optional inversion matches the originalReversoption.
- Signal generation
- Long block: when the previous bar had
fast > slowand the signal bar closes withfast <= slow, a long entry is requested. If the previous bar hadfast < slow, a long exit is requested. - Short block: when the previous bar had
fast < slowand the signal bar closes withfast >= slow, a short entry is requested. If the previous bar hadfast > slow, a short exit is requested. - Signal bar offsets reproduce the original
SignalBarinputs.
- Long block: when the previous bar had
- Order management
- Entries are executed with the strategy volume. Opposite positions are closed before reversing.
- Optional stop-loss and take-profit levels are applied via
StartProtectionusing price-step distances.
Parameters
| Name | Description |
|---|---|
LongCandleType, ShortCandleType |
Candle timeframes for each block. |
LongFastMethod / Medium / Slow & ShortFastMethod / Medium / Slow |
Moving-average smoothing methods for fast, medium and slow curves. Unsupported legacy smoothers fall back to exponential averaging. |
LongFastLength, LongMediumLength, LongSlowLength |
Periods for the long block moving averages. |
ShortFastLength, ShortMediumLength, ShortSlowLength |
Periods for the short block moving averages. |
LongAppliedPrice, ShortAppliedPrice |
Price source used for each block (close, open, typical, Demark, etc.). |
EnableLongEntries, EnableShortEntries |
Toggle new long/short positions. |
EnableLongExits, EnableShortExits |
Toggle automatic exits. |
LongSignalBar, ShortSignalBar |
Historical shift (bars back) evaluated for crossings. |
LongReverse, ShortReverse |
Invert ratios (mirrors Revers flag in MQL). |
StopLossPoints, TakeProfitPoints |
Protective distances expressed in price steps (set to zero to disable). |
Volume (base strategy property) |
Defines default trade size. |
Implementation Notes
- Moving averages are taken from StockSharp's indicator library. Advanced smoothers (
JJMA,JurX,ParMA,VIDYA) default to exponential smoothing because direct equivalents are unavailable. - Indicator values are processed on finished candles only, matching the original
IsNewBarbehaviour. - Signal queues maintain only the required number of historical ratio values, avoiding heavy collections.
- Protective stops are optional; if both distances are zero the strategy still calls
StartProtection()to comply with the framework lifecycle.
Usage Tips
- Align candle types with the data subscription available in your connector.
- Optimise moving-average lengths and applied prices to fit the traded instrument.
- When using asymmetric timeframes (long/short), both subscriptions are visualised on separate chart areas for clarity.
Limitations Compared to MQL5 Version
- Money-management modes (
MM,MarginMode) are not replicated; trade size follows the StockSharpVolumeproperty. - Some exotic smoothing algorithms from
SmoothAlgorithms.mqhare approximated with exponential moving averages. - Stop/limit orders are converted to generic protection levels instead of individual order parameters.
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>
/// Strategy that trades crossings between fast and slow XDidi index ratios calculated for long and short configurations.
/// </summary>
public class XDidiIndexCloudDuplexStrategy : Strategy
{
private readonly StrategyParam<DataType> _longCandleType;
private readonly StrategyParam<SmoothingMethods> _longFastMethod;
private readonly StrategyParam<int> _longFastLength;
private readonly StrategyParam<SmoothingMethods> _longMediumMethod;
private readonly StrategyParam<int> _longMediumLength;
private readonly StrategyParam<SmoothingMethods> _longSlowMethod;
private readonly StrategyParam<int> _longSlowLength;
private readonly StrategyParam<AppliedPrices> _longAppliedPrice;
private readonly StrategyParam<bool> _longEnableEntry;
private readonly StrategyParam<bool> _longEnableExit;
private readonly StrategyParam<bool> _longReverse;
private readonly StrategyParam<int> _longSignalBar;
private readonly StrategyParam<DataType> _shortCandleType;
private readonly StrategyParam<SmoothingMethods> _shortFastMethod;
private readonly StrategyParam<int> _shortFastLength;
private readonly StrategyParam<SmoothingMethods> _shortMediumMethod;
private readonly StrategyParam<int> _shortMediumLength;
private readonly StrategyParam<SmoothingMethods> _shortSlowMethod;
private readonly StrategyParam<int> _shortSlowLength;
private readonly StrategyParam<AppliedPrices> _shortAppliedPrice;
private readonly StrategyParam<bool> _shortEnableEntry;
private readonly StrategyParam<bool> _shortEnableExit;
private readonly StrategyParam<bool> _shortReverse;
private readonly StrategyParam<int> _shortSignalBar;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private DecimalLengthIndicator _longFastMa = null!;
private DecimalLengthIndicator _longMediumMa = null!;
private DecimalLengthIndicator _longSlowMa = null!;
private DecimalLengthIndicator _shortFastMa = null!;
private DecimalLengthIndicator _shortMediumMa = null!;
private DecimalLengthIndicator _shortSlowMa = null!;
private decimal?[] _longFastHistory = Array.Empty<decimal?>();
private decimal?[] _longSlowHistory = Array.Empty<decimal?>();
private decimal?[] _shortFastHistory = Array.Empty<decimal?>();
private decimal?[] _shortSlowHistory = Array.Empty<decimal?>();
/// <summary>
/// Initializes a new instance of the <see cref="XDidiIndexCloudDuplexStrategy"/> class.
/// </summary>
public XDidiIndexCloudDuplexStrategy()
{
_longCandleType = Param(nameof(LongCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Long Candle Type", "Timeframe used for the long XDidi calculation", "General");
_longFastMethod = Param(nameof(LongFastMethod), SmoothingMethods.Sma)
.SetDisplay("Long Fast Method", "Smoothing method for the short moving average in the long block", "Indicators");
_longFastLength = Param(nameof(LongFastLength), 3)
.SetDisplay("Long Fast Length", "Length for the short moving average in the long block", "Indicators")
.SetGreaterThanZero();
_longMediumMethod = Param(nameof(LongMediumMethod), SmoothingMethods.Sma)
.SetDisplay("Long Medium Method", "Smoothing method for the middle moving average in the long block", "Indicators");
_longMediumLength = Param(nameof(LongMediumLength), 8)
.SetDisplay("Long Medium Length", "Length for the middle moving average in the long block", "Indicators")
.SetGreaterThanZero();
_longSlowMethod = Param(nameof(LongSlowMethod), SmoothingMethods.Sma)
.SetDisplay("Long Slow Method", "Smoothing method for the slow moving average in the long block", "Indicators");
_longSlowLength = Param(nameof(LongSlowLength), 20)
.SetDisplay("Long Slow Length", "Length for the slow moving average in the long block", "Indicators")
.SetGreaterThanZero();
_longAppliedPrice = Param(nameof(LongAppliedPrice), AppliedPrices.Close)
.SetDisplay("Long Applied Price", "Price source used for the long XDidi calculation", "Indicators");
_longEnableEntry = Param(nameof(EnableLongEntries), true)
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");
_longEnableExit = Param(nameof(EnableLongExits), true)
.SetDisplay("Enable Long Exits", "Allow closing long positions", "Trading");
_longReverse = Param(nameof(LongReverse), false)
.SetDisplay("Reverse Long Ratios", "Invert long XDidi ratios (matches original indicator option)", "Indicators");
_longSignalBar = Param(nameof(LongSignalBar), 0)
.SetDisplay("Long Signal Bar", "Bar shift used for long signals", "Trading")
.SetNotNegative();
_shortCandleType = Param(nameof(ShortCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Short Candle Type", "Timeframe used for the short XDidi calculation", "General");
_shortFastMethod = Param(nameof(ShortFastMethod), SmoothingMethods.Sma)
.SetDisplay("Short Fast Method", "Smoothing method for the short moving average in the short block", "Indicators");
_shortFastLength = Param(nameof(ShortFastLength), 3)
.SetDisplay("Short Fast Length", "Length for the short moving average in the short block", "Indicators")
.SetGreaterThanZero();
_shortMediumMethod = Param(nameof(ShortMediumMethod), SmoothingMethods.Sma)
.SetDisplay("Short Medium Method", "Smoothing method for the middle moving average in the short block", "Indicators");
_shortMediumLength = Param(nameof(ShortMediumLength), 8)
.SetDisplay("Short Medium Length", "Length for the middle moving average in the short block", "Indicators")
.SetGreaterThanZero();
_shortSlowMethod = Param(nameof(ShortSlowMethod), SmoothingMethods.Sma)
.SetDisplay("Short Slow Method", "Smoothing method for the slow moving average in the short block", "Indicators");
_shortSlowLength = Param(nameof(ShortSlowLength), 20)
.SetDisplay("Short Slow Length", "Length for the slow moving average in the short block", "Indicators")
.SetGreaterThanZero();
_shortAppliedPrice = Param(nameof(ShortAppliedPrice), AppliedPrices.Close)
.SetDisplay("Short Applied Price", "Price source used for the short XDidi calculation", "Indicators");
_shortEnableEntry = Param(nameof(EnableShortEntries), true)
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");
_shortEnableExit = Param(nameof(EnableShortExits), true)
.SetDisplay("Enable Short Exits", "Allow closing short positions", "Trading");
_shortReverse = Param(nameof(ShortReverse), false)
.SetDisplay("Reverse Short Ratios", "Invert short XDidi ratios (matches original indicator option)", "Indicators");
_shortSignalBar = Param(nameof(ShortSignalBar), 0)
.SetDisplay("Short Signal Bar", "Bar shift used for short signals", "Trading")
.SetNotNegative();
_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
.SetDisplay("Stop Loss Points", "Protective stop in price steps applied to both directions", "Risk")
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
.SetDisplay("Take Profit Points", "Protective target in price steps applied to both directions", "Risk")
.SetNotNegative();
}
/// <summary>
/// Candle type used for the long XDidi calculation.
/// </summary>
public DataType LongCandleType
{
get => _longCandleType.Value;
set => _longCandleType.Value = value;
}
/// <summary>
/// Smoothing method for the fast moving average in the long block.
/// </summary>
public SmoothingMethods LongFastMethod
{
get => _longFastMethod.Value;
set => _longFastMethod.Value = value;
}
/// <summary>
/// Length for the fast moving average in the long block.
/// </summary>
public int LongFastLength
{
get => _longFastLength.Value;
set => _longFastLength.Value = value;
}
/// <summary>
/// Smoothing method for the medium moving average in the long block.
/// </summary>
public SmoothingMethods LongMediumMethod
{
get => _longMediumMethod.Value;
set => _longMediumMethod.Value = value;
}
/// <summary>
/// Length for the medium moving average in the long block.
/// </summary>
public int LongMediumLength
{
get => _longMediumLength.Value;
set => _longMediumLength.Value = value;
}
/// <summary>
/// Smoothing method for the slow moving average in the long block.
/// </summary>
public SmoothingMethods LongSlowMethod
{
get => _longSlowMethod.Value;
set => _longSlowMethod.Value = value;
}
/// <summary>
/// Length for the slow moving average in the long block.
/// </summary>
public int LongSlowLength
{
get => _longSlowLength.Value;
set => _longSlowLength.Value = value;
}
/// <summary>
/// Applied price for the long XDidi calculation.
/// </summary>
public AppliedPrices LongAppliedPrice
{
get => _longAppliedPrice.Value;
set => _longAppliedPrice.Value = value;
}
/// <summary>
/// Enable opening long positions.
/// </summary>
public bool EnableLongEntries
{
get => _longEnableEntry.Value;
set => _longEnableEntry.Value = value;
}
/// <summary>
/// Enable closing long positions.
/// </summary>
public bool EnableLongExits
{
get => _longEnableExit.Value;
set => _longEnableExit.Value = value;
}
/// <summary>
/// Invert ratios for the long XDidi block.
/// </summary>
public bool LongReverse
{
get => _longReverse.Value;
set => _longReverse.Value = value;
}
/// <summary>
/// Bar shift used for long signals.
/// </summary>
public int LongSignalBar
{
get => _longSignalBar.Value;
set => _longSignalBar.Value = value;
}
/// <summary>
/// Candle type used for the short XDidi calculation.
/// </summary>
public DataType ShortCandleType
{
get => _shortCandleType.Value;
set => _shortCandleType.Value = value;
}
/// <summary>
/// Smoothing method for the fast moving average in the short block.
/// </summary>
public SmoothingMethods ShortFastMethod
{
get => _shortFastMethod.Value;
set => _shortFastMethod.Value = value;
}
/// <summary>
/// Length for the fast moving average in the short block.
/// </summary>
public int ShortFastLength
{
get => _shortFastLength.Value;
set => _shortFastLength.Value = value;
}
/// <summary>
/// Smoothing method for the medium moving average in the short block.
/// </summary>
public SmoothingMethods ShortMediumMethod
{
get => _shortMediumMethod.Value;
set => _shortMediumMethod.Value = value;
}
/// <summary>
/// Length for the medium moving average in the short block.
/// </summary>
public int ShortMediumLength
{
get => _shortMediumLength.Value;
set => _shortMediumLength.Value = value;
}
/// <summary>
/// Smoothing method for the slow moving average in the short block.
/// </summary>
public SmoothingMethods ShortSlowMethod
{
get => _shortSlowMethod.Value;
set => _shortSlowMethod.Value = value;
}
/// <summary>
/// Length for the slow moving average in the short block.
/// </summary>
public int ShortSlowLength
{
get => _shortSlowLength.Value;
set => _shortSlowLength.Value = value;
}
/// <summary>
/// Applied price for the short XDidi calculation.
/// </summary>
public AppliedPrices ShortAppliedPrice
{
get => _shortAppliedPrice.Value;
set => _shortAppliedPrice.Value = value;
}
/// <summary>
/// Enable opening short positions.
/// </summary>
public bool EnableShortEntries
{
get => _shortEnableEntry.Value;
set => _shortEnableEntry.Value = value;
}
/// <summary>
/// Enable closing short positions.
/// </summary>
public bool EnableShortExits
{
get => _shortEnableExit.Value;
set => _shortEnableExit.Value = value;
}
/// <summary>
/// Invert ratios for the short XDidi block.
/// </summary>
public bool ShortReverse
{
get => _shortReverse.Value;
set => _shortReverse.Value = value;
}
/// <summary>
/// Bar shift used for short signals.
/// </summary>
public int ShortSignalBar
{
get => _shortSignalBar.Value;
set => _shortSignalBar.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (ShortCandleType == LongCandleType)
return [(Security, LongCandleType)];
return new[]
{
(Security, LongCandleType),
(Security, ShortCandleType)
};
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longFastHistory = Array.Empty<decimal?>();
_longSlowHistory = Array.Empty<decimal?>();
_shortFastHistory = Array.Empty<decimal?>();
_shortSlowHistory = Array.Empty<decimal?>();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_longFastMa = CreateMovingAverage(LongFastMethod, LongFastLength);
_longMediumMa = CreateMovingAverage(LongMediumMethod, LongMediumLength);
_longSlowMa = CreateMovingAverage(LongSlowMethod, LongSlowLength);
_shortFastMa = CreateMovingAverage(ShortFastMethod, ShortFastLength);
_shortMediumMa = CreateMovingAverage(ShortMediumMethod, ShortMediumLength);
_shortSlowMa = CreateMovingAverage(ShortSlowMethod, ShortSlowLength);
_longFastHistory = new decimal?[Math.Max(LongSignalBar + 2, 2)];
_longSlowHistory = new decimal?[Math.Max(LongSignalBar + 2, 2)];
_shortFastHistory = new decimal?[Math.Max(ShortSignalBar + 2, 2)];
_shortSlowHistory = new decimal?[Math.Max(ShortSignalBar + 2, 2)];
var longSubscription = SubscribeCandles(LongCandleType);
ISubscriptionHandler<ICandleMessage> shortSubscriptionObj = null;
if (ShortCandleType == LongCandleType)
{
longSubscription.Bind((ICandleMessage c) =>
{
ProcessLongCandle(c);
ProcessShortCandle(c);
}).Start();
}
else
{
longSubscription.Bind(ProcessLongCandle).Start();
shortSubscriptionObj = SubscribeCandles(ShortCandleType);
shortSubscriptionObj.Bind(ProcessShortCandle).Start();
}
var primaryArea = CreateChartArea();
if (primaryArea != null)
{
DrawCandles(primaryArea, longSubscription);
DrawIndicator(primaryArea, _longFastMa);
DrawIndicator(primaryArea, _longMediumMa);
DrawIndicator(primaryArea, _longSlowMa);
if (ShortCandleType == LongCandleType)
{
DrawIndicator(primaryArea, _shortFastMa);
DrawIndicator(primaryArea, _shortMediumMa);
DrawIndicator(primaryArea, _shortSlowMa);
}
DrawOwnTrades(primaryArea);
}
if (shortSubscriptionObj != null)
{
var secondaryArea = CreateChartArea();
if (secondaryArea != null)
{
DrawCandles(secondaryArea, shortSubscriptionObj);
DrawIndicator(secondaryArea, _shortFastMa);
DrawIndicator(secondaryArea, _shortMediumMa);
DrawIndicator(secondaryArea, _shortSlowMa);
DrawOwnTrades(secondaryArea);
}
}
var priceStep = Security?.PriceStep ?? 0m;
Unit stopLossUnit = null;
Unit takeProfitUnit = null;
if (priceStep > 0m)
{
if (StopLossPoints > 0m)
stopLossUnit = new Unit(StopLossPoints * priceStep, UnitTypes.Absolute);
if (TakeProfitPoints > 0m)
takeProfitUnit = new Unit(TakeProfitPoints * priceStep, UnitTypes.Absolute);
}
StartProtection(stopLoss: stopLossUnit ?? new(), takeProfit: takeProfitUnit ?? new());
}
private void ProcessLongCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetAppliedPrice(LongAppliedPrice, candle);
var fastValue = _longFastMa.Process(new DecimalIndicatorValue(_longFastMa, price, candle.OpenTime) { IsFinal = true });
var mediumValue = _longMediumMa.Process(new DecimalIndicatorValue(_longMediumMa, price, candle.OpenTime) { IsFinal = true });
var slowValue = _longSlowMa.Process(new DecimalIndicatorValue(_longSlowMa, price, candle.OpenTime) { IsFinal = true });
if (!_longFastMa.IsFormed || !_longMediumMa.IsFormed || !_longSlowMa.IsFormed)
return;
var medium = mediumValue.GetValue<decimal>();
if (medium == 0m)
return;
var fast = fastValue.GetValue<decimal>() / medium;
var slow = slowValue.GetValue<decimal>() / medium;
if (LongReverse)
{
fast = -fast;
slow = -slow;
}
UpdateHistory(_longFastHistory, fast);
UpdateHistory(_longSlowHistory, slow);
if (!HasSignalData(_longFastHistory, _longSlowHistory, LongSignalBar))
return;
var currentFast = _longFastHistory[LongSignalBar]!.Value;
var currentSlow = _longSlowHistory[LongSignalBar]!.Value;
var previousFast = _longFastHistory[LongSignalBar + 1]!.Value;
var previousSlow = _longSlowHistory[LongSignalBar + 1]!.Value;
var openSignal = false;
var closeSignal = false;
if (previousFast > previousSlow && EnableLongEntries && currentFast <= currentSlow)
openSignal = true;
if (previousFast < previousSlow && EnableLongExits)
closeSignal = true;
ExecuteLongSignals(openSignal, closeSignal);
}
private void ProcessShortCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetAppliedPrice(ShortAppliedPrice, candle);
var fastValue = _shortFastMa.Process(new DecimalIndicatorValue(_shortFastMa, price, candle.OpenTime) { IsFinal = true });
var mediumValue = _shortMediumMa.Process(new DecimalIndicatorValue(_shortMediumMa, price, candle.OpenTime) { IsFinal = true });
var slowValue = _shortSlowMa.Process(new DecimalIndicatorValue(_shortSlowMa, price, candle.OpenTime) { IsFinal = true });
if (!_shortFastMa.IsFormed || !_shortMediumMa.IsFormed || !_shortSlowMa.IsFormed)
return;
var medium = mediumValue.GetValue<decimal>();
if (medium == 0m)
return;
var fast = fastValue.GetValue<decimal>() / medium;
var slow = slowValue.GetValue<decimal>() / medium;
if (ShortReverse)
{
fast = -fast;
slow = -slow;
}
UpdateHistory(_shortFastHistory, fast);
UpdateHistory(_shortSlowHistory, slow);
if (!HasSignalData(_shortFastHistory, _shortSlowHistory, ShortSignalBar))
return;
var currentFast = _shortFastHistory[ShortSignalBar]!.Value;
var currentSlow = _shortSlowHistory[ShortSignalBar]!.Value;
var previousFast = _shortFastHistory[ShortSignalBar + 1]!.Value;
var previousSlow = _shortSlowHistory[ShortSignalBar + 1]!.Value;
var openSignal = false;
var closeSignal = false;
if (previousFast < previousSlow && EnableShortEntries && currentFast >= currentSlow)
openSignal = true;
if (previousFast > previousSlow && EnableShortExits)
closeSignal = true;
ExecuteShortSignals(openSignal, closeSignal);
}
private void ExecuteLongSignals(bool openSignal, bool closeSignal)
{
if (closeSignal && Position > 0)
SellMarket(Position);
if (openSignal && Position <= 0)
{
var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
if (volume > 0m)
BuyMarket(volume);
}
}
private void ExecuteShortSignals(bool openSignal, bool closeSignal)
{
if (closeSignal && Position < 0)
BuyMarket(Math.Abs(Position));
if (openSignal && Position >= 0)
{
var volume = Volume + (Position > 0 ? Position : 0m);
if (volume > 0m)
SellMarket(volume);
}
}
private static void UpdateHistory(decimal?[] buffer, decimal value)
{
for (var i = buffer.Length - 1; i > 0; i--)
buffer[i] = buffer[i - 1];
buffer[0] = value;
}
private static bool HasSignalData(decimal?[] fastHistory, decimal?[] slowHistory, int signalBar)
{
var requiredIndex = signalBar + 1;
if (requiredIndex >= fastHistory.Length || requiredIndex >= slowHistory.Length)
return false;
return fastHistory[signalBar].HasValue &&
fastHistory[requiredIndex].HasValue &&
slowHistory[signalBar].HasValue &&
slowHistory[requiredIndex].HasValue;
}
private static decimal GetAppliedPrice(AppliedPrices priceType, ICandleMessage candle)
{
return priceType switch
{
AppliedPrices.Close => candle.ClosePrice,
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPrices.Weighted => (candle.ClosePrice * 2m + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice
? candle.HighPrice
: candle.ClosePrice < candle.OpenPrice
? candle.LowPrice
: candle.ClosePrice,
AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
? (candle.HighPrice + candle.ClosePrice) / 2m
: candle.ClosePrice < candle.OpenPrice
? (candle.LowPrice + candle.ClosePrice) / 2m
: candle.ClosePrice,
AppliedPrices.Demark => CalculateDemarkPrice(candle),
_ => candle.ClosePrice,
};
}
private static decimal CalculateDemarkPrice(ICandleMessage candle)
{
var baseSum = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
var adjusted = candle.ClosePrice < candle.OpenPrice
? (baseSum + candle.LowPrice) / 2m
: candle.ClosePrice > candle.OpenPrice
? (baseSum + candle.HighPrice) / 2m
: (baseSum + candle.ClosePrice) / 2m;
return ((adjusted - candle.LowPrice) + (adjusted - candle.HighPrice)) / 2m;
}
private static DecimalLengthIndicator CreateMovingAverage(SmoothingMethods method, int length)
{
return method switch
{
SmoothingMethods.Sma => new SimpleMovingAverage { Length = length },
SmoothingMethods.Ema => new ExponentialMovingAverage { Length = length },
SmoothingMethods.Smma => new SmoothedMovingAverage { Length = length },
SmoothingMethods.Lwma => new WeightedMovingAverage { Length = length },
SmoothingMethods.T3 => new TripleExponentialMovingAverage { Length = length },
SmoothingMethods.Ama => new KaufmanAdaptiveMovingAverage { Length = length },
_ => new ExponentialMovingAverage { Length = length }
};
}
/// <summary>
/// Available smoothing methods that approximate the original MQL implementation.
/// </summary>
public enum SmoothingMethods
{
Sma,
Ema,
Smma,
Lwma,
Jjma,
JurX,
ParMa,
T3,
Vidya,
Ama
}
/// <summary>
/// Price sources supported by the strategy.
/// </summary>
public enum AppliedPrices
{
Close = 1,
Open,
High,
Low,
Median,
Typical,
Weighted,
Simple,
Quarter,
TrendFollow0,
TrendFollow1,
Demark
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage, TripleExponentialMovingAverage,
KaufmanAdaptiveMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class x_didi_index_cloud_duplex_strategy(Strategy):
def __init__(self):
super(x_didi_index_cloud_duplex_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._fast_length = self.Param("FastLength", 3)
self._medium_length = self.Param("MediumLength", 8)
self._slow_length = self.Param("SlowLength", 20)
self._signal_bar = self.Param("SignalBar", 0)
self._enable_long_entries = self.Param("EnableLongEntries", True)
self._enable_long_exits = self.Param("EnableLongExits", True)
self._enable_short_entries = self.Param("EnableShortEntries", True)
self._enable_short_exits = self.Param("EnableShortExits", True)
self._stop_loss_points = self.Param("StopLossPoints", 1000.0)
self._take_profit_points = self.Param("TakeProfitPoints", 2000.0)
self._fast_ma = None
self._medium_ma = None
self._slow_ma = None
self._fast_history = []
self._slow_history = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@property
def MediumLength(self):
return self._medium_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def EnableLongEntries(self):
return self._enable_long_entries.Value
@property
def EnableLongExits(self):
return self._enable_long_exits.Value
@property
def EnableShortEntries(self):
return self._enable_short_entries.Value
@property
def EnableShortExits(self):
return self._enable_short_exits.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
def OnStarted2(self, time):
super(x_didi_index_cloud_duplex_strategy, self).OnStarted2(time)
self._fast_ma = SimpleMovingAverage()
self._fast_ma.Length = self.FastLength
self._medium_ma = SimpleMovingAverage()
self._medium_ma.Length = self.MediumLength
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self.SlowLength
buf_size = max(self.SignalBar + 2, 2)
self._fast_history = [None] * buf_size
self._slow_history = [None] * buf_size
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ma, self._medium_ma, self._slow_ma, self._process_candle).Start()
sec = self.Security
ps = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
sl_unit = Unit()
tp_unit = Unit()
if ps > 0:
if self.StopLossPoints > 0:
sl_unit = Unit(self.StopLossPoints * ps, UnitTypes.Absolute)
if self.TakeProfitPoints > 0:
tp_unit = Unit(self.TakeProfitPoints * ps, UnitTypes.Absolute)
self.StartProtection(tp_unit, sl_unit)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_ma)
self.DrawIndicator(area, self._medium_ma)
self.DrawIndicator(area, self._slow_ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_v, medium_v, slow_v):
if candle.State != CandleStates.Finished:
return
if not self._fast_ma.IsFormed or not self._medium_ma.IsFormed or not self._slow_ma.IsFormed:
return
med = float(medium_v)
if med == 0:
return
fast_ratio = float(fast_v) / med
slow_ratio = float(slow_v) / med
self._update_history(self._fast_history, fast_ratio)
self._update_history(self._slow_history, slow_ratio)
if not self._has_signal_data():
return
sb = self.SignalBar
cur_fast = self._fast_history[sb]
cur_slow = self._slow_history[sb]
prev_fast = self._fast_history[sb + 1]
prev_slow = self._slow_history[sb + 1]
open_long = False
close_long = False
open_short = False
close_short = False
if prev_fast > prev_slow and self.EnableLongEntries and cur_fast <= cur_slow:
open_long = True
if prev_fast < prev_slow and self.EnableLongExits:
close_long = True
if prev_fast < prev_slow and self.EnableShortEntries and cur_fast >= cur_slow:
open_short = True
if prev_fast > prev_slow and self.EnableShortExits:
close_short = True
if close_long and self.Position > 0:
self.SellMarket()
if open_long and self.Position <= 0:
vol = self.Volume + (abs(self.Position) if self.Position < 0 else 0)
if vol > 0:
self.BuyMarket()
if close_short and self.Position < 0:
self.BuyMarket()
if open_short and self.Position >= 0:
vol = self.Volume + (self.Position if self.Position > 0 else 0)
if vol > 0:
self.SellMarket()
def _update_history(self, buf, value):
for i in range(len(buf) - 1, 0, -1):
buf[i] = buf[i - 1]
buf[0] = value
def _has_signal_data(self):
sb = self.SignalBar
req = sb + 1
if req >= len(self._fast_history) or req >= len(self._slow_history):
return False
return (self._fast_history[sb] is not None and
self._fast_history[req] is not None and
self._slow_history[sb] is not None and
self._slow_history[req] is not None)
def OnReseted(self):
super(x_didi_index_cloud_duplex_strategy, self).OnReseted()
self._fast_history = []
self._slow_history = []
def CreateClone(self):
return x_didi_index_cloud_duplex_strategy()