Avalanche AV — это рандомизированная мартингейловая стратегия. После заданного количества завершённых свечей она с равной вероятностью открывает длинную или короткую позицию. Каждая сделка получает фиксированные уровни стоп-лосса и тейк-профита в пунктах. Если сделка закрылась с убытком, объём следующей увеличивается в соответствии с коэффициентом мартингейла; прибыльные сделки возвращают объём к исходному значению, как только баланс достигает нового максимума. Дополнительно стратегия контролирует максимальную просадку по открытой позиции и принудительно закрывает её при превышении порога.
Исходник на MQL работал по тикам. Перенос на StockSharp сохраняет ту же вероятностную модель, но использует завершённые свечи, что облегчает тестирование и реалтрейд на баровых данных.
Правила торговли
Интервал решений. Стратегия ждёт указанное число завершённых свечей, прежде чем рассматривать новую сделку. Если позиция уже открыта, счётчик продолжает идти, но новый вход не выполняется.
Направление входа. Генерируется случайное число; значение выше 16384 инициирует покупку, иначе выполняется продажа. Сделка открывается только при отсутствии активной позиции.
Размер позиции. Стартовый объём задаётся параметром InitialVolume. После каждого убыточного закрытия объём умножается на MartingaleMultiplier и приводится к допустимому шагу объёма. Прибыльные сделки сбрасывают объём к начальному уровню, если текущий баланс превысил предыдущий максимум, иначе рост объёма продолжается.
Стоп и тейк. Уровни рассчитываются в пунктах от цены входа. Один пункт равен шагу цены инструмента.
Просадка по счёту. Пока позиция открыта, стратегия отслеживает нереализованный результат. Если убыток превышает MaxDrawdownPercent от баланса (начальный баланс + реализованная прибыль/убыток), позиция закрывается.
Параметры
Параметр
Значение по умолчанию
Описание
InitialVolume
0.1
Стартовый объём сделки.
StopLossPips
15
Расстояние стоп-лосса в пунктах (0 — без стопа).
TakeProfitPips
30
Расстояние тейк-профита в пунктах (0 — без тейка).
MaxDrawdownPercent
75
Максимальная допустимая плавающая просадка в процентах.
MartingaleMultiplier
1.6
Множитель объёма после убытка.
DecisionInterval
9
Количество завершённых свечей между решениями.
CandleType
Таймфрейм 1 минута
Тип свечей для расчётов.
Примечания
Объём автоматически нормализуется по VolumeStep, MinVolume и MaxVolume инструмента. Если привести объём к допустимому значению не удаётся, он сбрасывается на стартовый.
Стоп и тейк рассчитываются через PriceStep, поэтому для экзотических инструментов нужно проверить шаг цены.
Контроль просадки требует наличия PriceStep и StepPrice; при отсутствии этих данных защита отключается.
Из-за использования генератора случайных чисел результаты могут отличаться от запуска к запуску при одинаковых данных.
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 strategy using channel breakout with Highest/Lowest.
/// </summary>
public class AvalancheAvStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private decimal? _prevHigh;
private decimal? _prevLow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
public AvalancheAvStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 14)
.SetGreaterThanZero()
.SetDisplay("Period", "Channel period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = null;
_prevLow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = null;
_prevLow = null;
var highest = new Highest { Length = Period };
var lowest = new Lowest { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevHigh = high;
_prevLow = low;
return;
}
var close = candle.ClosePrice;
if (_prevHigh == null || _prevLow == null)
{
_prevHigh = high;
_prevLow = low;
return;
}
if (close > _prevHigh.Value && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (close < _prevLow.Value && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevHigh = high;
_prevLow = low;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class avalanche_av_strategy(Strategy):
def __init__(self):
super(avalanche_av_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 14) \
.SetDisplay("Period", "Channel period", "Indicators")
self._prev_high = None
self._prev_low = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
def OnReseted(self):
super(avalanche_av_strategy, self).OnReseted()
self._prev_high = None
self._prev_low = None
def OnStarted2(self, time):
super(avalanche_av_strategy, self).OnStarted2(time)
self._prev_high = None
self._prev_low = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _on_process(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
hv = float(high_value)
lv = float(low_value)
close = float(candle.ClosePrice)
if self._prev_high is None or self._prev_low is None:
self._prev_high = hv
self._prev_low = lv
return
if close > self._prev_high and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < self._prev_low and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = hv
self._prev_low = lv
def CreateClone(self):
return avalanche_av_strategy()