Стратегия повторяет логику советника MetaTrader 4 «800BB», используя высокоуровневый API StockSharp. Система открывает сделки на возврат к среднему, когда цена пробивает экстремально длинные полосы Боллинджера и уже на следующей свече возвращается внутрь канала. Управление риском реализовано через стоп-лосс и тейк-профит, рассчитанные по ATR, а также через динамический расчет объёма позиции на основе выбранного процента риска.
Обзор
Работает с любым инструментом и таймфреймом, переданным через параметр CandleType.
Использует полосы Боллинджера с периодом 800 и отклонением 2 для поиска экстремальных выходов цены.
Подтверждает вход, когда новая свеча открывается внутри полос после предыдущего закрытия вне канала.
Размер позиции определяется по расстоянию стоп-лосса в «пунктах» ATR и по значению RiskPercent, применённому к текущей стоимости портфеля.
Копирует расчёт «пункта» из MetaTrader, умножая шаг цены на 10, если инструмент котируется с точностью до 3 или 5 знаков после запятой.
Торговая логика
Лонг
Предыдущая завершённая свеча открывалась или закрывалась ниже нижней полосы Боллинджера, фиксируя выход в перепроданность.
Текущая свеча открывается на уровне или выше предыдущей нижней полосы (цена вернулась в канал).
Открытых длинных позиций нет. При наличии короткой позиции она закрывается перед открытием новой.
Объём рассчитывается по ATR-стопу и заданному проценту риска.
Регистрируется рыночная покупка на открытии свечи. Стоп-лосс размещается на расстоянии StopLossAtrMultiplier × ATR ниже входа, тейк-профит — на TakeProfitAtrMultiplier × ATR выше входа.
Шорт
Предыдущая завершённая свеча открывалась или закрывалась выше верхней полосы Боллинджера, показывая выход в перекупленность.
Текущая свеча открывается на уровне или ниже предыдущей верхней полосы (цена вернулась внутрь канала).
Открытых коротких позиций нет. При наличии длинной позиции она закрывается перед открытием новой.
Объём определяется тем же расчётом по ATR и проценту риска.
Регистрируется рыночная продажа на открытии свечи. Стоп-лосс размещается на StopLossAtrMultiplier × ATR выше входа, тейк-профит — на TakeProfitAtrMultiplier × ATR ниже входа.
Управление выходами
Защитные ордера: Стоп-лосс и тейк-профит отслеживаются внутри стратегии и проверяются по каждой завершённой свече. При достижении любого уровня позиция закрывается рынком.
Обратные сигналы: При появлении противоположного сигнала текущая позиция закрывается перед открытием новой сделки.
Визуализация: Оригинальный советник рисовал вертикальные линии потенциальных входов. В данной реализации добавляются только свечи, полосы Боллинджера и собственные сделки на доступном графике.
Параметры
Параметр
Значение по умолчанию
Описание
RiskPercent
2
Процент стоимости портфеля, который рискуется в одной сделке.
TakeProfitAtrMultiplier
1.5
Множитель ATR для расчёта расстояния тейк-профита.
Таймфрейм (или другой тип свечей), используемый для сигналов.
Примечания
Для корректного расчёта объёма необходимо, чтобы адаптер портфеля предоставлял 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class eight_hundred_bb_strategy(Strategy):
def __init__(self):
super(eight_hundred_bb_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(eight_hundred_bb_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(eight_hundred_bb_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return eight_hundred_bb_strategy()