Открыть на GitHub

Стратегия Negative Spread

Стратегия Negative Spread использует редкие ситуации, когда лучшая цена спроса оказывается ниже лучшей цены предложения, что создаёт отрицательный спред. При появлении такого дисбаланса стратегия продаёт по рынку, пытаясь захватить аномальный спред. После открытия короткой позиции она закрывается при следующем обновлении стакана, когда рынок возвращается в нормальное состояние.

Система анализирует только события стакана и не использует свечи или индикаторы. В качестве защитных мер доступны параметры стоп-лосса и тейк-профита, рассчитываемые в пипсах относительно шага цены инструмента.

Детали

  • Условия входа: Ask < Bid и отсутствует открытая позиция.
  • Длинные/короткие: Только короткие.
  • Условия выхода: Позиция закрывается сразу после открытия.
  • Стопы: Опциональные стоп-лосс и тейк-профит в пипсах.
  • Значения по умолчанию:
    • Volume = 1
    • TakeProfitPips = 5000
    • StopLossPips = 5000
  • Фильтры:
    • Категория: Арбитраж
    • Направление: Short
    • Индикаторы: Нет
    • Стопы: Опционально
    • Сложность: Базовая
    • Таймфрейм: Тиковый
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Высокий
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();
		}
	}
}