Ver en GitHub

Negative Spread Strategy

The Negative Spread strategy exploits rare moments when the best ask price falls below the best bid price, creating a negative spread. When this mispricing appears, the strategy sells at market and attempts to capture the abnormal spread. After the short position is opened, it is closed on the next order book update once the market returns to a normal state.

The system listens only to order book events and does not rely on candles or indicators. Optional stop-loss and take-profit parameters are provided as safety measures and are calculated in pips using the instrument's tick size.

Details

  • Entry Criteria: BestAsk < BestBid and no active position.
  • Long/Short: Short only.
  • Exit Criteria: Position is closed immediately after it opens.
  • Stops: Optional stop-loss and take-profit in pips.
  • Default Values:
    • Volume = 1
    • TakeProfitPips = 5000
    • StopLossPips = 5000
  • Filters:
    • Category: Arbitrage
    • Direction: Short
    • Indicators: None
    • Stops: Optional
    • Complexity: Basic
    • Timeframe: Tick
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: High
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that detects price dislocations using Bollinger Bands
/// and trades mean reversion when price extends beyond bands.
/// </summary>
public class NegativeSpreadStrategy : Strategy
{
	private readonly StrategyParam<int> _bbPeriod;
	private readonly StrategyParam<decimal> _bbWidth;
	private readonly StrategyParam<DataType> _candleType;

	public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
	public decimal BbWidth { get => _bbWidth.Value; set => _bbWidth.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public NegativeSpreadStrategy()
	{
		_bbPeriod = Param(nameof(BbPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");

		_bbWidth = Param(nameof(BbWidth), 1.5m)
			.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var bb = new BollingerBands { Length = BbPeriod, Width = BbWidth };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bb, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(1, UnitTypes.Percent),
			stopLoss: new Unit(0.5m, UnitTypes.Percent)
		);

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

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

		if (!bbValue.IsFormed)
			return;

		var bb = (BollingerBandsValue)bbValue;
		if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
			return;

		var close = candle.ClosePrice;

		// Mean reversion: sell when above upper band, buy when below lower band
		if (close > upper && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}
		else if (close < lower && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
	}
}