TrendScalperStrategy — порт экспертного советника MetaTrader 4 Currencyprofits_01_1.mq4 на платформу StockSharp. Исходный робот представляет собой трендового скальпера: он проверяет пересечение быстрой EMA и медленной SMA, а затем ищет ложные проколы недавних экстремумов, чтобы войти в сделку. Переписанная версия сохраняет ту же логику, используя свечные подписки и индикаторы высокого уровня StockSharp.
Логика торговли
Индикаторы
Быстрая EMA (по умолчанию 6) по ценам закрытия.
Медленная SMA (по умолчанию 12) по ценам закрытия.
Максимум и минимум за последние N свечей (по умолчанию 6) по High/Low.
Условия входа
Покупка: цена касается нижней границы (Lowest Low), при этом быстрая EMA выше медленной SMA. Отправляется рыночная заявка Buy с объёмом согласно выбранному режиму мани-менеджмента.
Продажа: цена касается верхней границы (Highest High), а быстрая EMA ниже медленной SMA. Размещается рыночная заявка Sell.
Пока позиция открыта, новых сделок не открываем — как и в оригинальном советнике, одновременно присутствует только один ордер.
Условия выхода
Закрытие Long: при пробое свечой верхней границы Highest High текущая длинная позиция закрывается по рынку.
Закрытие Short: при падении ниже Lowest Low короткая позиция закрывается по рынку.
При StopLossPoints > 0 дополнительно активируется StartProtection, который привязывает защитный стоп-лосс к каждой сделке.
Управление капиталом
Реализованы три режима из оригинального MQL-советника:
Та же формула, но результат округляется вверх до целого, минимум — 1 лот, максимум — 100.
Баланс берётся из Portfolio.CurrentValue (с запасным вариантом BeginValue). Если значение недоступно, стратегия возвращается к фиксированному объёму, чтобы сохранить совместимость в тестах.
Управление рисками
Стоп-лосс: параметр StopLossPoints задаётся в пунктах. В OnStarted расстояние умножается на Security.PriceStep и передаётся в StartProtection, после чего платформа автоматически сопровождает защитные заявки.
Единственная позиция: новая сделка отправляется только при Position == 0, что полностью повторяет поведение MQL-версии.
Параметры
Имя
Значение по умолчанию
Описание
CandleType
Таймфрейм 15 минут
Свечи, по которым считаются индикаторы и формируются сигналы.
FastLength
6
Период быстрой EMA.
SlowLength
12
Период медленной SMA.
BreakoutWindow
6
Количество свечей для расчёта ближайших максимумов/минимумов.
FixedVolume
0.1 лота
Объём сделки при отключённом мани-менеджменте либо при отсутствии данных по балансу.
MoneyManagementMode
0
Режим расчёта объёма (фиксированный, дробный, округление до целых).
MoneyManagementRisk
40
Фактор риска для расчёта объёма по балансу.
StopLossPoints
50
Дистанция стоп-лосса в пунктах (конвертируется в абсолютную цену перед запуском StartProtection).
Особенности реализации
Используется цепочка SubscribeCandles().Bind(...), поэтому нет необходимости самостоятельно хранить историю цен.
Все комментарии в коде написаны на английском языке в соответствии с требованиями репозитория.
Тесты и другие файлы проекта не изменялись — задача ограничена переносом стратегии и подготовкой документации.
Рекомендации по использованию
Подбирайте таймфрейм в соответствии с изначальной идеей робота (короткие внутридневные интервалы для скальпинга).
Убедитесь, что у инструмента задан корректный PriceStep, иначе перевод стоп-лосса из пунктов в цену будет неверным.
Аккуратно меняйте MoneyManagementRisk: увеличение значения быстро наращивает объём сделки из-за формулы ceil(balance * risk / 10000).
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend Scalper strategy - EMA crossover with Highest/Lowest breakout confirmation.
/// Buys when fast EMA crosses above slow EMA and close is near highest.
/// Sells when fast EMA crosses below slow EMA and close is near lowest.
/// </summary>
public class TrendScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TrendScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 8)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class trend_scalper_strategy(Strategy):
def __init__(self):
super(trend_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 8).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._channel_period = self.Param("ChannelPeriod", 20).SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
@property
def fast_period(self): return self._fast_period.Value
@property
def slow_period(self): return self._slow_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(trend_scalper_strategy, self).OnReseted()
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
def OnStarted2(self, time):
super(trend_scalper_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished: return
f = float(fast); s = float(slow)
if not self._has_prev:
self._prev_fast = f; self._prev_slow = s; self._has_prev = True; return
if self._prev_fast <= self._prev_slow and f > s and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and f < s and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_fast = f; self._prev_slow = s
def CreateClone(self): return trend_scalper_strategy()