在 GitHub 上查看

Spread Informer 策略

用于收集所选品种买卖价差(Bid-Ask Spread)的完整统计信息,并在价差超过可配置的上限时给出提示。策略持续订阅 Level1 行情,记录价差的最大值、最小值和平均值(以点数表示),在停止时输出总结,可用于在回测前评估流动性情况或验证历史数据质量。

详细信息

  • 数据来源:Level1 的最优买价(Bid)与最优卖价(Ask)。
  • 统计内容
    • 观测周期的起始与结束时间。
    • 价差最大值及对应时间。
    • 价差最小值及对应时间。
    • 基于所有 Level1 更新计算的平均价差。
  • 警报机制
    • 当价差(点数)突破 MaxSpreadPoints 阈值时,可选地触发提醒。
    • AlertIntervalSeconds 控制相邻提醒之间的最小时间间隔,避免日志被刷屏。
    • 只有在价差从下方向上突破阈值时才会触发提醒。
  • 日志输出
    • 实时提醒通过 LogInfo 写入日志。
    • 策略停止时在 OnStopped 输出总体统计。
  • 默认参数
    • MaxSpreadPoints = 0(禁用提醒)。
    • AlertIntervalSeconds = 0(不限制提醒频率)。

参数

名称 说明 默认值 备注
MaxSpreadPoints 最大允许的价差(点)。设置为 0 时关闭提醒。 0 点数基于品种的最小报价步长计算。
AlertIntervalSeconds 连续提醒之间的最小时间间隔。 0 当价差持续过大时,可避免重复提醒。

使用建议

  1. 将策略绑定到目标品种,并确认能获取 Level1 行情。
  2. 根据该品种的可接受价差配置 MaxSpreadPoints
  3. 如需抑制频繁提醒,可提高 AlertIntervalSeconds 的值。
  4. 停止策略后查看日志输出,即可获得完整的价差统计。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Spread Informer strategy: CCI momentum crossover.
/// Buys when CCI crosses above zero, sells when crosses below zero.
/// </summary>
public class SpreadInformerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevCci;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public SpreadInformerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_cciLevel = Param(nameof(CciLevel), 100m)
			.SetDisplay("CCI Level", "CCI threshold for crossover", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCci = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCci = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevCci < -CciLevel && cciValue >= -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevCci > CciLevel && cciValue <= CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevCci = cciValue;
		_hasPrev = true;
	}
}