Открыть на GitHub

Прорыв по объему

Стратегия Volume Breakout наблюдает за резким ростом объема. Когда показания выходят за пределы среднего диапазона, цена часто начинает новое движение.

Тестирование показывает среднегодичную доходность около 103%. Стратегию лучше запускать на фондовом рынке.

Позиция открывается, как только индикатор пробивает диапазон, рассчитанный по последним данным и множителю отклонения. Возможны сделки в обе стороны со стопом.

Система подходит трейдерам импульсных стратегий, ищущим ранние прорывы. Сделки закрываются, когда объем возвращается к среднему. По умолчанию используется AvgPeriod = 20.

Подробности

  • Условия входа: индикатор превышает среднее на величину множителя отклонения.
  • Длинные/короткие: оба направления.
  • Условия выхода: индикатор возвращается к среднему.
  • Стопы: да.
  • Значения по умолчанию:
    • AvgPeriod = 20
    • Multiplier = 2.0m
    • CandleType = TimeSpan.FromMinutes(5)
    • StopLoss = 2.0m
  • Фильтры:
    • Категория: Breakout
    • Направление: оба
    • Индикаторы: Volume
    • Стопы: да
    • Сложность: средняя
    • Таймфрейм: краткосрочный
    • Сезонность: нет
    • Нейросети: нет
    • Дивергенция: нет
    • Уровень риска: средний
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that trades on volume breakouts.
/// When volume rises significantly above its average, it enters position in the direction determined by price.
/// </summary>
public class VolumeBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _avgPeriod;
	private readonly StrategyParam<decimal> _multiplier;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLoss;
	
	private SimpleMovingAverage _volumeAverage;
	private SimpleMovingAverage _volumeStdDev;
	private decimal _lastAvgVolume;
	private decimal _lastStdDev;
	
	/// <summary>
	/// Period for volume average calculation.
	/// </summary>
	public int AvgPeriod
	{
		get => _avgPeriod.Value;
		set => _avgPeriod.Value = value;
	}
	
	/// <summary>
	/// Standard deviation multiplier for breakout detection.
	/// </summary>
	public decimal Multiplier
	{
		get => _multiplier.Value;
		set => _multiplier.Value = value;
	}
	
	/// <summary>
	/// Candle type for strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Stop-loss percentage.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}
	
	/// <summary>
	/// Initialize <see cref="VolumeBreakoutStrategy"/>.
	/// </summary>
	public VolumeBreakoutStrategy()
	{
		_avgPeriod = Param(nameof(AvgPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Average Period", "Period for volume average calculation", "Indicators")
			
			.SetOptimize(10, 50, 5);
		
		_multiplier = Param(nameof(Multiplier), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("Multiplier", "Standard deviation multiplier for breakout detection", "Indicators")
			
			.SetOptimize(1.0m, 3.0m, 0.5m);
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
		
		_stopLoss = Param(nameof(StopLoss), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop Loss percentage", "Risk Management")
			
			.SetOptimize(1.0m, 5.0m, 0.5m);
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastAvgVolume = 0;
		_lastStdDev = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		
		// Create indicators for volume analysis
		_volumeAverage = new SMA { Length = AvgPeriod };
		_volumeStdDev = new SMA { Length = AvgPeriod };
		
		// Create subscription
		var subscription = SubscribeCandles(CandleType);
		
		// Bind candles to processing method
		subscription
			.Bind(ProcessCandle)
			.Start();
			
		// Enable stop loss protection
		StartProtection(
			takeProfit: new Unit(3, UnitTypes.Percent),
			stopLoss: new Unit(StopLoss, UnitTypes.Percent));
		
		// Create chart area for visualization
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;
			
		// Calculate volume indicators
		var volume = candle.TotalVolume;
		
		// Calculate volume average
		var avgValue = _volumeAverage.Process(new DecimalIndicatorValue(_volumeAverage, volume, candle.ServerTime) { IsFinal = true });
		var avgVolume = avgValue.ToDecimal();

		// Calculate standard deviation approximation
		var deviation = Math.Abs(volume - avgVolume);
		var stdDevValue = _volumeStdDev.Process(new DecimalIndicatorValue(_volumeStdDev, deviation, candle.ServerTime) { IsFinal = true });
		var stdDev = stdDevValue.ToDecimal();

		// Skip the first N candles until we have enough data
		if (!_volumeAverage.IsFormed || !_volumeStdDev.IsFormed)
		{
			_lastAvgVolume = avgVolume;
			_lastStdDev = stdDev;
			return;
		}

		// Volume breakout detection (volume increases significantly above its average)
		if (volume > avgVolume + Multiplier * stdDev && Position == 0)
		{
			// Determine direction based on price movement
			var bullish = candle.ClosePrice > candle.OpenPrice;

			// Trade in the direction of price movement
			if (bullish)
			{
				BuyMarket();
			}
			else
			{
				SellMarket();
			}
		}

		// Update last values
		_lastAvgVolume = avgVolume;
		_lastStdDev = stdDev;
	}
}