Стратегия переносит советник 15M Scalper из MetaTrader на высокоуровневый API StockSharp. Реализована та же комбинация
фильтров (взвешенные средние, стохастик, Parabolic SAR, многофреймовый Momentum и месячный MACD) и полный набор защит
(тейк-профит в пунктах и деньгах, трейлинг, перевод в безубыток, стоп по просадке капитала). Алгоритм работает по закрытию
свечей, как и оригинал, а параметры полностью повторяют входные настройки MQL.
Логика работы
Трендовый фильтр – на рабочем таймфрейме (по умолчанию 15 минут) рассчитываются две LWMA по типичной цене
((High + Low + Close) / 3). Для длинного сигнала быстрая средняя должна быть выше медленной, для короткого – наоборот.
Реверс стохастика – стохастик 5/3/3 анализируется по двум последним закрытым барам. Длинная сделка открывается при
возврате %K выше 20, короткая – при пробое 80 вниз (аналог условий Stoc1/Stoc2).
Подтверждение SAR – значение Parabolic SAR на завершённой свече должно располагаться ниже открытия предыдущего бара
(для покупок) или выше (для продаж), что повторяет проверку SAR < Open[1] / SAR > Open[1].
Momentum старшего таймфрейма – индикатор Momentum с периодом 14 на настраиваемом старшем таймфрейме (по умолчанию 1 час)
должен отклониться от уровня 100 на величину, не меньшую порога для покупок/продаж, хотя бы на одном из трёх последних баров.
Месячный MACD – MACD, построенный по месячным свечам (в версии StockSharp – по 30-дневным барам), требует, чтобы основная
линия находилась выше сигнальной для лонга и ниже для шорта. Этот же MACD используется и как выходной фильтр при включённом
параметре UseExitByMacd.
Обработка сделок – при появлении противоположного сигнала стратегия сначала закрывает текущую позицию и только на
следующем баре рассматривает вход в новую сторону. Размер лота масштабируется по LotExponent, а IncreaseFactor
увеличивает базовый объём при серии убытков.
Управление рисками
Стоп-лосс / тейк-профит – задаются в "пунктах" MetaTrader и пересчитываются в абсолютное расстояние через
Security.PriceStep. Для инструментов с дробным шагом (5/3 знака) шаг умножается на 10, что имитирует работу MQL.
Перевод в безубыток – после прохождения расстояния BreakEvenTriggerSteps стоп переносится к цене входа плюс
BreakEvenOffsetSteps. Если цена вернулась обратно – позиция закрывается рыночной заявкой.
Трейлинг-стоп – отслеживает максимум/минимум после входа. Как только откат превышает TrailingStopSteps, позиция
закрывается (полный аналог OrderModify из оригинала).
Денежные цели – параметры UseProfitTargetMoney, UseProfitTargetPercent и EnableMoneyTrailing оперируют плавающей
прибылью, рассчитанной как количество шагов цены × StepPrice. Поддерживаются фиксированный тейк-профит, процентная цель и
трейлинг по деньгам (MoneyTrailingStop).
Стоп по капиталу – UseEquityStop отслеживает максимум суммы (начальный капитал + реализованная прибыль + плавающий результат).
При просадке больше TotalEquityRisk процентов вся позиция закрывается – так же, как в функции AccountEquityHigh().
Мартингейл – каждый дополнительный вход умножает объём на LotExponent. Если подряд получено более одной убыточной сделки,
IncreaseFactor добавляет к базовому объёму надбавку, пропорциональную числу убытков.
Параметры
Параметр
Описание
CandleType
Рабочий таймфрейм (по умолчанию 15-минутные свечи).
MomentumCandleType
Таймфрейм для фильтра Momentum (по умолчанию часовые свечи).
MacdCandleType
Таймфрейм для MACD (по умолчанию 30-дневные свечи).
FastMaPeriod, SlowMaPeriod
Периоды LWMA, определяющих направление тренда.
MomentumPeriod
Период Momentum на старшем таймфрейме.
MomentumBuyThreshold, MomentumSellThreshold
Минимальное отклонение от 100 для допуска сделок в Long/Short.
StopLossSteps, TakeProfitSteps
Дистанции стоп-лосса и тейк-профита в шагах цены (0 – отключить).
Настройки мартингейла: базовый лот, мультипликатор, надбавка после убытков, максимум доборов.
UseExitByMacd
Закрывать позиции при обратном пересечении MACD.
Как использовать
Привяжите стратегию к инструменту и убедитесь, что заданы Security.PriceStep и Security.StepPrice. Без этих значений
денежные цели работать не будут.
При необходимости измените CandleType, MomentumCandleType и MacdCandleType, чтобы адаптировать стратегию под другие
таймфреймы. Значения по умолчанию соответствуют оригинальному советнику (M15/H1/Monthly).
Настройте параметры, выраженные в пунктах (StopLossSteps, TakeProfitSteps, трейлинг, безубыток) с учётом волатильности и
шага цены инструмента.
Определите правила управления капиталом: фиксированные или процентные тейк-профиты, денежный трейлинг, стоп по просадке.
Запустите стратегию – необходимые подписки на свечи создаются автоматически, индикаторы отображаются на графике при его
наличии, сделки начинают исполняться после формирования всех индикаторов.
Особенности и отличия от MQL-версии
В StockSharp используется агрегированная позиция. При смене сигнала текущая позиция закрывается и новая оценивается со следующей
свечи – это избавляет от наложения разнонаправленных ордеров.
Денежные расчёты зависят от Security.PriceStep и Security.StepPrice. Если значения недоступны, плавающая прибыль считается
нулевой, а денежные цели фактически отключаются (как и в MetaTrader при отсутствии данных).
IncreaseFactor в переносе увеличивает базовый объём на IncreaseFactor × количество_убытков, поскольку в тестовой среде нет
информации о свободной марже. Поведение остаётся аналогичным задумке автора.
Все решения принимаются только по закрытым свечам – как и в оригинальном коде, где использовались сдвиги iMA/iStochastic
по историческим барам.
Если доступен график, стратегия выводит на него те же индикаторы, что облегчает визуальное сравнение с MetaTrader.
Перед реальной торговлей обязательно проверьте шаг цены, стоимость шага и ограничения по объёму для выбранного инструмента. Эти
величины напрямую влияют на преобразование пунктов и расчёт денежных целей внутри стратегии.
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>
/// 15-minute scalper strategy using WMA crossover with ParabolicSar confirmation.
/// Buys when fast WMA crosses above slow WMA and SAR is below price.
/// Sells on reverse conditions.
/// </summary>
public class FifteenMinuteScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public FifteenMinuteScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 85)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.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();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fifteen_minute_scalper_strategy(Strategy):
"""
15-minute scalper using WMA crossover with SL/TP and cooldown.
Buys when fast WMA crosses above slow WMA, sells on reverse.
"""
def __init__(self):
super(fifteen_minute_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20) \
.SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 85) \
.SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Trading timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fifteen_minute_scalper_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(fifteen_minute_scalper_strategy, self).OnStarted2(time)
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast_val)
self._prev_slow = float(slow_val)
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
sl_pts = self._stop_loss_points.Value
tp_pts = self._take_profit_points.Value
if self.Position > 0 and self._entry_price > 0:
if sl_pts > 0 and close <= self._entry_price - sl_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close >= self._entry_price + tp_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if sl_pts > 0 and close >= self._entry_price + sl_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close <= self._entry_price - tp_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return fifteen_minute_scalper_strategy()