Открыть на GitHub

Стратегия ScalpWiz 9001

Общее описание

ScalpWiz 9001 — это многоуровневая скальперская стратегия, повторяющая логику одноимённого эксперта MetaTrader. Алгоритм измеряет, насколько далеко последняя свеча закрылась за пределами полос Боллинджера, и при резком расширении волатильности разворачивает сетку отложенных стоп-заявок выше или ниже рынка. Сохраняется оригинальная система управления капиталом: для каждого уровня можно задать фиксированный объём либо долю риска от капитала.

После срабатывания одной из заявок оставшиеся отменяются, а открытая позиция сопровождается стоп-лоссом, тейк-профитом и трейлинг-блоком, который начинает работу только после прохождения дополнительного буфера. Стратегия рассчитана на агрессивные сделки на малых таймфреймах, но может применяться на любом инструменте, поддерживаемом StockSharp.

Логика сигналов

  1. Подписка на выбранный таймфрейм и расчёт полос Боллинджера с периодом BandsPeriod (по умолчанию 20) и множителем BandsDeviation (по умолчанию 2).
  2. Проверка, насколько закрытие ушло от верхней или нижней полосы. Если превышение достигает расстояния уровня Level3Pips (переведённого в цену), стратегия готовится к контртрендовому входу:
    • Закрытие выше верхней полосы → размещаются sell stop ордера ниже текущей цены.
    • Закрытие ниже нижней полосы → размещаются buy stop ордера выше текущей цены.
  3. Всего выставляются четыре отложенных ордера с нарастающими расстояниями (Level0PipsLevel3Pips). Для каждого уровня используется собственный объём или процент риска. Если ExpirationMinutes > 0, то ордера автоматически отменяются по истечении заданного времени.
  4. После исполнения одного из ордеров остальные заявки снимаются. Позиция управляется стоп-лоссом (StopLossPips), тейк-профитом (TakeProfitPips) и трейлингом (TrailingStopPips, TrailingStepPips). Трейлинг переносит защитный стоп только после прохождения расстояния TrailingStopPips + TrailingStepPips от цены входа.
  5. Выход из позиции выполняется рыночной заявкой при достижении стопа или цели на закрывшейся свече.

Параметры

  • Candle Type — таймфрейм расчёта полос Боллинджера.
  • Bands Period / Bands Deviation — настройки индикатора.
  • Stop Loss (pips) — расстояние защитного стопа в пунктах.
  • Take Profit (pips) — расстояние тейк-профита в пунктах.
  • Trailing Stop (pips) — величина трейлинг-стопа.
  • Trailing Step (pips) — дополнительный шаг перед переносом стопа.
  • Expiration (minutes) — время жизни отложенных ордеров (0 — без ограничения).
  • Management Mode — режим интерпретации значений уровней (FixedVolume или RiskPercent).
  • Level 0-3 Value — объём сделки или процент риска для каждого уровня.
  • Level 0-3 Pips — расстояния установки ордеров в пунктах.

Управление рисками

При выборе режима RiskPercent объём ордера рассчитывается из стоимости портфеля и расстояния стоп-лосса:

объём = (equity × riskPercent / 100) / (stopOffset / priceStep × stepPrice)

Если информация о шаге цены или стоимости шага недоступна, объём принудительно обнуляется для исключения некорректных сделок. В режиме FixedVolume используются заданные значения, округляемые к допустимому шагу объёма инструмента.

Сопровождение позиции

  • Стоп-лосс и тейк-профит задаются относительно фактической цены исполнения.
  • Трейлинг полностью повторяет реализацию в MetaTrader: перенос стопа начинается только после прохождения дополнительного шага и затем удерживает расстояние TrailingStopPips.
  • Закрытие позиции производится рыночными заявками, что обеспечивает совместимость с площадками без серверных защитных ордеров.

Практические рекомендации

  • Подбирайте значения уровней с учётом шага цены инструмента. Для пятизначных валютных пар один пункт соответствует десяти минимальным шагам — стратегия корректирует это автоматически по количеству знаков инструмента.
  • Проверьте биржевые ограничения на минимальные расстояния до цены (freeze/stop level) и при необходимости увеличьте значения LevelXPips.
  • Для режима риска необходимы актуальные данные портфеля и параметры шага цены. При их отсутствии объём будет равен нулю.
  • Стратегия обрабатывает только закрывшиеся свечи, что сглаживает шум по сравнению с тиковым вариантом оригинального эксперта.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bollinger Bands breakout scalping strategy.
/// Buys when price breaks below lower band (mean reversion) and sells when price breaks above upper band.
/// Uses stop-loss and take-profit for risk management.
/// </summary>
public class ScalpWiz9001Strategy : Strategy
{
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _bandsDeviation;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;

	private BollingerBands _bollinger;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Bollinger Bands lookback period.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands deviation multiplier.
	/// </summary>
	public decimal BandsDeviation
	{
		get => _bandsDeviation.Value;
		set => _bandsDeviation.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public ScalpWiz9001Strategy()
	{
		_bandsPeriod = Param(nameof(BandsPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bands Period", "Bollinger Bands period", "Indicator");

		_bandsDeviation = Param(nameof(BandsDeviation), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Bands Deviation", "Bollinger Bands deviation", "Indicator");

		_stopLossPips = Param(nameof(StopLossPips), 100)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 150)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_bollinger = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bollinger = new BollingerBands
		{
			Length = BandsPeriod,
			Width = BandsDeviation
		};

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.BindEx(_bollinger, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower)
			return;

		if (!_bollinger.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP for existing positions
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPips > 0 && close <= _entryPrice - StopLossPips * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 5;
				return;
			}

			if (TakeProfitPips > 0 && close >= _entryPrice + TakeProfitPips * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 5;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPips > 0 && close >= _entryPrice + StopLossPips * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 5;
				return;
			}

			if (TakeProfitPips > 0 && close <= _entryPrice - TakeProfitPips * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 5;
				return;
			}
		}

		// Entry signals
		if (Position == 0)
		{
			// Buy when price touches lower band (mean reversion up)
			if (close <= lower)
			{
				BuyMarket();
				_entryPrice = close;
				_cooldown = 5;
			}
			// Sell when price touches upper band (mean reversion down)
			else if (close >= upper)
			{
				SellMarket();
				_entryPrice = close;
				_cooldown = 5;
			}
		}
	}
}