Открыть на GitHub

Стратегия Simple

Обзор

Simple Strategy — это высокоуровневая реализация экспертного советника S!mple.mq4 из каталога MQL/9019 для платформы StockSharp. Исходный советник отслеживал фиксированный набор валютных пар и открывал позиции, когда 50-периодная линейно-взвешенная скользящая средняя пересекала 200-периодную простую среднюю. Каждую сделку можно было повторять заданное число раз, а к ордерам добавлялись стоп-лосс и тейк-профит, заданные в валюте счёта. Конверсия сохраняет все эти особенности, представляет параметры в виде StrategyParam и формирует диагностические сообщения, аналогичные полю Comment в MetaTrader.

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

  1. Подготовка данных. Стратегия подписывается на настраиваемый тип свечей (по умолчанию пятиминутные) и связывает обе скользящие средние через метод SubscribeCandles().Bind(...).
  2. Поиск пересечения. Для каждой средней хранятся два предыдущих значения. Сигнал BUY появляется, когда две свечи назад LWMA находилась ниже SMA, а на последней завершённой свече закрылась выше. Сигнал SELL формируется при обратном условии.
  3. Оценка тренда. Для восстановления текстового отчёта в МТ4 хранится значение медленной SMA, полученное TrendMargin свечей назад. Текущее значение сравнивается с ним, чтобы классифицировать фон как UP, DOWN или WAIT, и посчитать разницу в шагах цены.
  4. Исполнение.
    • При сигнале BUY закрывается short-позиция (если она есть), после чего объём доводится до NumOrders * TradeVolume. Это повторяет поведение советника, который отправлял несколько одинаковых ордеров подряд.
    • При сигнале SELL сначала ликвидируется long-позиция, затем открывается short до того же агрегированного объёма.
  5. Защитные уровни. Денежные стоп-лосс и тейк-профит (StopLossMoney, TakeProfitMoney) переводятся в цену через PriceStep/StepPrice инструмента и объём одной «ступени» (TradeVolume). На каждой завершённой свече проверяются максимум и минимум; при срабатывании уровня позиция закрывается рыночным ордером.
  6. Операционный флаг. Фактические заявки отправляются только если EnableTrading = true, что соответствует параметру makeTrades из оригинального советника. Значение false оставляет стратегию в режиме наблюдения.

Риск-менеджмент и денежные стопы

  • Значения стоп-лосса и тейк-профита интерпретируются как допустимая прибыль/убыток для одной единицы объёма (одного MT4-ордера). Для пересчёта используется метаданные инструмента (PriceStep, StepPrice). Если эти поля не заданы, стратегия один раз записывает предупреждение и отключает денежные защиты.
  • Проверка защитных уровней выполняется по максимуму и минимуму завершённой свечи, что воспроизводит логику советника, но укладывается в рамки высокоуровневого API StockSharp.
  • При запуске вызывается StartProtection(), чтобы сохранить активной встроенную защиту счёта.

Параметры

Имя Значение по умолчанию Описание
TradeVolume 0.1 Объём одной «ступени» входа (эквивалент объёму ордера в МТ4). Значение синхронизируется со Strategy.Volume.
NumOrders 1 Максимальное число таких ступеней в одном направлении. Целевой объём = TradeVolume * NumOrders.
StopLossMoney 0 Денежный стоп-лосс на одну ступень. Ноль отключает стоп.
TakeProfitMoney 0 Денежный тейк-профит на одну ступень. Ноль отключает цель.
TrendMargin 10 Количество завершённых свечей между текущей SMA и опорным значением для определения тренда.
FastLength 50 Период быстрой линейно-взвешенной скользящей средней.
SlowLength 200 Период медленной простой скользящей средней.
EnableTrading false Разрешение на торговлю. При false стратегия только пишет сигналы в лог.
CandleType таймфрейм 5 минут Тип свечей для расчётов.

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

  • В MetaTrader советник перебирал шесть заранее заданных символов. В StockSharp стратегия работает с одним инструментом, назначенным в Strategy.Security. Чтобы повторить мульти-символьную работу, запустите несколько экземпляров стратегии или объедините их в родительскую стратегию.
  • Денежные стопы зависят от PriceStep и StepPrice. Для валютных пар задайте минимальный шаг цены (например, 0.0001) и стоимость шага на один лот. При отсутствии этих параметров дистанция будет равна нулю, о чём стратегия предупредит в логе.
  • Журнал на каждой завершённой свече повторяет информацию из MetaTrader: направление сигнала, значения обеих скользящих, расстояние между ними в шагах цены и оценку тренда.
  • Агрегирование объёма через NumOrders сохраняет суммарную экспозицию на уровне оригинала, но использует высокоуровневые методы BuyMarket/SellMarket вместо множества отдельных OrderSend.
  • Python-версия не создаётся — это соответствует заданию.

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

  • Назначьте валютный инструмент с корректно заполненными PriceStep, StepPrice и VolumeStep. Подберите TradeVolume под нужный размер лота и включите торговлю (EnableTrading = true) только после проверки журналов.
  • Для режима «только анализ» оставьте EnableTrading = false. После переключения в true следующая точка пересечения скользящих сгенерирует рыночную заявку.
  • Поскольку защитные уровни контролируются на закрытии свечей, при необходимости более точной реакции используйте свечи меньшего таймфрейма, чтобы приблизиться к тиковой логике MetaTrader.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple: Weighted MA crosses Simple MA crossover strategy.
/// Trades direction changes when fast WMA crosses slow SMA.
/// </summary>
public class SimpleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public SimpleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastLength = Param(nameof(FastLength), 50)
			.SetDisplay("Fast WMA", "Fast weighted moving average period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 200)
			.SetDisplay("Slow SMA", "Slow simple moving average period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fast = new WeightedMovingAverage { Length = FastLength };
		var slow = new SimpleMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, atr, ProcessCandle)
			.Start();

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

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

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: WMA/SMA crossover
		if (Position == 0)
		{
			if (_prevFast <= _prevSlow && fastVal > slowVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevFast >= _prevSlow && fastVal < slowVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}