Портирование советника RUBBERBANDS 1.6 с платформы MetaTrader на высокоуровневый API StockSharp. В исходной версии одновременно поддерживаются встречные покупки и продажи, а закрытая прибыльная сторона сразу замещается новой заявкой. При превышении заданного денежного порога активируется «страховочная сетка». В StockSharp используется модель неттинга, поэтому вместо одновременных разнонаправленных ордеров реализовано усреднение по текущему направлению при сохранении тех же денежных условий.
Логика торговли
Старт цикла. В начале каждой минуты или при включении переключателя Enter Now открывается рыночная позиция объёмом BaseVolume. Направление чередуется: покупка, затем продажа и т.д.
Базовая цель по прибыли. Текущий нереализованный результат сравнивается с TargetProfitPerLot * BaseVolume. После достижения порога позиция закрывается, а следующая сделка открывается в обратную сторону.
Сессионный контроль. Опции UseSessionTakeProfit и UseSessionStopLoss отслеживают суммарную (реализованную + нереализованную) прибыль в денежном выражении на базовый лот. При достижении порога все позиции закрываются, счётчики сбрасываются.
Режим безопасности. Если опция включена и убыток превышает SafetyStartPerLot * BaseVolume, стратегия переходит в режим усреднения и добавляет заявки объёмом SafetyVolume в сторону текущей позиции. Каждый дополнительный убыток на SafetyStepPerLot за лот планирует ещё одно усреднение.
Выход из режима безопасности. Позиция полностью закрывается при достижении прибыли SafetyProfitPerLot * |Position| либо при превышении порога SafetyModeTakeProfitPerLot * BaseVolume на уровне всей сессии.
Условия входа
Для покупки
Нет открытой позиции и либо завершилась очередная минута, либо включён флаг Enter Now.
Очередной цикл должен начинаться с длинной позиции.
Флаг Stop Trading выключен.
Для продажи
Аналогично условиям покупки, но следующий цикл должен стартовать с короткой позиции.
Управление выходом
Достижение цели по базе. Закрыть текущую позицию и сменить направление следующего цикла.
Сессионные цели. Закрыть позицию, обнулить накопленную прибыль и ждать следующего запуска.
Прибыль в режиме безопасности. Закрыть позицию при достижении целевого значения PnL в режиме усреднения.
Усреднение. Дополнительные заявки добавляются при каждом шаге убытка SafetyStepPerLot.
Ручное закрытие. Переключатель Close Now закрывает позицию на следующей свече и обнуляет накопленную прибыль.
Параметры
Параметр
Описание
BaseVolume
Объём стартовой рыночной заявки.
TargetProfitPerLot
Денежная цель по прибыли на базовый лот.
UseSessionTakeProfit / SessionTakeProfitPerLot
Включение и настройка сессионного тейк-профита.
UseSessionStopLoss / SessionStopLossPerLot
Включение и настройка сессионного стоп-лосса.
UseSafetyMode
Включение режима усреднения.
SafetyStartPerLot
Убыток на базовый лот для активации режима безопасности.
SafetyVolume
Объём каждой усредняющей заявки.
SafetyStepPerLot
Дополнительный убыток на лот для постановки следующего усреднения.
SafetyProfitPerLot
Цель по прибыли в режиме безопасности.
SafetyModeTakeProfitPerLot
Сессионная цель по прибыли при активном режиме безопасности.
Таймфрейм свечей, управляющих логикой (по умолчанию 1 минута).
Практические замечания
Из-за неттинговой модели StockSharp стратегия усредняет текущую позицию вместо открытия хеджирующих ордеров. Денежные пороги соответствуют настройкам оригинального советника.
Все пороговые значения указаны в валюте счёта на один лот. Подберите их с учётом стоимости тика инструмента.
Переключатели Stop Trading, Close Now, Enter Now, Quiesce можно менять из интерфейса без изменения кода.
В методе OnStarted вызывается StartProtection() для использования стандартных защит StockSharp.
Алгоритм автоматически подгоняет объём под шаг и минимальный/максимальный объём инструмента; убедитесь, что соответствующие параметры заданы в инструменте.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rubberbands Safety Net: Mean reversion with Bollinger-style bands.
/// Buys at lower band, sells at upper band with ATR stops.
/// </summary>
public class RubberbandsSafetyNetStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _bandMult;
private decimal _entryPrice;
public RubberbandsSafetyNetStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 20)
.SetDisplay("SMA Length", "SMA center band period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for bands and stops.", "Indicators");
_bandMult = Param(nameof(BandMult), 2.0m)
.SetDisplay("Band Mult", "ATR multiplier for bands.", "Signals");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal BandMult
{
get => _bandMult.Value;
set => _bandMult.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
var upper = smaVal + atrVal * BandMult;
var lower = smaVal - atrVal * BandMult;
if (Position > 0)
{
if (close >= smaVal || close <= _entryPrice - atrVal * 3m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= smaVal || close >= _entryPrice + atrVal * 3m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close <= lower)
{
_entryPrice = close;
BuyMarket();
}
else if (close >= upper)
{
_entryPrice = close;
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
class rubberbands_safety_net_strategy(Strategy):
def __init__(self):
super(rubberbands_safety_net_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._sma_length = self.Param("SmaLength", 20) \
.SetDisplay("SMA Length", "SMA center band period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for bands and stops.", "Indicators")
self._band_mult = self.Param("BandMult", 2.0) \
.SetDisplay("Band Mult", "ATR multiplier for bands.", "Signals")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmaLength(self):
return self._sma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def BandMult(self):
return self._band_mult.Value
def OnStarted2(self, time):
super(rubberbands_safety_net_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._sma = SimpleMovingAverage()
self._sma.Length = self.SmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sv = float(sma_val)
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
bm = float(self.BandMult)
upper = sv + av * bm
lower = sv - av * bm
if self.Position > 0:
if close >= sv or close <= self._entry_price - av * 3.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= sv or close >= self._entry_price + av * 3.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close <= lower:
self._entry_price = close
self.BuyMarket()
elif close >= upper:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(rubberbands_safety_net_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return rubberbands_safety_net_strategy()