Источник: советник MetaTrader 4 et4_MTC_v1.mq4 от GlobeInvestFund.
Цель: предоставить шаблон на базе StockSharp, который повторяет вспомогательные функции исходного эксперта (расчёт объёма, задержка между сделками, набор параметров) и готов для дальнейшего расширения.
Стиль торговли: каркас стратегии — автоматически сделки не совершаются, все торговые правила добавляются пользователем.
Ключевые особенности
Полное совпадение параметров
Свойства TakeProfit, StopLoss, Slippage, Lots, EnableLogging соответствуют переменным extern из MQL4.
Параметр TradeCooldown отражает жёстко зашитую задержку в 30 секунд между операциями.
Через CandleType задаётся тип свечей, имитируя “текущий таймфрейм” MT4.
Расчёт лотов от баланса
При отрицательном Lots объём вычисляется по формуле floor((balance / 1000 * |Lots|) / 10) / 10 с минимальным значением 0.1 лота.
Контроль паузы между сделками
Все события с ордерами (регистрация, изменение, отмена, исполнение) обновляют время последней операции и запускают обратный отсчёт до следующей попытки, аналогично проверке CurTime() - LastTradeTime < 30 в MQL4.
Определение новой свечи
Логика CheckLevels портирована через сравнение времён завершённых свечей; флаг _isNewCandle можно использовать при реализации сигналов.
Использование высокоуровневого API
Подписка на данные осуществляется через SubscribeCandles().Bind(...).
StartProtection() вызывается один раз при старте.
Не применяются кастомные коллекции и запросы истории, что соответствует правилам репозитория.
Параметры
Свойство
Значение по умолчанию
Оптимизация
Описание
TakeProfit
150
✔️
Дистанция до профита (точки), заготовка для пользовательских выходов.
Lots
-10
✔️
Фиксированный объём при значении ≥ 0, либо расчёт от баланса при отрицательном значении.
StopLoss
50
✔️
Дистанция до стоп-лосса в точках.
Slippage
3
✖️
Допустимая просадка цены, сохранена для совместимости.
EnableLogging
false
✖️
Включает информирующие сообщения о срабатывании задержки.
TradeCooldown
30 секунд
✖️
Минимальная пауза между операциями.
CandleType
Свечи 1 минуты
✖️
Тип данных для подписки.
Последовательность работы
Запуск стратегии
Расчитывается стартовое значение Volume с учётом баланса.
Оформляется подписка на свечи и запускается защита позиций.
Обработка завершённой свечи
Проверяется, что свеча закрыта (CandleStates.Finished).
Обновляется внутренний индикатор новой свечи.
Вызывается IsFormedAndOnlineAndAllowTrading() для валидации состояния движка.
При активной задержке выполнение прерывается (с дополнительным логированием, если оно включено).
Последовательно вызываются заглушки OpenPosition, ManagePosition, ClosePosition.
Обработчики ордеров и сделок
Методы OnOrderRegistered, OnOrderChanged, OnOrderCanceled, OnNewMyTrade обновляют _lastTradeTime, тем самым инициируя новую паузу между действиями.
Расширение
Реализуйте условия входа в OpenPosition и возвращайте true при успешной регистрации ордера.
Добавьте сопровождение позиции в ManagePosition (перенос стопа, фиксирование части прибыли и т.д.).
Опишите выходы в ClosePosition.
При необходимости реагировать только на первую свечу используйте _isNewCandle.
Примечания по портированию
Исходный советник содержал только вспомогательные функции, поэтому перевод концентрируется на инфраструктуре.
Все комментарии переведены на английский язык в соответствии с требованиями AGENTS.md.
Для отступов применяются табуляции.
Python-версия не создавалась по условиям задачи.
Как использовать
Подключите Et4MtcV1Strategy в проекте StockSharp и заранее задайте Security и Portfolio.
Настройте параметры (в первую очередь Lots) через свойства или интерфейс оптимизации.
Наследуйтесь от класса либо переопределяйте заглушки, чтобы внедрить собственные правила.
Запустите стратегию — механизм задержки не позволит совершить повторную операцию раньше заданного интервала.
Тестирование
Автоматические тесты не добавлялись, поскольку оригинальный код не содержал конкретных торговых сигналов. При расширении стратегии рекомендуются собственные проверки.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Momentum
from StockSharp.Algo.Strategies import Strategy
class et4_mtc_v1_strategy(Strategy):
"""
EMA + Momentum crossover strategy.
Buys when price > EMA and momentum crosses above 0.
Sells when price < EMA and momentum crosses below 0.
"""
def __init__(self):
super(et4_mtc_v1_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._momentum_period = self.Param("MomentumPeriod", 14) \
.SetDisplay("Momentum", "Momentum period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(et4_mtc_v1_strategy, self).OnReseted()
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(et4_mtc_v1_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
mom = Momentum()
mom.Length = self._momentum_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, mom, self._process_candle).Start()
def _process_candle(self, candle, ema_val, mom_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
ema_val = float(ema_val)
mom_val = float(mom_val)
if not self._has_prev:
self._prev_mom = mom_val
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_mom = mom_val
return
if close > ema_val and self._prev_mom <= 0 and mom_val > 0 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 2
elif close < ema_val and self._prev_mom >= 0 and mom_val < 0 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 2
self._prev_mom = mom_val
def CreateClone(self):
return et4_mtc_v1_strategy()