在 GitHub 上查看

同步图表确认策略

该策略基于原始 MQL “SyncCharts” 工具的思路:监听同一交易品种的两路 K 线数据,仅在两路数据同步且趋势方向一致时 才允许开仓。主图代表交易者重点关注的时间框架,副图通常是更快的周期或其他聚合方式。通过强制两路信号保持一致, 策略可以过滤掉由于不同时间框架暂时错位而产生的噪声信号。

系统特别适用于具有多时间框架趋势结构的标的,例如指数期货和主要货币对。当两个图表不同步时仓位会自动平仓, 这让账户状态始终与交易者看到的图表保持一致,从而避免因图表滞后而造成的意外风险。

细节

  • 入场条件
    • 做多:主图与副图的简单移动平均线(SMA)在最近一根已完成的 K 线上均呈上升趋势,并且两根 K 线的时间戳差值 小于同步容差。
    • 做空:两条 SMA 同时向下,且时间差仍在容差范围内。
  • 离场条件
    • 时间不同步:若两路数据的最新 K 线间隔超过容差,立即平仓。
    • 趋势分歧:若一条 SMA 上升而另一条下降,立即平仓。
  • 止损:没有额外的硬性止损,自动平仓逻辑相当于软性止损。
  • 多空方向:支持双向交易。
  • 默认参数
    • 主图:5 分钟时间框架。
    • 副图:1 分钟时间框架。
    • SMA 长度:两路数据均为 20 周期。
    • 同步容差:K 线开盘时间差 15 秒。
  • 过滤器
    • 分类:趋势确认 / 多时间框架。
    • 方向:双向。
    • 指标:双 SMA。
    • 止损:无固定止损,仅在不同步或方向冲突时平仓。
    • 复杂度:中等(需要双订阅与同步检查)。
    • 时间框架:可配置(默认日内)。
    • 季节性:无。
    • 神经网络:无。
    • 背离:通过拒绝方向不一致的情况实现过滤。
    • 风险等级:中等,得益于双重确认。

工作流程

  1. 使用 StockSharp 的高级 API 建立两个蜡烛订阅:主图和副图。
  2. 每个数据流通过相同周期的 SMA,比较当前值与前一根蜡烛的值以确定趋势方向。
  3. 当两根蜡烛都完成后,计算时间戳差值,必须小于设定的容差。
  4. 若两路数据同步且趋势一致向上,则买入(若有空头仓位会先平掉);若趋势一致向下,则卖出(若有多头仓位会先 平掉)。
  5. 一旦数据不同步或趋势分歧,立即平仓,确保账户与图表保持同步。

使用建议

  • 为同一品种选择两个通常高度相关的时间框架(例如 5 分钟 + 1 分钟,或 1 小时 + 15 分钟)。
  • 如果数据源存在轻微延迟,可适当调大同步容差。
  • 在实盘部署时,建议结合独立的风险管理或止损模块。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Sync Charts Confirmation strategy: dual SMA trend confirmation.
/// Uses fast and slow SMAs to confirm trend direction.
/// Buys when both SMAs trend up, sells when both trend down.
/// </summary>
public class SyncChartsConfirmationStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private bool _wasBullish;
	private bool _hasPrevTrend;
	private int _candlesSinceTrade;

	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 int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public SyncChartsConfirmationStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 40)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between signals", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_wasBullish = false;
		_hasPrevTrend = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrevTrend = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var fast = new SimpleMovingAverage { Length = FastPeriod };
		var slow = new SimpleMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var isBullish = fast > slow;

		if (_hasPrevTrend && isBullish != _wasBullish && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (isBullish && Position <= 0)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (!isBullish && Position >= 0)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_wasBullish = isBullish;
		_hasPrevTrend = true;
	}
}