Открыть на GitHub

Стратегия Multi Lot Scalper

Обзор

Multi Lot Scalper — это конверсия классического советника MetaTrader, который строит сетку усредняющих сделок на основе наклона гистограммы MACD. Алгоритм создавался для основных валютных пар, где спред минимален и чётко определён размер пункта. После появления сигнала стратегия открывает серию рыночных ордеров в направлении тренда и увеличивает объём каждой последующей позиции, если рынок двигается против открытой корзины. Перенос на StockSharp сохранил исходную логику входа, правила мани-менеджмента и защитные механизмы, но реализован с использованием высокоуровневого API подписки на свечи.

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

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

  1. Определение направления. Индикатор MACD с параметрами MacdFastLength, MacdSlowLength, MacdSignalLength рассчитывается на каждой завершённой свече. Рост основной линии относительно предыдущего значения трактуется как сигнал к покупкам, падение — как сигнал к продажам. Параметр ReverseSignals меняет трактовку для контртрендовой торговли.
  2. Первичный вход. Первая сделка открывается сразу после появления сигнала, если фильтр по датам и времени (StartYear, StartMonth, EndYear, EndMonth, EndHour, EndMinute) разрешает торговлю. Ордера рыночные, что соответствует оригинальному советнику.
  3. Доливки. Новые позиции добавляются только после движения цены против последнего входа минимум на EntryDistancePips. Каждый дополнительный ордер увеличивает базовый объём в 2 раза, либо в 1.5 раза при больших корзинах (MaxTrades > 12), что повторяет математику исходного эксперта.
  4. Стопы и цели. Параметры InitialStopPips и TakeProfitPips пересчитываются в уровни для всей корзины. При движении цены в прибыль более чем на EntryDistancePips + TrailingStopPips активируется трейлинг, подтягивая общий стоп вслед за ценой.
  5. Защита счёта. Когда количество позиций приближается к лимиту (MaxTrades - OrdersToProtect), а плавающая прибыль достигает SecureProfit, стратегия закрывает последнюю сделку и временно прекращает набор, если включён флаг UseAccountProtection.

Управление капиталом

Оригинальный эксперт мог пересчитывать базовый лот в зависимости от баланса счёта. Параметры UseMoneyManagement, RiskPercent, IsStandardAccount сохраняют эту возможность. Если функция активна, вместо LotSize используется объём, рассчитанный от стоимости портфеля (Portfolio.CurrentValue), с поправкой на стандартный или мини-счёт.

Параметры

Параметр Описание Значение по умолчанию
TakeProfitPips Размер тейк-профита для каждой сделки, в пунктах. 40
LotSize Базовый лот при отключённом мани-менеджменте. 0.1
InitialStopPips Первичный стоп-лосс в пунктах. 0
TrailingStopPips Дистанция трейлинг-стопа. 20
MaxTrades Максимальное количество одновременных доливок. 10
EntryDistancePips Минимальное встречное движение для следующей сделки. 15
SecureProfit Прибыль в валюте депозита, запускающая защитное закрытие. 10
UseAccountProtection Включает защитный выход при достижении SecureProfit. true
OrdersToProtect Число последних сделок, подпадающих под защиту. 3
ReverseSignals Инвертирует направление сигналов MACD. false
UseMoneyManagement Пересчитывать лот по балансу счёта. false
RiskPercent Процент риска для расчёта лота при активном мани-менеджменте. 12
IsStandardAccount Использовать расчёт для стандартного счёта (1 лот = 100 000). false
EurUsdPipValue Стоимость пункта для EURUSD. 10
GbpUsdPipValue Стоимость пункта для GBPUSD. 10
UsdChfPipValue Стоимость пункта для USDCHF. 10
UsdJpyPipValue Стоимость пункта для USDJPY. 9.715
DefaultPipValue Стоимость пункта по умолчанию для прочих инструментов. 5
StartYear Первый год, в который разрешено открытие новых корзин. 2005
StartMonth Первый месяц, разрешающий новые сделки. 1
EndYear Последний год для новых входов. 2006
EndMonth Последний месяц для новых входов. 12
EndHour Час (по 24-часовому формату), после которого набор запрещён. 22
EndMinute Минуты дневного ограничения. 30
CandleType Тип свечей, используемый для расчётов (по умолчанию 15 минут). 15 минут
MacdFastLength Быстрая EMA в индикаторе MACD. 14
MacdSlowLength Медленная EMA в индикаторе MACD. 26
MacdSignalLength EMA сигнальной линии MACD. 9

Рекомендации по использованию

  • Убедитесь, что шаг цены инструмента совпадает с предполагаемым размером пункта. При торговле CFD, металлами или криптовалютами обновите параметры стоимости пункта.
  • Мартингейл быстро увеличивает экспозицию. Начните с консервативных значений MaxTrades, EntryDistancePips и TrailingStopPips, прежде чем расширять сетку.
  • Подбирайте параметры MACD и таймфрейм под конкретный инструмент: более медленные графики дают меньше доливок, более быстрые — повышают активность.
  • Если защитное закрытие срабатывает слишком часто, рассмотрите уменьшение SecureProfit или сокращение TrailingStopPips.
  • Фильтр по времени удобно использовать для исключения торгов в периоды важных новостей или на низколиквидной вечерней сессии.

Особенности конверсии

  • В StockSharp используется высокоуровневый вызов SubscribeCandles().BindEx(...), поэтому расчёт индикатора и обработка свечей происходят автоматически.
  • Трейлинг-стоп реализован на уровне суммарной позиции, что логично для портфельного учёта и повторяет идею оригинала о защите всей корзины.
  • Формула перерасчёта лота через AccountBalance заменена на использование Portfolio.CurrentValue, что обеспечивает эквивалентное поведение на платформе StockSharp.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Multi Lot Scalper: MACD slope scalping with EMA filter and ATR stops.
/// </summary>
public class MultiLotScalperStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _emaFilterLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevMacd;
	private decimal _entryPrice;

	public MultiLotScalperStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_emaFilterLength = Param(nameof(EmaFilterLength), 50)
			.SetDisplay("EMA Filter", "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 FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int EmaFilterLength
	{
		get => _emaFilterLength.Value;
		set => _emaFilterLength.Value = value;
	}

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

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

		_prevMacd = 0;
		_entryPrice = 0;
	}

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

		_prevMacd = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var emaFilter = new ExponentialMovingAverage { Length = EmaFilterLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, emaFilter, atr, ProcessCandle)
			.Start();

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

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

		var macd = fastVal - slowVal;

		if (_prevMacd == 0 || atrVal <= 0)
		{
			_prevMacd = macd;
			return;
		}

		var close = candle.ClosePrice;

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

		if (Position == 0)
		{
			if (macd > 0 && _prevMacd <= 0 && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (macd < 0 && _prevMacd >= 0 && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevMacd = macd;
	}
}