Открыть на GitHub

Стратегия Et4 MTC v1 (StockSharp)

Общее описание

  • Источник: советник MetaTrader 4 et4_MTC_v1.mq4 от GlobeInvestFund.
  • Цель: предоставить шаблон на базе StockSharp, который повторяет вспомогательные функции исходного эксперта (расчёт объёма, задержка между сделками, набор параметров) и готов для дальнейшего расширения.
  • Стиль торговли: каркас стратегии — автоматически сделки не совершаются, все торговые правила добавляются пользователем.

Ключевые особенности

  1. Полное совпадение параметров
    • Свойства TakeProfit, StopLoss, Slippage, Lots, EnableLogging соответствуют переменным extern из MQL4.
    • Параметр TradeCooldown отражает жёстко зашитую задержку в 30 секунд между операциями.
    • Через CandleType задаётся тип свечей, имитируя “текущий таймфрейм” MT4.
  2. Расчёт лотов от баланса
    • При отрицательном Lots объём вычисляется по формуле floor((balance / 1000 * |Lots|) / 10) / 10 с минимальным значением 0.1 лота.
  3. Контроль паузы между сделками
    • Все события с ордерами (регистрация, изменение, отмена, исполнение) обновляют время последней операции и запускают обратный отсчёт до следующей попытки, аналогично проверке CurTime() - LastTradeTime < 30 в MQL4.
  4. Определение новой свечи
    • Логика CheckLevels портирована через сравнение времён завершённых свечей; флаг _isNewCandle можно использовать при реализации сигналов.
  5. Использование высокоуровневого API
    • Подписка на данные осуществляется через SubscribeCandles().Bind(...).
    • StartProtection() вызывается один раз при старте.
    • Не применяются кастомные коллекции и запросы истории, что соответствует правилам репозитория.

Параметры

Свойство Значение по умолчанию Оптимизация Описание
TakeProfit 150 ✔️ Дистанция до профита (точки), заготовка для пользовательских выходов.
Lots -10 ✔️ Фиксированный объём при значении ≥ 0, либо расчёт от баланса при отрицательном значении.
StopLoss 50 ✔️ Дистанция до стоп-лосса в точках.
Slippage 3 ✖️ Допустимая просадка цены, сохранена для совместимости.
EnableLogging false ✖️ Включает информирующие сообщения о срабатывании задержки.
TradeCooldown 30 секунд ✖️ Минимальная пауза между операциями.
CandleType Свечи 1 минуты ✖️ Тип данных для подписки.

Последовательность работы

  1. Запуск стратегии
    • Расчитывается стартовое значение Volume с учётом баланса.
    • Оформляется подписка на свечи и запускается защита позиций.
  2. Обработка завершённой свечи
    • Проверяется, что свеча закрыта (CandleStates.Finished).
    • Обновляется внутренний индикатор новой свечи.
    • Вызывается IsFormedAndOnlineAndAllowTrading() для валидации состояния движка.
    • При активной задержке выполнение прерывается (с дополнительным логированием, если оно включено).
    • Последовательно вызываются заглушки OpenPosition, ManagePosition, ClosePosition.
  3. Обработчики ордеров и сделок
    • Методы OnOrderRegistered, OnOrderChanged, OnOrderCanceled, OnNewMyTrade обновляют _lastTradeTime, тем самым инициируя новую паузу между действиями.

Расширение

  • Реализуйте условия входа в OpenPosition и возвращайте true при успешной регистрации ордера.
  • Добавьте сопровождение позиции в ManagePosition (перенос стопа, фиксирование части прибыли и т.д.).
  • Опишите выходы в ClosePosition.
  • При необходимости реагировать только на первую свечу используйте _isNewCandle.

Примечания по портированию

  • Исходный советник содержал только вспомогательные функции, поэтому перевод концентрируется на инфраструктуре.
  • Все комментарии переведены на английский язык в соответствии с требованиями AGENTS.md.
  • Для отступов применяются табуляции.
  • Python-версия не создавалась по условиям задачи.

Как использовать

  1. Подключите Et4MtcV1Strategy в проекте StockSharp и заранее задайте Security и Portfolio.
  2. Настройте параметры (в первую очередь Lots) через свойства или интерфейс оптимизации.
  3. Наследуйтесь от класса либо переопределяйте заглушки, чтобы внедрить собственные правила.
  4. Запустите стратегию — механизм задержки не позволит совершить повторную операцию раньше заданного интервала.

Тестирование

  • Автоматические тесты не добавлялись, поскольку оригинальный код не содержал конкретных торговых сигналов. При расширении стратегии рекомендуются собственные проверки.
using System;

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

namespace StockSharp.Samples.Strategies;

public class Et4MtcV1Strategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevMom;
	private bool _hasPrev;
	private int _cooldown;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Et4MtcV1Strategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA trend filter", "Indicators");
		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMom = default;
		_hasPrev = default;
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var mom = new Momentum { Length = MomentumPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, mom, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal mom)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) return;

		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevMom = mom; _hasPrev = true; return; }
		if (_cooldown > 0)
		{
			_cooldown--;
			_prevMom = mom;
			return;
		}

		if (close > ema && _prevMom <= 0 && mom > 0 && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
			_cooldown = 2;
		}
		else if (close < ema && _prevMom >= 0 && mom < 0 && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
			_cooldown = 2;
		}

		_prevMom = mom;
	}
}