Открыть на GitHub

Стратегия Avalanche AV

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