Multi Lot Scalper — это конверсия классического советника MetaTrader, который строит сетку усредняющих сделок на основе наклона гистограммы MACD. Алгоритм создавался для основных валютных пар, где спред минимален и чётко определён размер пункта. После появления сигнала стратегия открывает серию рыночных ордеров в направлении тренда и увеличивает объём каждой последующей позиции, если рынок двигается против открытой корзины. Перенос на StockSharp сохранил исходную логику входа, правила мани-менеджмента и защитные механизмы, но реализован с использованием высокоуровневого API подписки на свечи.
По умолчанию стратегия использует 15-минутные свечи, однако через параметр CandleType можно подключить любой таймфрейм, подходящий для выбранного инструмента.
Логика торговли
Определение направления. Индикатор MACD с параметрами MacdFastLength, MacdSlowLength, MacdSignalLength рассчитывается на каждой завершённой свече. Рост основной линии относительно предыдущего значения трактуется как сигнал к покупкам, падение — как сигнал к продажам. Параметр ReverseSignals меняет трактовку для контртрендовой торговли.
Первичный вход. Первая сделка открывается сразу после появления сигнала, если фильтр по датам и времени (StartYear, StartMonth, EndYear, EndMonth, EndHour, EndMinute) разрешает торговлю. Ордера рыночные, что соответствует оригинальному советнику.
Доливки. Новые позиции добавляются только после движения цены против последнего входа минимум на EntryDistancePips. Каждый дополнительный ордер увеличивает базовый объём в 2 раза, либо в 1.5 раза при больших корзинах (MaxTrades > 12), что повторяет математику исходного эксперта.
Стопы и цели. Параметры InitialStopPips и TakeProfitPips пересчитываются в уровни для всей корзины. При движении цены в прибыль более чем на EntryDistancePips + TrailingStopPips активируется трейлинг, подтягивая общий стоп вслед за ценой.
Защита счёта. Когда количество позиций приближается к лимиту (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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class multi_lot_scalper_strategy(Strategy):
"""
Multi Lot Scalper: MACD slope scalping with EMA filter and ATR stops.
"""
def __init__(self):
super(multi_lot_scalper_strategy, self).__init__()
self._fast_ema = self.Param("FastEmaLength", 12).SetDisplay("Fast EMA", "Fast EMA", "Indicators")
self._slow_ema = self.Param("SlowEmaLength", 26).SetDisplay("Slow EMA", "Slow EMA", "Indicators")
self._ema_filter = self.Param("EmaFilterLength", 50).SetDisplay("EMA Filter", "Trend filter", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_macd = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(multi_lot_scalper_strategy, self).OnReseted()
self._prev_macd = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(multi_lot_scalper_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_ema.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_ema.Value
ema_filter = ExponentialMovingAverage()
ema_filter.Length = self._ema_filter.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, ema_filter, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_filter)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
ema = float(ema_val)
atr = float(atr_val)
macd = fast - slow
close = float(candle.ClosePrice)
if self._prev_macd == 0 or atr <= 0:
self._prev_macd = macd
return
if self.Position > 0:
if close >= self._entry_price + atr * 2.0 or close <= self._entry_price - atr * 1.5 or (macd < self._prev_macd and macd < 0):
self.SellMarket()
self._entry_price = 0
elif self.Position < 0:
if close <= self._entry_price - atr * 2.0 or close >= self._entry_price + atr * 1.5 or (macd > self._prev_macd and macd > 0):
self.BuyMarket()
self._entry_price = 0
if self.Position == 0:
if macd > 0 and self._prev_macd <= 0 and close > ema:
self._entry_price = close
self.BuyMarket()
elif macd < 0 and self._prev_macd >= 0 and close < ema:
self._entry_price = close
self.SellMarket()
self._prev_macd = macd
def CreateClone(self):
return multi_lot_scalper_strategy()