Открыть на GitHub

Стратегия разворота по полосам Боллинджера

Стратегия является портом эксперт-советника BollingerBandsEA (ver. 3.0) с платформы MetaTrader. Она ищет возврат к среднему после экстремальных выходов цены за полосы Боллинджера внутри активной сессии.

Логика торговли

  1. Подписывается на основную внутридневную серию свечей (по умолчанию 15 минут) и на дневные свечи для трендового фильтра.
  2. Рассчитывает полосы Боллинджера (период 20, множитель 2.0) на внутридневных данных и простую среднюю с периодом 100 на дневных закрытиях.
  3. Отслеживает текущие и предыдущие дневные экстремумы, а также сохраняет значения полос с предыдущей свечи.
  4. Открывает сделки только в торговом окне: с момента SessionStartOffsetMinutes минут после открытия рынка до SessionEndOffsetMinutes минут до закрытия.
  5. Прекращает открывать новые позиции, когда суммарный результат за текущий день (включая плавающий PnL) становится положительным.
  6. Условия для продажи: предыдущая свеча медвежья, закрылась выше верхней полосы, текущая цена остаётся выше полосы, ширина полос достаточна, цена ниже дневной SMA, и цена обновляет текущий или предыдущий дневной максимум.
  7. Условия для покупки: предыдущая свеча бычья, закрылась ниже нижней полосы, текущая цена остаётся ниже полосы, ширина полос достаточна, цена выше дневной SMA, и цена обновляет текущий или предыдущий дневной минимум.
  8. Размер позиции может быть фиксированным или рассчитываться по заданному проценту риска с учётом дистанции до стоп-лосса в пунктах.
  9. Выход из позиции контролируется стоп-лоссом, тейк-профитом, опциональным закрытием на средней полосе, трейлинг-стопом, переходом в безубыток и принудительным закрытием убыточных позиций по истечении заданного времени.

Параметры

Параметр Описание
CandleType Тип свечей для основной логики.
BollingerLength Период расчёта полос Боллинджера.
BollingerWidth Множитель ширины полос.
DailyMaLength Период дневной SMA для фильтра тренда.
StopLossPoints Дистанция стоп-лосса в пунктах.
UseRiskVolume Использовать ли риск-ориентированный размер позиции.
RiskPercent Доля капитала, которой рискнет сделка.
FixedVolume Фиксированный объём при отключённом риск-менеджменте.
SessionStartOffsetMinutes Количество минут после открытия рынка, когда разрешены входы.
SessionEndOffsetMinutes Количество минут до закрытия рынка, когда входы запрещены.
CloseOnMiddleBand Закрывать позицию при пересечении средней полосы.
EnableTrailing Включить трейлинг-стоп.
TrailingFactor Требуемое движение (в кратных стопу) для активации трейлинга.
EnableBreakEven Переносить ли стоп в безубыток.
BreakEvenFactor Необходимый профит (в кратных стопу) для перехода в безубыток.
CloseLosingAfterMinutes Время в минутах до принудительного закрытия убыточной позиции.

Дополнительно

  • Стоп-лосс и тейк-профит моделируются по экстремумам свечей. При необходимости можно добавить регистрацию реальных защитных заявок.
  • Расчёт риск-объёма требует корректных Security.Step и Security.StepPrice. При их отсутствии стратегия использует фиксированный объём.
  • Ограничение дневной прибыли основано на PnL стратегии, поэтому важно соответствие валюты портфеля и параметров риска.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trades reversals from Bollinger Bands.
/// Buys when price closes below lower band and sells when price closes above upper band.
/// </summary>
public class BollingerBandsSessionReversalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerLength;
	private readonly StrategyParam<decimal> _bollingerWidth;

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the Bollinger Bands moving average.
	/// </summary>
	public int BollingerLength
	{
		get => _bollingerLength.Value;
		set => _bollingerLength.Value = value;
	}

	/// <summary>
	/// Width multiplier for Bollinger Bands.
	/// </summary>
	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public BollingerBandsSessionReversalStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "General");

		_bollingerLength = Param(nameof(BollingerLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators");
	}

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

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

		var bollinger = new BollingerBands
		{
			Length = BollingerLength,
			Width = BollingerWidth
		};

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

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

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

		if (bbValue is not IBollingerBandsValue bb)
			return;

		var middle = bb.MovingAverage ?? 0m;
		var upper = bb.UpBand ?? 0m;
		var lower = bb.LowBand ?? 0m;

		if (middle == 0m || upper == 0m || lower == 0m)
			return;

		var price = candle.ClosePrice;

		// Reversal: buy when price falls below lower band
		if (price < lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Reversal: sell when price rises above upper band
		else if (price > upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit at middle band
		else if (Position > 0 && price >= middle)
		{
			SellMarket();
		}
		else if (Position < 0 && price <= middle)
		{
			BuyMarket();
		}
	}
}