在 GitHub 上查看

AltrTrend Signal v2.2 策略

该策略是 MetaTrader 专家顾问 Exp_AltrTrend_Signal_v2_2 的 StockSharp 版本。它复刻 AltrTrend Signal 指标的自适应通道 算法,并按照原版设置的延迟条形执行信号。ADX 数值会压缩或放宽通道,因此只有在趋势强度足够时才会产生突破。

工作原理

  1. 在选定周期的每根收盘 K 线上计算动态通道。通道宽度取决于窗口内的最高价和最低价,窗口长度根据上一根的 ADX 值进行放大或缩小(KPeriod / ADX)。
  2. 内部边界 (smin, smax) 按 KPercent 向中线收缩,价格必须收在该区间之外才会改变趋势状态。
  3. 当趋势从空头转为多头且收盘价高于上边界时生成买入信号;跌破下边界则生成卖出信号。下单会在 SignalBar 参数指定的延迟条形上执行,与 MQL5 专家顾问保持一致。
  4. 止损与止盈以点数转换为价格步长,确保保护性离场与原始 EA 的固定 SL/TP 行为一致。

细节

  • 入场条件
    • 多头:上一状态为空头或中性,价格收在上边界之外,并且允许开多。若允许,系统会自动平掉已有空单。
    • 空头:上一状态为多头或中性,价格收在下边界之外,并且允许开空。若允许,系统会自动平掉已有多单。
  • 出场条件
    • 相反方向的突破信号(需启用对应方向的平仓开关)。
    • 价格步长表示的止损或止盈距离。
  • 多空方向:双向交易,开仓和平仓权限互相独立。
  • 风险控制
    • StopLossPointsTakeProfitPoints 将原策略中的 MM 参数转化为固定距离的保护性退出。
  • 指标设置
    • KPercent 控制通道边界向中心收缩的幅度。
    • KStop 保留原指标的箭头投射值,便于绘图或日志。
    • KPeriod 定义在 ADX 调整前的基准窗口长度。
    • AdxPeriod 设置用于调节通道宽度的 ADX 周期。
    • SignalBar 指定执行信号前需要等待的完整 K 线数量。
  • 推荐市场
    • 适用于趋势强弱波动明显的市场,例如主要货币对、黄金以及股指期货。默认时间周期为 H1,与原始模板一致。

参数

参数 说明
CandleType 用于构建自适应通道的时间周期。
KPercent 通道内边界的收缩百分比。
KStop 箭头目标价的乘数(保留兼容性)。
KPeriod ADX 调整前的基础观察周期。
AdxPeriod 控制通道宽度的 ADX 周期。
SignalBar 延迟执行信号的已完成 K 线数量。
AllowBuyEntries / AllowSellEntries 是否允许开多/开空。
AllowBuyExits / AllowSellExits 是否允许自动平多/平空。
StopLossPoints 以价格步长表示的止损距离(0 表示关闭)。
TakeProfitPoints 以价格步长表示的止盈距离(0 表示关闭)。

该移植版本完整保留了原专家顾问的参数和控制开关,便于在 StockSharp Designer、Shell 或 Runner 中复现相同的交易 行为。

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AltrTrend signal strategy using EMA crossover for alternating trend detection.
/// </summary>
public class AltrTrendSignalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

		_fastPeriod = Param(nameof(FastPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		_prevFast = null;
		_prevSlow = null;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, 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)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fastVal > slowVal;

		_prevFast = fastVal;
		_prevSlow = slowVal;

		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}