在 GitHub 上查看

Triple SMA Spread 策略

概述

本策略是 MetaTrader 5 专家顾问 3sma.mq5(id 21495)的 C# 版本,实现于 StockSharp 平台。策略保持原始思路:当三条简单移动平均线之间的价差达到设定的阈值时开仓或平仓。通过 StockSharp 的高级 API,我们只需订阅蜡烛图数据并绑定指标,无需手动维护序列。

原始 MT5 行为

MT5 版本使用三条简单移动平均线,它们具有不同的周期和水平位移。第一条(快速线)使用当前柱,第二条和第三条分别向过去偏移 1 根和 2 根柱。每个 tick 到来时,算法执行以下流程:

  1. 根据品种报价精度,将用户设置的点差(pips)转换为价格单位。
  2. 当快速 SMA 低于中速 SMA 至少半个点差时平掉多头;当快速 SMA 高于中速 SMA 至少半个点差时平掉空头。
  3. 如果满足 MA1 > MA2 + spreadMA2 > MA3 + spread,且没有该 EA 的其他多单,则开多;若三个均线呈相反顺序,则开空。
  4. 仅使用市价单,仓位大小固定,不设置止损或止盈。

StockSharp 实现特点

  • 指标管理:创建三个 SimpleMovingAverage 指标并绑定同一根蜡烛订阅。通过简短的历史缓冲区模拟 MT5 中的“shift”参数,使比较值都来自已完成的柱。
  • 点差换算MaSpreadPips 参数以点(pip)输入。策略利用 Security.PriceStep(若不可用则回退到 Security.Step),对三位或五位报价的外汇品种乘以 10,以复制 MT5 中针对小数点报价的调整。
  • 订单执行:使用 BuyMarketSellMarket 提交市价单。如果需要反向开仓,会在基础手数上加上当前净头寸的绝对值,以一次订单完成平仓与反向建仓。
  • 可视化:若环境支持图表,策略会绘制原始蜡烛、三条移动平均线以及成交轨迹。

参数说明

名称 含义 默认值
Volume 每次市价下单的数量。 0.1
FastMaPeriod 快速 SMA(MA1)的周期。 9
FastMaShift 快速 SMA 使用的完成蜡烛偏移量。 0
MiddleMaPeriod 中速 SMA(MA2)的周期。 14
MiddleMaShift 中速 SMA 使用的完成蜡烛偏移量。 1
SlowMaPeriod 慢速 SMA(MA3)的周期。 29
SlowMaShift 慢速 SMA 使用的完成蜡烛偏移量。 2
MaSpreadPips 相邻两条 SMA 之间要求的最小点差。 10
CandleType 用于计算的蜡烛类型。 1 分钟时间框

交易逻辑

  1. 等待三个移动平均线都完成形成,历史缓冲区包含足够的位移值。
  2. 将点差从 pip 转换为价格,并计算一半点差供平仓过滤使用。
  3. 平仓规则
    • 若快速 SMA 低于中速 SMA 至少半个点差,则平掉多头。
    • 若快速 SMA 高于中速 SMA 至少半个点差,则平掉空头。
  4. 开仓规则
    • 当快速 SMA 大于中速 SMA 加上完整点差,且中速 SMA 大于慢速 SMA 加上同样点差时开多(如当前持有空头,则一次性反手)。
    • 当快速 SMA 小于中速 SMA 减去点差,且中速 SMA 小于慢速 SMA 减去点差时开空(如当前持有多头,则一次性反手)。

与 MT5 版本的差异

  • StockSharp 仅跟踪每个标的的净头寸。出现反向信号时,会按“基础手数 + 当前仓位绝对值”的数量发出单笔市价单,既平掉原有仓位又建立新仓。MT5 EA 可以同时持有多单和空单。
  • 点差换算基于 Security 提供的元数据。如果既没有 PriceStep 也没有 Step,则回退到值 1
  • 策略在蜡烛收盘时执行,而不是每个 tick,因为高级 API 是基于蜡烛订阅的。
  • 未移植 MT5 中详尽的日志函数,如需额外日志可直接使用 StockSharp 的日志系统。

使用建议

  • 选择的蜡烛时间框应与在 MT5 中使用的时间框一致。
  • 交易点值与标准 pip 不同时,请重新调整点差参数。
  • 由于使用已完成的蜡烛,信号会在当前蜡烛收盘后才触发。
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>
/// Triple SMA strategy that trades when three moving averages are properly aligned.
/// Enters long when fast > middle > slow, enters short when fast &lt; middle &lt; slow.
/// </summary>
public class TripleSmaSpreadStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _middlePeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private int _prevSignal;

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

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

	public int MiddlePeriod
	{
		get => _middlePeriod.Value;
		set => _middlePeriod.Value = value;
	}

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

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

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

		_middlePeriod = Param(nameof(MiddlePeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Middle Period", "Middle SMA period", "Indicators");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSignal = 0;
	}

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

		_prevSignal = 0;

		var fastSma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowSma = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastSma, slowSma, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var close = candle.ClosePrice;
		var signal = 0;
		if (fast > slow && close > fast)
			signal = 1;
		else if (fast < slow && close < fast)
			signal = -1;

		if (signal == _prevSignal)
			return;

		var oldSignal = _prevSignal;
		_prevSignal = signal;

		if (signal == 1 && oldSignal <= 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal == -1 && oldSignal >= 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}