XDidi Index Cloud Duplex 策略
概述
XDidi Index Cloud Duplex 策略将 MQL5 专家顾问 Exp_XDidi_Index_Cloud_Duplex 的双通道逻辑迁移到 StockSharp。策略同时维护多头与空头两套 XDidi 指标配置,通过可配置的时间框架计算快速、慢速均线与中轴均线的比例关系,并据此发出入场与出场信号。
交易逻辑
- 指标计算
- 对每个模块分别计算三条均线(快线、中线、慢线),价格来源可选收盘价、典型价、Demark 价等。
- XDidi 指数定义为
fast / medium与slow / medium。如果启用Reverse,两个比值同时取相反数。
- 信号判定
- 多头模块:若前一根参考柱存在
fast > slow且信号柱收盘时fast <= slow,触发做多;若前一柱fast < slow,触发平多。 - 空头模块:若前一根参考柱
fast < slow且信号柱fast >= slow,触发做空;若前一柱fast > slow,触发平空。 LongSignalBar与ShortSignalBar控制信号柱与上一参考柱的偏移量。
- 多头模块:若前一根参考柱存在
- 订单执行
- 使用策略的
Volume下市场单,若已有相反头寸会先行平仓再反向开仓。 StopLossPoints与TakeProfitPoints以价格步长表示,最终通过StartProtection应用。
- 使用策略的
参数说明
- 时间框架:
LongCandleType、ShortCandleType。 - 均线类型与周期:
LongFast/Medium/SlowMethod、ShortFast/Medium/SlowMethod及对应长度。缺少的平滑算法(JJMA、JurX、ParMA、VIDYA)退化为指数均线。 - 价格来源:
LongAppliedPrice、ShortAppliedPrice。 - 交易开关:
EnableLongEntries、EnableLongExits、EnableShortEntries、EnableShortExits。 - 信号柱偏移:
LongSignalBar、ShortSignalBar。 - 比值取反:
LongReverse、ShortReverse。 - 风险控制:
StopLossPoints、TakeProfitPoints(为 0 时关闭)。 - 下单数量:基础属性
Volume。
实现细节
- 使用 StockSharp 自带的均线指标;无法一一对应的平滑算法采用指数均线作为替代。
- 仅在蜡烛收盘后处理信号,与原版
IsNewBar行为一致。 - 仅缓存所需的少量历史值,避免维护大型集合。
- 即便停用止损/止盈,仍调用
StartProtection()以保持策略生命周期一致。
使用建议
- 确认数据源能够提供所选时间框架的蜡烛。
- 根据品种特性调整均线参数与价格类型,可利用优化器批量测试。
- 当多空模块使用不同时间框架时,图表会为每个模块创建独立区域,便于观察。
与 MQL5 版本的差异
- 未实现原始 EA 中的资金管理模式(MM、MarginMode),下单量由
Volume控制。 SmoothAlgorithms.mqh中的部分特殊平滑算法使用近似实现。- 止损/止盈不再单独附加到订单,而是通过策略保护统一处理。
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()