Avalanche — это сеточная стратегия возврата к среднему, основанная на оригинальном советнике Avalanche v1.2 для MetaTrader. В основе лежит анализ расположения цены относительно опорной средней (ERP), рассчитываемой как простая скользящая на более высоком таймфрейме. Если цена находится ниже ERP, стратегия ожидает возврат вверх и наращивает длинные позиции. Когда цена поднимается выше ERP, система набирает короткие позиции. Каждое добавление позиции происходит через заданный шаг и сопровождается индивидуальными уровнями стоп-лосса и тейк-профита.
В данной реализации StockSharp воспроизводится «toward»-ветка оригинального алгоритма. «Away»-ордера, направленные против ERP, не реализованы, так как стратегии в StockSharp оперируют одной чистой позицией. Тем не менее логика сеточного накопления, буферов и раздельного сопровождения сделок полностью сохранена.
Алгоритм работы
Подписка на два потока свечей: рабочий таймфрейм для торговли и отдельный таймфрейм для вычисления ERP.
Расчёт ERP с помощью индикатора SMA и определение, находится ли цена выше или ниже средней. Параметр ErpChangeBuffer предотвращает ложные переключения направления.
При смене положения относительно ERP существующая сетка закрывается и ожидание начинается заново.
Если разрешён параметр OpenStartingOrders, первая позиция открывается сразу после появления сигнала (покупка при цене ниже ERP, продажа — при цене выше).
При движении цены в прибыльную сторону ещё на IntervalToward пунктов выполняется дополнительная покупка/продажа.
При неблагоприятном движении на IntervalToward + StackBufferToward пунктов добавляется усредняющая сделка.
Каждая позиция сопровождается собственными уровнями TakeProfitToward и StopLossToward, что позволяет закрывать прибыльные части сетки независимо от остальных.
Настройки
Параметр
Назначение
BaseVolume
Базовый объём заявки до применения множителей.
TowardMultiplier
Множитель объёма для стандартных сделок по направлению к ERP.
TowardInterestMultiplier
Множитель объёма при положительном свопе в торговом направлении.
IntervalToward
Шаг (в пунктах) между последовательными сделками при движении в прибыльную сторону.
StackBufferToward
Дополнительный буфер (в пунктах) при усреднении против движения.
TakeProfitToward
Дистанция тейк-профита для каждой сделки; 0 отключает цель.
StopLossToward
Дистанция стоп-лосса для каждой сделки; 0 отключает защиту.
ErpPeriod
Период SMA, используемой как ERP.
ErpChangeBuffer
Буфер вокруг ERP перед сменой направления.
CandleType
Таймфрейм, по которому принимаются торговые решения.
ErpCandleType
Таймфрейм для расчёта ERP.
OpenStartingOrders
Автоматически открывать стартовую позицию после появления сигнала.
Отличия от оригинального эксперта
Не используются встречные «away»-ордера — стратегия работает только в сторону предполагаемого возврата к ERP.
Сделки выполняются рыночными заявками, а не отложенными стоп-ордерами.
Сохраняется логика определения направления положительного свопа для выбора множителя объёма.
Рекомендации по применению
Подбирайте значения IntervalToward и StackBufferToward в зависимости от волатильности инструмента, чтобы контролировать скорость набора сетки.
Следите за величиной совокупной позиции: сеточные стратегии способны быстро накапливать риск.
Используйте дополнительные фильтры — ограничения по времени торгов, лимиты по просадке, внешние стопы по капиталу.
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>
/// Avalanche grid strategy - mean reversion around EMA.
/// Buys when price drops below EMA, sells when above.
/// Uses RSI to confirm oversold/overbought conditions.
/// </summary>
public class AvalancheStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<DataType> _candleType;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal RsiOversold { get => _rsiOversold.Value; set => _rsiOversold.Value = value; }
public decimal RsiOverbought { get => _rsiOverbought.Value; set => _rsiOverbought.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AvalancheStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("EMA Period", "EMA period for equilibrium", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "RSI period", "Indicators");
_rsiOversold = Param(nameof(RsiOversold), 35m)
.SetDisplay("RSI Oversold", "RSI oversold level", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 65m)
.SetDisplay("RSI Overbought", "RSI overbought level", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// Below EMA and oversold - buy
if (close < emaValue && rsiValue <= RsiOversold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Above EMA and overbought - sell
else if (close > emaValue && rsiValue >= RsiOverbought && 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 ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class avalanche_strategy(Strategy):
def __init__(self):
super(avalanche_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA period for equilibrium", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 35.0) \
.SetDisplay("RSI Oversold", "RSI oversold level", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 65.0) \
.SetDisplay("RSI Overbought", "RSI overbought level", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def ema_period(self):
return self._ema_period.Value
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def rsi_oversold(self):
return self._rsi_oversold.Value
@property
def rsi_overbought(self):
return self._rsi_overbought.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(avalanche_strategy, self).OnReseted()
def OnStarted2(self, time):
super(avalanche_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, rsi, self.process_candle).Start()
def process_candle(self, candle, ema_value, rsi_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema_value)
rsi_val = float(rsi_value)
if close < ema_val and rsi_val <= self.rsi_oversold and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close > ema_val and rsi_val >= self.rsi_overbought and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return avalanche_strategy()