Ver en GitHub

Expert NEWS Strategy

Overview

Expert NEWS Strategy is a direct conversion of the "Expert_NEWS" MQL5 robot. The strategy continuously places symmetrical stop orders above and below the current market price and manages the resulting positions with break-even protection, trailing stops, and scheduled refreshes of pending orders. The implementation relies on Level1 quotes and keeps the default trading volume at 0.1 lots.

Trading Logic

  1. Quote subscription – the strategy listens to best bid/ask updates and computes order prices from the latest values.
  2. Initial stop orders – when no long position or buy stop exists, a new buy stop is placed at ask + EntryOffsetTicks * PriceStep. When no short position or sell stop exists, a sell stop is placed at bid - EntryOffsetTicks * PriceStep.
  3. Order refreshing – every OrderRefreshSeconds, the strategy cancels and re-creates a pending stop if the required price deviates by more than TrailingStepTicks ticks.
  4. Position protection – after a fill, the strategy opens protective stop and take-profit orders if the requested distances meet the MinimumStopTicks constraint.
  5. Break-even control – when UseBreakEven is enabled, the stop is pulled to entry ± BreakEvenProfitTicks once the market moves far enough and the new stop respects the minimum distance from the current quote.
  6. Trailing stop – once the price advances by TrailingStartTicks, the stop follows using TrailingStopTicks as the distance and TrailingStepTicks as the minimum improvement step.
  7. Cleanup – flattening the position cancels every remaining protective order.

Parameters

Parameter Description
StopLossTicks Initial protective stop distance (ticks). Set to zero to disable the initial stop order.
TakeProfitTicks Initial take-profit distance (ticks). Set to zero to disable the target order.
TrailingStopTicks Distance of the trailing stop (ticks).
TrailingStartTicks Profit in ticks required before the trailing logic activates.
TrailingStepTicks Minimum improvement when refreshing either the trailing stop or the pending entry orders.
UseBreakEven Enables the break-even shift of the stop once there is enough profit.
BreakEvenProfitTicks Additional profit cushion when moving the stop to break-even.
EntryOffsetTicks Distance between current quote and each new stop entry order.
OrderRefreshSeconds Time interval between automatic refresh attempts for pending stop orders.
MinimumStopTicks Manual fallback for the broker stop-level requirement. Stops closer than this distance are not submitted.

Position Management

  • Protective orders always match the net position volume. Partial fills automatically resize the stop and take-profit orders.
  • Break-even and trailing logic work even when the initial stop is disabled; the stop will be created dynamically once the rules are satisfied.
  • The strategy keeps the most recent stop price in memory so that trailing updates preserve monotonic behavior.

Usage Notes

  • Ensure the Security.PriceStep is configured; every tick distance parameter is multiplied by this value.
  • The default volume is 0.1 to mirror the original robot. Adjust the Volume property if another size is required.
  • MinimumStopTicks should be set to the venue’s stop-level requirement if the trading venue enforces one. Leave it at zero to allow the tightest possible stops.
  • The algorithm does not rely on historical bars and can operate on streaming quotes only.
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;
	}
}