Стратегия воспроизводит эксперта Firebird v0.60. Скользящая средняя строится по открытиям свечей или среднему значению High/Low, после чего вверх и вниз откладываются процентные конверты. Пробой верхней границы запускает продажи, пробой нижней — покупки. Дополнительные ордера усредняют позицию только если цена ушла минимум на заданный шаг пунктов. Общий стоп-лосс делится между всеми ордерами, что защищает от «убегающих» трендов и запрещает повторный вход в ту же сторону после срабатывания стопа.
Детали
Вход:
Рассчитывается SMA по выбранному источнику (Open или (High+Low)/2).
Верхний конверт = SMA × (1 + Percent/100), нижний = SMA × (1 − Percent/100).
Шорт при закрытии выше верхней границы (если не заблокировано стопом), лонг при закрытии ниже нижней границы (если не заблокировано).
Усреднение разрешено, когда цена ушла на PipStep пунктов (с учётом степени усиления) дальше последнего входа.
Направление: Лонг и шорт.
Выход:
Общий тейк-профит на средней цене входа ± TakeProfit пунктов.
Общий стоп-лосс на средней цене ∓ StopLoss / числу ордеров пунктов.
После стопа включается блокировка повторного входа в ту же сторону до сигнала противоположного направления.
Стопы: Да, общий стоп и тейк.
Значения по умолчанию:
MaLength = 10
Percent = 0.3
TradeOnFriday = true
UseHighLow = false (используются открытия)
PipStep = 30
IncreasementPower = 0
TakeProfit = 30
StopLoss = 200
TradeVolume = 1
Фильтры:
Категория: Контртренд
Направление: Оба
Индикаторы: SMA-конверты
Стопы: Да
Сложность: Средняя
Таймфрейм: Любой
Сезонность: Опциональный фильтр пятницы
Нейросети: Нет
Дивергенция: Нет
Риск: Высокий из-за усреднения
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Firebird MA Envelope Exhaustion strategy - Bollinger Bands mean reversion.
/// Buys when close drops below lower band (exhaustion).
/// Sells when close rises above upper band (exhaustion).
/// Exits at the middle band.
/// </summary>
public class FirebirdMaEnvelopeExhaustionStrategy : 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 FirebirdMaEnvelopeExhaustionStrategy()
{
_bbPeriod = Param(nameof(BbPeriod), 10)
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bbWidth = Param(nameof(BbWidth), 2m)
.SetDisplay("BB Width", "Bollinger Bands width", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); }
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();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal || value.IsEmpty)
return;
var bbVal = value as BollingerBandsValue;
if (bbVal == null)
return;
var upper = bbVal.UpBand;
var lower = bbVal.LowBand;
var middle = bbVal.MovingAverage;
if (upper == null || lower == null || middle == null)
return;
var close = candle.ClosePrice;
// Close below lower band = exhaustion, buy
if (close < lower.Value && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Close above upper band = exhaustion, sell
else if (close > upper.Value && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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 BollingerBands
from StockSharp.Algo.Strategies import Strategy
class firebird_ma_envelope_exhaustion_strategy(Strategy):
def __init__(self):
super(firebird_ma_envelope_exhaustion_strategy, self).__init__()
self._bb_period = self.Param("BbPeriod", 10).SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_width = self.Param("BbWidth", 2.0).SetDisplay("BB Width", "Bollinger Bands width", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def bb_period(self): return self._bb_period.Value
@property
def bb_width(self): return self._bb_width.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(firebird_ma_envelope_exhaustion_strategy, self).OnReseted()
def OnStarted2(self, time):
super(firebird_ma_envelope_exhaustion_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.bb_period
bb.Width = self.bb_width
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, self.process_candle).Start()
def process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not bb_value.IsFinal or bb_value.IsEmpty:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper = float(bb_value.UpBand)
lower = float(bb_value.LowBand)
close = float(candle.ClosePrice)
if close < lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close > upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return firebird_ma_envelope_exhaustion_strategy()