在 GitHub 上查看

Elli 策略

概述

Elli 策略将 MetaTrader 4 的 "Elli" 专家顾问迁移到 StockSharp 的高级 API。原版脚本使用 1 小时 Ichimoku 结构配合低周期 ADX 过滤,同时内置固定的止损止盈。移植版本保留所有趋势判定规则,以 StartProtection 替代手工修改订单,并把每个调节参数封装为可优化的 StrategyParam<T>,方便在 Designer 中测试不同组合。

交易逻辑

  1. Ichimoku 趋势结构
    • 订阅 CandleType 指定的主时间框(默认 H1),按照原始周期 19/60/120 计算 Tenkan、Kijun 与 Senkou。
    • 多头信号要求 Tenkan > Kijun > Senkou Span A > Senkou Span B,且蜡烛收盘价位于 Kijun 之上;空头条件完全相反。
    • Tenkan 与 Kijun 的绝对差值必须超过 TenkanKijunGapPips(以点为单位),以过滤掉云层平坦的行情。
  2. 方向动能确认
    • 第二个数据流在 AdxCandleType(默认 M1)上运行 Average Directional Index。
    • 当上一根 +DI 低于 ConvertLow、当前 +DI 突破 ConvertHigh 时才允许做多;做空则针对 −DI 检查同样的阈值,复制 MQL 中 iADX 的加速判断。
  3. 下单执行
    • 所有条件满足时,策略以 OrderVolume + |Position| 的数量发送市价单,若存在反向仓位会先行对冲关闭。
    • 系统始终保持单向持仓,与原脚本的 OrdersTotal() < 1 检查一致。
  4. 风险控制
    • StartProtection 根据点值换算出绝对价格,为订单附加对称的止损与止盈。
    • 持仓管理交由保护单完成,行为与 MT4 版本完全一致。

指标与数据订阅

  • 主时间框:CandleType(默认 1 小时)用于 Ichimoku 计算。
  • ADX 时间框:AdxCandleType(默认 1 分钟)用于监控 +DI/−DI。
  • 指标:Ichimoku(Tenkan、Kijun、Senkou Span B)与 AverageDirectionalIndex
  • 若界面存在图表区域,可绘制蜡烛、指标及成交轨迹。

参数

名称 默认值 说明
OrderVolume 1 市价单的基础手数。
TakeProfitPips 60 止盈距离(点)。
StopLossPips 30 止损距离(点)。
TenkanPeriod 19 Tenkan-sen 周期。
KijunPeriod 60 Kijun-sen 周期。
SenkouSpanBPeriod 120 Senkou Span B 周期。
TenkanKijunGapPips 20 Tenkan 与 Kijun 的最小点差。
ConvertHigh 13 当前 DI 必须突破的阈值。
ConvertLow 6 前一根 DI 需要维持在其下方的阈值。
AdxPeriod 10 ADX 计算周期。
CandleType H1 Ichimoku 使用的时间框。
AdxCandleType M1 ADX 使用的时间框。

所有参数均通过 StrategyParam<T> 暴露,可在 Designer 中优化与回测。

实现细节

  • 点值转换遵循常见外汇习惯:五位报价使用 0.0001,三位报价使用 0.01,确保阈值与原策略一致。
  • _latestPlusDi_previousPlusDi_latestMinusDi_previousMinusDi 缓存 DI 序列,精确复现 iADX(symbol, timeframe, period, mode, shift) 中 shift=0/1 的比较。
  • IsFormedAndOnlineAndAllowTrading() 确保所有数据与指标准备完毕后才允许下单,避免热身阶段的误触发。
  • 下单量使用 Volume + Math.Abs(Position),即刻扭转旧仓,保持单一方向的暴露。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Elli: EMA crossover with ATR momentum confirmation.
/// Fast EMA above slow EMA = bullish, below = bearish.
/// Entry when ATR expansion confirms trend strength.
/// </summary>
public class ElliStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevAtr;
	private decimal _entryPrice;

	public ElliStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastLength = Param(nameof(FastLength), 19)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 60)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for momentum.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;

		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_prevAtr = atrVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit: stop or take based on ATR
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: EMA crossover with ATR expansion
		if (Position == 0)
		{
			var atrRising = atrVal > _prevAtr;

			if (_prevFast <= _prevSlow && fastVal > slowVal && atrRising)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevFast >= _prevSlow && fastVal < slowVal && atrRising)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
		_prevAtr = atrVal;
	}
}