Данный документ описывает порт советника MetaTrader earlyTopProrate_V1 на платформу StockSharp. Стратегия ищет внутридневные движения от дневного открытия и поэтапно фиксирует прибыль по трём целям. Конвертация выполнена с использованием высокоуровневого API StockSharp с сохранением ключевых идей управления позицией и капиталом.
Основная логика
Дневной контекст – стратегия восстанавливает цену открытия, максимум и минимум текущего дня на основе поступающих свечей. Доминирующее направление определяется сравнением high - open и open - low.
Торговое окно – новые сделки открываются только между StartHour (включительно) и EndHour (исключительно). Значения по умолчанию охватывают раннюю европейскую сессию.
Условия входа –
При доминирующем восходящем движении и закрытии свечи выше дневного открытия открывается длинная позиция.
При доминирующем нисходящем движении и закрытии ниже дневного открытия открывается короткая позиция.
Одновременно допускается только одна рыночная позиция (MaxPositions = 1).
Управление объёмом – объём сделки рассчитывается выбранным режимом мани-менеджмента. Полученное значение округляется по шагу объёма инструмента и ограничивается биржевыми минимумами и максимумами.
Сопровождение позиции – после открытия сделки применяется набор выходов, описанный ниже. Логика повторяет оригинальный советник, но реализована через рыночные заявки StockSharp вместо прямого изменения стоп- и тейк-профитов.
Закрытие сессии – если позиция остаётся открытой к моменту ClosingHour, она принудительно закрывается по рынку.
Управление сделкой
В MQL-версии стопы и тейк-профиты модифицировались вручную. Порт на StockSharp повторяет поведение посредством проверок на каждой закрытой свече:
Безубыток после просадки (BreakEvenTrigger) – при движении против позиции на заданное количество пунктов стратегия ждёт возврата к цене входа и закрывает остаток в ноль.
Аварийный стоп (StopLoss0) – при превышении допустимой просадки позиция немедленно закрывается.
Перенос стопа в безубыток (StopLoss1) – после набранной прибыли стоп переносится на цену входа.
Стоп в прибыль (StopLoss2) – при дальнейшем росте прибыли защитный стоп смещается выше (для лонга) или ниже (для шорта) цены входа. Смещение соответствует формуле StopLoss2 - StopLoss1, что воспроизводит расчёт setSL2-35 из оригинала.
Частичное закрытие (TakeProfit1/2/3 и Ratio1/2/3) – каждая из трёх целей закрывает часть текущего объёма позиции. Проценты рассчитываются от остатка, поэтому последующие цели работают с уменьшенным объёмом. Третья цель ликвидирует весь остаток.
Все расстояния задаются в пунктах. Параметр PointMultiplier умножает биржевой PriceStep, повторяя метатрейдеровскую формулу value * 10 * Point (значение по умолчанию – 10).
Режимы мани-менеджмента
Параметр MoneyManagementType выбирает один из четырёх вариантов расчёта объёма:
Режим
Описание
0 или 1
Фиксированный объём, равный BaseVolume (как и в оригинальном советнике).
2
Корень из баланса – 0.1 * sqrt(balance / 1000) * MoneyManagementFactor, где используется текущая стоимость портфеля.
3
Модель риска от капитала – equity / price / 1000 * MoneyManagementRiskPercent / 100, что повторяет формулу AccountEquity/Close[0] в MQL.
После расчёта объём нормализуется по шагу объёма и биржевым ограничениям.
Параметры
Параметр
Описание
CandleType
Свечной таймфрейм для принятия решений (по умолчанию 5 минут).
StartHour / EndHour
Часы начала и окончания торгового окна (0–23).
ClosingHour
Час, когда оставшиеся позиции закрываются.
TimeZoneShift
Справочный сдвиг часового пояса (сохранён для совместимости).
BaseVolume
Базовый лот до применения мани-менеджмента.
MaxPositions
Максимальное количество одновременных позиций.
TakeProfit1, TakeProfit2, TakeProfit3
Дистанции до трёх целей в пунктах.
BreakEvenTrigger
Просадка в пунктах, после которой включается выход в безубыток.
StopLoss0, StopLoss1, StopLoss2
Пороги для аварийного выхода и переноса стопов.
Ratio1, Ratio2, Ratio3
Проценты закрываемого объёма на каждой цели.
MoneyManagementType
Режим расчёта объёма (0–3).
MoneyManagementFactor
Множитель для корневой модели.
MoneyManagementRiskPercent
Процент риска для модели от капитала.
PointMultiplier
Множитель к биржевому шагу цены при переводе пунктов в реальные цены.
Практические рекомендации
Выбирайте таймфрейм, соответствующий доступной ликвидности и вашему горизонту. 5-минутные свечи дают баланс между скоростью реакции и шумом.
Если понятие «пункт» на площадке отличается от оригинального MetaTrader, скорректируйте PointMultiplier.
Безубыточная и трейлинговая логика работает по закрытым свечам, поэтому внутрисвечное поведение может отличаться от тикового исполнения MetaTrader. Учтите это при тестах.
Параметр TimeZoneShift носит информативный характер. Настраивайте реальные торговые часы через StartHour, EndHour и ClosingHour.
Как начать работу
Добавьте стратегию в проект StockSharp либо запустите её в Designer/Runner.
Настройте свечную подписку (CandleType) и торговые часы под выбранный инструмент.
Подберите дистанции и доли частичного выхода с учётом волатильности актива.
Выберите режим мани-менеджмента и задайте соответствующие параметры (BaseVolume, MoneyManagementFactor, MoneyManagementRiskPercent).
Сначала протестируйте стратегию на демо или исторических данных, чтобы убедиться, что порт ведёт себя ожидаемо, прежде чем работать с реальными средствами.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Early Top Prorate V1: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class EarlyTopProrateV1Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public EarlyTopProrateV1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 25)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <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 fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 1.5m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 1.5m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 55) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 45) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class early_top_prorate_v1_strategy(Strategy):
def __init__(self):
super(early_top_prorate_v1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 25) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(early_top_prorate_v1_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 1.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 1.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 55:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 45:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(early_top_prorate_v1_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return early_top_prorate_v1_strategy()