Открыть на GitHub

Стратегия Rubberbands 3

Стратегия представляет собой порт эксперта MetaTrader 4 RUBBERBANDS_3 на платформу StockSharp. Алгоритм отслеживает два экстремума цены, наращивает позицию при расширении диапазона на заданное число пунктов и полностью закрывает серию ордеров при обратном движении. После фиксации профита стратегия может перевернуться и начать набор позиций в противоположном направлении. Одновременно ведётся контроль суммарной прибыли и убытка в пределах торговой сессии.

Важно: в StockSharp позиции неттингуются. В оригинальном советнике могли одновременно существовать покупки и продажи. В портированной версии серия ордеров в одном направлении закрывается перед открытием противоположных сделок. Логика «растягивания резинки» и закрытия на откате при этом сохраняется.

Логика работы

  1. При старте сохраняются текущие максимальное и минимальное значения цены закрытия (либо используются значения InitialMax и InitialMin).
  2. Если цена поднимается выше максимума на величину PipStep, выставляется рыночная покупка объёмом OrderVolume, а максимум обновляется.
  3. Если цена опускается ниже минимума на PipStep, выставляется рыночная продажа объёмом OrderVolume, минимум обновляется.
  4. При обратном движении на BackStep пунктов вся серия сделок текущего направления закрывается. Как только позиции обнулены, стратегия готовится открыть сделки противоположного направления.
  5. Реализован контроль сессионных результатов: при достижении SessionTakeProfit × OrderVolume все позиции закрываются. Если во время переворота плавающий убыток превышает SessionStopLoss × OrderVolume, серия также принудительно закрывается.
  6. Флаг QuiesceNow переводит стратегию в «тихий» режим после закрытия всех позиций. StopNow полностью приостанавливает логику, CloseNow инициирует немедленное закрытие всех ордеров.

Обработка сигналов выполняется по завершённым свечам типа CandleType. По умолчанию используется минутный таймфрейм, что соответствует первоначальному советнику, срабатывавшему в начале каждой минуты.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Базовый объём каждой рыночной заявки. 0.02
MaxOrders Максимальное число одновременно открытых ордеров в одном направлении. 10
PipStep Расстояние в пунктах, при котором добавляется новый ордер. 100
BackStep Откат в пунктах, при котором серия закрывается и готовится разворот. 20
QuiesceNow При true стратегия не открывает новые сделки, находясь без позиций. false
DoNow Принудительно открывает первую сделку (покупку) сразу после запуска. false
StopNow Полностью приостанавливает торговую логику без закрытия текущих сделок. false
CloseNow Запрашивает немедленное закрытие всех позиций по рыночным ценам. false
UseSessionTakeProfit Включает контроль суммарного профита сессии. true
SessionTakeProfit Сессионный тейк-профит в валюте счёта на один лот. 2000
UseSessionStopLoss Включает сессионный стоп-лосс. true
SessionStopLoss Максимально допустимый убыток на один лот при перевороте. 4000
UseInitialValues Использовать сохранённые экстремумы при перезапуске стратегии. false
InitialMax Сохранённый максимум, применяется при UseInitialValues = true. 0
InitialMin Сохранённый минимум, применяется при UseInitialValues = true. 0
CandleType Тип свечей, по которым ведётся расчёт. TimeFrame(1m)

Управление сессией

  • Агрегация прибыли. После каждого полного закрытия серии фиксированный результат добавляется к накопленной прибыли _realizedProfit. Плавающий результат рассчитывается по средневзвешенным ценам открытых позиций.
  • Сессионный тейк-профит. При достижении целевого значения вся серия закрывается, экстремумы сбрасываются.
  • Сессионный стоп-лосс. Во время переворота отслеживается текущий убыток. Если он превышает порог SessionStopLoss, позиции закрываются и торговая сессия начинается заново.

Рекомендации по использованию

  • Для корректного пересчёта пунктов необходимо заполнить Security.PriceStep. При отсутствии данных используется резервное значение 0.0001.
  • Из-за неттинга обратное направление открывается только после полного закрытия предыдущей серии. Учтите это при сравнении отчётов с оригинальной версией.
  • Флаг DoNow влияет только на первое открытие. Дальнейшие сделки выполняются по правилам расширения диапазона.
  • QuiesceNow полезен, если нужно оставить стратегию запущенной, но не позволять ей начинать новые циклы после закрытия позиций.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Rubberbands 3: Grid expansion strategy using SMA+ATR bands.
/// Enters at band extremes, adds on continuation, exits at mean.
/// </summary>
public class Rubberbands3Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private int _gridCount;

	public Rubberbands3Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_smaLength = Param(nameof(SmaLength), 20)
			.SetDisplay("SMA Length", "SMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	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;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_gridCount = 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 * 1.5m;
		var lower = smaVal - atrVal * 1.5m;

		if (Position > 0)
		{
			if (close >= smaVal)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (close <= _entryPrice - atrVal * 5m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (_gridCount < 4 && close <= _entryPrice - atrVal * 0.8m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				BuyMarket();
			}
		}
		else if (Position < 0)
		{
			if (close <= smaVal)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (close >= _entryPrice + atrVal * 5m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (_gridCount < 4 && close >= _entryPrice + atrVal * 0.8m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				SellMarket();
			}
		}

		if (Position == 0)
		{
			if (close <= lower)
			{
				_entryPrice = close;
				_gridCount = 0;
				BuyMarket();
			}
			else if (close >= upper)
			{
				_entryPrice = close;
				_gridCount = 0;
				SellMarket();
			}
		}
	}
}