Стратегия Expert NEWS представляет собой конверсию MQL5-советника «Expert_NEWS». Алгоритм постоянно размещает симметричные стоп-заявки выше и ниже текущей цены и управляет открытыми позициями с помощью перевода в безубыток, трейлинга и плановых обновлений отложенных ордеров. Реализация использует только потоковые котировки Level1, а базовый объём сделки равен 0.1 лота.
Логика работы
Подписка на котировки – стратегия отслеживает лучшие цены спроса/предложения и рассчитывает уровни ордеров по свежим данным.
Стартовые стоп-заявки – при отсутствии длинной позиции и активного buy stop выставляется ордер по цене ask + EntryOffsetTicks * PriceStep; при отсутствии короткой позиции и sell stop размещается ордер по цене bid - EntryOffsetTicks * PriceStep.
Обновление заявок – каждые OrderRefreshSeconds секунд ордер отменяется и перевыставляется, если нужная цена изменилась больше чем на TrailingStepTicks тик.
Защита позиции – после срабатывания ордера открываются защитные stop и take-profit, если выбранные расстояния удовлетворяют ограничению MinimumStopTicks.
Безубыток – при активном UseBreakEven стоп переносится к entry ± BreakEvenProfitTicks, когда прибыль достаточна и новый уровень соблюдает минимальное расстояние от текущей цены.
Трейлинг-стоп – после достижения прибыли TrailingStartTicks стоп следует за ценой на расстоянии TrailingStopTicks, а каждое обновление требует минимум TrailingStepTicks тиков улучшения.
Очистка – закрытие позиции приводит к отмене всех оставшихся защитных заявок.
Параметры
Параметр
Описание
StopLossTicks
Начальное расстояние защитного стопа в тиках. Ноль отключает стартовый стоп.
TakeProfitTicks
Начальное расстояние тейк-профита в тиках. Ноль отключает тейк-профит.
TrailingStopTicks
Расстояние трейлинг-стопа в тиках.
TrailingStartTicks
Минимальная прибыль в тиках, необходимая для запуска трейлинга.
TrailingStepTicks
Минимальное улучшение при обновлении трейлинг-стопа или при перерегистрации отложенных ордеров.
UseBreakEven
Включает перенос стопа в безубыток при достижении цели по прибыли.
BreakEvenProfitTicks
Дополнительная подушка прибыли при переносе стопа в безубыток.
EntryOffsetTicks
Расстояние между текущей ценой и новыми стоп-заявками на вход.
OrderRefreshSeconds
Интервал между автоматическими попытками обновить отложенные стопы (в секундах).
MinimumStopTicks
Минимально допустимое расстояние стопов (в тиках). Ордеры ближе не отправляются.
Управление позицией
Защитные заявки всегда соответствуют объёму текущей позиции; частичные исполнения автоматически уменьшают или увеличивают объём stop/limit заявок.
Даже при отключённом стартовом стопе механизмы безубытка и трейлинга создадут новый стоп, как только условия будут выполнены.
Стратегия запоминает последнюю цену стопа, поэтому трейлинг обновляет уровень только в одном направлении.
Рекомендации по использованию
Убедитесь, что задан Security.PriceStep, поскольку все параметры в тиках домножаются на этот шаг цены.
Значение Volume по умолчанию равно 0.1, что соответствует исходному роботу. При необходимости измените объём в свойствах стратегии.
Если площадка предъявляет требования к минимальной дистанции стопов, укажите её в MinimumStopTicks. При отсутствии ограничений оставьте параметр равным нулю.
Алгоритм не использует свечи или исторические данные; для работы достаточно потоковых котировок.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy converted from the MQL Expert NEWS robot.
/// Detects breakouts above/below a reference price range and enters positions.
/// Uses stop loss and take profit for position management.
/// </summary>
public class ExpertNewsStrategy : Strategy
{
private readonly StrategyParam<decimal> _entryOffset;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _entryPrice;
private int _lastSignal;
/// <summary>
/// Entry offset from the high/low range.
/// </summary>
public decimal EntryOffset
{
get => _entryOffset.Value;
set => _entryOffset.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Number of bars to determine high/low range.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ExpertNewsStrategy()
{
_entryOffset = Param(nameof(EntryOffset), 200m)
.SetGreaterThanZero()
.SetDisplay("Entry Offset", "Offset from range high/low for entry", "Parameters");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Bars for range calculation", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_entryPrice = 0m;
_lastSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
new Unit(TakeProfit, UnitTypes.Absolute),
new Unit(StopLoss, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
_highs.RemoveAt(0);
if (_lows.Count > LookbackPeriod + 1)
_lows.RemoveAt(0);
if (_highs.Count <= LookbackPeriod)
return;
// Compute range from prior bars (excluding current)
var rangeHigh = decimal.MinValue;
var rangeLow = decimal.MaxValue;
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
if (_lows[i] < rangeLow) rangeLow = _lows[i];
}
var close = candle.ClosePrice;
var breakoutUp = close > rangeHigh + EntryOffset;
var breakoutDown = close < rangeLow - EntryOffset;
if (breakoutUp && _lastSignal != 1 && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_lastSignal = 1;
}
else if (breakoutDown && _lastSignal != -1 && Position >= 0)
{
SellMarket();
_entryPrice = close;
_lastSignal = -1;
}
else if (!breakoutUp && !breakoutDown)
_lastSignal = 0;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class expert_news_strategy(Strategy):
def __init__(self):
super(expert_news_strategy, self).__init__()
self._entry_offset = self.Param("EntryOffset", 200.0)
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._lookback_period = self.Param("LookbackPeriod", 20)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
@property
def EntryOffset(self):
return self._entry_offset.Value
@EntryOffset.setter
def EntryOffset(self, value):
self._entry_offset.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def LookbackPeriod(self):
return self._lookback_period.Value
@LookbackPeriod.setter
def LookbackPeriod(self, value):
self._lookback_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(expert_news_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
period = int(self.LookbackPeriod)
if len(self._highs) > period + 1:
self._highs.pop(0)
if len(self._lows) > period + 1:
self._lows.pop(0)
if len(self._highs) <= period:
return
range_high = -1e18
range_low = 1e18
for i in range(len(self._highs) - 1):
if self._highs[i] > range_high:
range_high = self._highs[i]
if self._lows[i] < range_low:
range_low = self._lows[i]
offset = float(self.EntryOffset)
breakout_up = close > range_high + offset
breakout_down = close < range_low - offset
if breakout_up and self._last_signal != 1 and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._last_signal = 1
elif breakout_down and self._last_signal != -1 and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._last_signal = -1
elif not breakout_up and not breakout_down:
self._last_signal = 0
def OnReseted(self):
super(expert_news_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
def CreateClone(self):
return expert_news_strategy()