在 GitHub 上查看

TrueSort 1001 策略

TrueSort 1001 是对原始 MQL 智能交易顾问的严格趋势化改写。策略同时监控五条简单移动平均线,只有在最近三根收盘K线上保持严格顺序时才会动作。与此同时,ADX 必须上升并高于设定阈值以确认趋势强度。入场后使用按价格步长计算的移动止损来保护头寸,只要均线排序被破坏就立即平仓。

逻辑

趋势与动量过滤

  • 在所选周期上计算五条 SMA(默认周期分别为 10、20、50、100、200)。
  • 做多时,最近三根已完成K线上必须满足 SMA10 > SMA20 > SMA50 > SMA100 > SMA200 的严格递减关系。
  • 做空时,要求同样三根K线满足完全相反的顺序。
  • ADX 的周期为 AdxPeriod,只有当当前值高于 AdxThreshold 且大于上一根K线的值时才允许开仓,表示趋势力量正在增强。

入场条件

  1. 当前没有持仓。
  2. 最近三根历史K线满足上述 SMA 排序规则。
  3. ADX 过滤条件成立。
  4. 在当前K线收盘时以 Volume 手下达市价单。

离场条件

  • 均线失序: 如果当前K线收盘后均线不再保持严格顺序,则立即平仓。
  • 移动止损:StopLossPoints 乘以品种的 PriceStep 得到绝对价格距离。多头仓位的初始止损设置在 SMA100收盘价 - 距离 之间的较大值;空头仓位则取 SMA100收盘价 + 距离 之间的较小值。之后每根K线都会只向盈利方向收紧止损,不会后移。一旦价格触发该水平,仓位会以市价平仓。

其他说明

  • 仅处理已经完成的K线,未收盘的蜡烛全部忽略。
  • 策略内部维护长度为 3 的均线历史缓冲,用以还原 MQL 中 shift 参数的行为,无需调用 GetValue() 访问历史值。
  • ADX 通过 BindEx 绑定,只有在数据完全形成且策略在线时才尝试交易。

参数

名称 默认值 说明
Volume 0.1 每次市价单的下单手数。
StopLossPoints 100 以价格步长表示的移动止损距离,设为 0 可关闭。
Sma10Length 10 最快 SMA 的周期。
Sma20Length 20 第二条 SMA 的周期。
Sma50Length 50 中间 SMA 的周期。
Sma100Length 100 既参与排序又用于初始止损的 SMA 周期。
Sma200Length 200 用于确认长期趋势的最慢 SMA 周期。
AdxPeriod 14 ADX 指标周期。
AdxThreshold 25 触发交易所需的 ADX 最小值并要求向上。
CandleType TimeSpan.FromHours(1).TimeFrame() 全部指标的计算蜡烛类型。

实现细节

  • 采用 StockSharp 的高级 API,通过一次订阅同时绑定五条 SMA 与 ADX。
  • 通过长度为三的数组保存最新的 SMA 值,实现与 MQL 脚本相同的位移逻辑,同时避免使用 GetValue()
  • 移动止损由策略手动维护,但仍调用 StartProtection() 以保留标准保护框架的可扩展性。
  • 代码内的注释全部使用英文,便于全球开发者阅读和维护。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy requiring strict moving average alignment
/// and rising volatility (ATR) before entering trades.
/// Exits when alignment is lost.
/// </summary>
public class TrueSort1001Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _midLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

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

	public TrueSort1001Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast SMA", "Fast SMA period.", "Indicators");

		_midLength = Param(nameof(MidLength), 50)
			.SetDisplay("Mid SMA", "Medium SMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 200)
			.SetDisplay("Slow SMA", "Slow SMA period.", "Indicators");

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

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

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

	public int MidLength
	{
		get => _midLength.Value;
		set => _midLength.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;
		_prevMid = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;
	}

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

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

		var fast = new SimpleMovingAverage { Length = FastLength };
		var mid = new SimpleMovingAverage { Length = MidLength };
		var slow = new SimpleMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

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

		var close = candle.ClosePrice;
		var bullishAligned = fastVal > midVal && midVal > slowVal;
		var bearishAligned = fastVal < midVal && midVal < slowVal;
		var atrRising = atrVal > _prevAtr;

		// Exit on alignment lost
		if (Position > 0 && !bullishAligned)
		{
			SellMarket();
		}
		else if (Position < 0 && !bearishAligned)
		{
			BuyMarket();
		}

		// Entry on alignment + rising ATR
		if (Position == 0)
		{
			if (bullishAligned && atrRising && close > fastVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (bearishAligned && atrRising && close < fastVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

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