Открыть на GitHub

Стратегия Trend Scalper (API/3858)

Обзор

TrendScalperStrategy — порт экспертного советника MetaTrader 4 Currencyprofits_01_1.mq4 на платформу StockSharp. Исходный робот представляет собой трендового скальпера: он проверяет пересечение быстрой EMA и медленной SMA, а затем ищет ложные проколы недавних экстремумов, чтобы войти в сделку. Переписанная версия сохраняет ту же логику, используя свечные подписки и индикаторы высокого уровня StockSharp.

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

  1. Индикаторы
    • Быстрая EMA (по умолчанию 6) по ценам закрытия.
    • Медленная SMA (по умолчанию 12) по ценам закрытия.
    • Максимум и минимум за последние N свечей (по умолчанию 6) по High/Low.
  2. Условия входа
    • Покупка: цена касается нижней границы (Lowest Low), при этом быстрая EMA выше медленной SMA. Отправляется рыночная заявка Buy с объёмом согласно выбранному режиму мани-менеджмента.
    • Продажа: цена касается верхней границы (Highest High), а быстрая EMA ниже медленной SMA. Размещается рыночная заявка Sell.
    • Пока позиция открыта, новых сделок не открываем — как и в оригинальном советнике, одновременно присутствует только один ордер.
  3. Условия выхода
    • Закрытие Long: при пробое свечой верхней границы Highest High текущая длинная позиция закрывается по рынку.
    • Закрытие Short: при падении ниже Lowest Low короткая позиция закрывается по рынку.
    • При StopLossPoints > 0 дополнительно активируется StartProtection, который привязывает защитный стоп-лосс к каждой сделке.

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

Реализованы три режима из оригинального MQL-советника:

Режим Описание Поведение в порте
0 Фиксированный объём (LotsIfNoMM). Используется значение параметра FixedVolume.
<0 Дробные лоты по балансу и фактору риска. Вычисляется ceil(balance * risk / 10000) / 10, ограничение сверху — 100 лотов.
>0 Целые лоты по балансу и фактору риска. Та же формула, но результат округляется вверх до целого, минимум — 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;
	}
}