Открыть на GitHub

Стратегия Scalpel

Обзор

Scalpel Strategy — порт советника MetaTrader 4 Scalpel.mq4 на платформу StockSharp. Алгоритм ищет импульсные пробои на базовом таймфрейме, подтверждает направление по минимумам/максимумам свечей H4, H1 и M30, а также фильтрует входы по направленному объёму на минутных свечах. Управление позицией повторяет логику оригинального EA: фиксированное тейк-профитное расстояние постепенно сокращается, стоп-приказ может сопровождать цену, а сделки закрываются по истечении заданного времени или в пятницу вечером.

Логика торговли

  • Мультифреймовый фильтр тренда: для лонга текущие минимумы H4/H1/M30 должны быть выше предыдущих, для шорта — максимумы должны снижаться.
  • Подтверждение пробоя: лучшая цена ask должна превысить максимум предыдущей свечи (для покупки), либо лучшая цена bid должна упасть ниже минимума (для продажи). Три последние экстремума базового таймфрейма образуют ступеньки в сторону пробоя.
  • Окно CCI: значение Commodity Channel Index предыдущей закрытой свечи должно лежать внутри настраиваемого диапазона около нуля. Положительное значение параметра создаёт симметричный коридор, отрицательное копирует асимметрию оригинального советника.
  • Фильтр направленного объёма: объёмы на волатильностном таймфрейме делятся на два скользящих блока. Сигнал разрешён только тогда, когда свежий блок содержит больше направленного объёма, чем старый, и старый блок не равен нулю. Отрицательное значение VolatilityWindow переключает расчёт на безнаправленное накопление по диапазону свечей.
  • Риск-менеджмент:
    • Фиксированные тейк-профит и стоп-лосс в минимальных шагах цены.
    • Каждые TakeProfitReduceMinutes минут тейк-профит сдвигается на один шаг цены в сторону позиции.
    • После прохождения TrailingStopPoints пунктов активируется плавающий стоп.
    • Позиция может быть принудительно закрыта через LiveMinutes минут или в заданный час в пятницу.
    • Новые сделки блокируются, если абсолютная позиция равна MaxDirectionalPositions * TradeVolume, либо пока не закончится таймер повторного входа.

Параметры

Название Значение по умолчанию Описание
TradeVolume -5 Объём заявки. Положительное значение — фиксированный лот, отрицательное — процент капитала, переводимый в объём по текущей цене ask.
TakeProfitPoints 40 Расстояние до тейк-профита в минимальных шагах цены.
StopLossPoints 340 Расстояние до стоп-лосса в минимальных шагах цены.
TrailingStopPoints 25 Дистанция плавающего стопа. Стоп начинает двигаться после достижения указанной прибыли.
CciPeriod 14 Период CCI на базовом таймфрейме.
CciLimit 75 Верхняя граница для лонгов и зеркальная нижняя граница для шортов. Отрицательное значение создаёт асимметрию как в исходном советнике.
MaxDirectionalPositions 1 Максимальное число нетто-позиций в одну сторону (в лотах, кратных расчётному объёму).
ReentryIntervalMinutes 0 Минимальный интервал между последовательными входами.
TakeProfitReduceMinutes 600 Интервал в минутах, через который тейк-профит сдвигается на один шаг цены. Ноль отключает механизм.
LiveMinutes 0 Максимальное время удержания позиции в минутах. Ноль — без ограничения.
VolatilityWindow 100 Размер окна волатильностного фильтра. Отрицательное значение включает безнаправленное накопление, ноль использует только последнюю свечу.
VolatilityThresholdPoints 1 Минимальный размер тела (для направленного режима) или диапазона (для безнаправленного) свечи, при котором объём попадает в статистику. Знак инвертирует распределение объёмов по направлениям.
FridayCloseHour 22 Час (0-23), когда по пятницам закрываются все сделки. Ноль отключает закрытие.
SpreadLimitPoints 5.5 Максимально допустимый спред при открытии позиции (в шагах цены).
CandleType 1 минута Базовый таймфрейм для сигналов и управления позицией.
Hour1CandleType 1 час Таймфрейм для фильтра H1.
Hour4CandleType 4 часа Таймфрейм для фильтра H4.
Minute30CandleType 30 минут Таймфрейм для фильтра M30.
VolatilityCandleType 1 минута Таймфрейм, на котором считается фильтр направленного объёма.

Особенности реализации

  • Стратегия подписывается на стакан, чтобы использовать актуальные значения bid/ask для проверки пробоя и контроля спреда.
  • Индикаторы подключены через высокоуровневый API StockSharp: CCI берётся из BindEx, а старшие таймфреймы обновляются отдельными подписками.
  • Логика плавающего стопа и постепенного уменьшения тейк-профита реализована вручную, что повторяет поведение оригинального советника.
  • При отрицательном TradeVolume размер сделки рассчитывается по текущей цене и ограничивается минимальным/максимальным лотом инструмента.

Использование

  1. Подключите стратегию к портфелю и выберите инструмент в Designer или Backtester.
  2. Настройте таймфреймы, параметры фильтров и правила риск-менеджмента.
  3. Запустите стратегию. Сигналы формируются только по завершённым свечам, заявки отправляются по рынку, выход управляется встроенными правилами.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Scalpel: CCI-based scalping with EMA filter and ATR stops.
/// </summary>
public class ScalpelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

	public ScalpelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ema, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (rsiVal > 50 && _prevRsi <= 50 && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (rsiVal < 50 && _prevRsi >= 50 && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}