Открыть на GitHub

Стратегия 800BB

Стратегия повторяет логику советника MetaTrader 4 «800BB», используя высокоуровневый API StockSharp. Система открывает сделки на возврат к среднему, когда цена пробивает экстремально длинные полосы Боллинджера и уже на следующей свече возвращается внутрь канала. Управление риском реализовано через стоп-лосс и тейк-профит, рассчитанные по ATR, а также через динамический расчет объёма позиции на основе выбранного процента риска.

Обзор

  • Работает с любым инструментом и таймфреймом, переданным через параметр CandleType.
  • Использует полосы Боллинджера с периодом 800 и отклонением 2 для поиска экстремальных выходов цены.
  • Подтверждает вход, когда новая свеча открывается внутри полос после предыдущего закрытия вне канала.
  • Размер позиции определяется по расстоянию стоп-лосса в «пунктах» ATR и по значению RiskPercent, применённому к текущей стоимости портфеля.
  • Копирует расчёт «пункта» из MetaTrader, умножая шаг цены на 10, если инструмент котируется с точностью до 3 или 5 знаков после запятой.

Торговая логика

Лонг

  1. Предыдущая завершённая свеча открывалась или закрывалась ниже нижней полосы Боллинджера, фиксируя выход в перепроданность.
  2. Текущая свеча открывается на уровне или выше предыдущей нижней полосы (цена вернулась в канал).
  3. Открытых длинных позиций нет. При наличии короткой позиции она закрывается перед открытием новой.
  4. Объём рассчитывается по ATR-стопу и заданному проценту риска.
  5. Регистрируется рыночная покупка на открытии свечи. Стоп-лосс размещается на расстоянии StopLossAtrMultiplier × ATR ниже входа, тейк-профит — на TakeProfitAtrMultiplier × ATR выше входа.

Шорт

  1. Предыдущая завершённая свеча открывалась или закрывалась выше верхней полосы Боллинджера, показывая выход в перекупленность.
  2. Текущая свеча открывается на уровне или ниже предыдущей верхней полосы (цена вернулась внутрь канала).
  3. Открытых коротких позиций нет. При наличии длинной позиции она закрывается перед открытием новой.
  4. Объём определяется тем же расчётом по ATR и проценту риска.
  5. Регистрируется рыночная продажа на открытии свечи. Стоп-лосс размещается на StopLossAtrMultiplier × ATR выше входа, тейк-профит — на TakeProfitAtrMultiplier × ATR ниже входа.

Управление выходами

  • Защитные ордера: Стоп-лосс и тейк-профит отслеживаются внутри стратегии и проверяются по каждой завершённой свече. При достижении любого уровня позиция закрывается рынком.
  • Обратные сигналы: При появлении противоположного сигнала текущая позиция закрывается перед открытием новой сделки.
  • Визуализация: Оригинальный советник рисовал вертикальные линии потенциальных входов. В данной реализации добавляются только свечи, полосы Боллинджера и собственные сделки на доступном графике.

Параметры

Параметр Значение по умолчанию Описание
RiskPercent 2 Процент стоимости портфеля, который рискуется в одной сделке.
TakeProfitAtrMultiplier 1.5 Множитель ATR для расчёта расстояния тейк-профита.
StopLossAtrMultiplier 1 Множитель ATR для расчёта расстояния стоп-лосса.
AtrPeriod 14 Период расчёта индикатора ATR.
BollingerPeriod 800 Период скользящей средней полос Боллинджера.
BollingerDeviation 2 Множитель стандартного отклонения полос Боллинджера.
CandleType 1 час Таймфрейм (или другой тип свечей), используемый для сигналов.

Примечания

  • Для корректного расчёта объёма необходимо, чтобы адаптер портфеля предоставлял Portfolio.CurrentValue. При нулевом значении риск-менеджмент блокирует открытие позиций.
  • Если у инструмента отсутствует корректный шаг цены или стоимость шага, расчёты «пункта» и стоимости пункта переходят на безопасные значения по умолчанию.
  • Из-за огромного периода полос Боллинджера (800 баров) стратегия начнёт торговать только после накопления достаточной истории для прогрева Bollinger и ATR.
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;

public class EightHundredBbStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EightHundredBbStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}