GitHub で見る

PROphet Strategy

Overview

This strategy is a StockSharp port of the MetaTrader 4 expert advisor "PROphet". The original EA evaluates the recent trading ran ge across four historical candles and uses those weighted ranges to trigger new trades. It keeps positions open only between the European and U.S. sessions and trails the stop-loss whenever price moves a fixed distance in favour of the trade. The StockSharp implementation keeps all of those mechanics while adapting them to the netting model used by StockSharp portfolios.

Trading logic

  • Subscribe to the configured timeframe (CandleType, default M5) and process only finished candles.
  • Maintain the three most recent completed candles to reproduce the High[i] and Low[i] indexing used by the MQL version.
  • Compute the long trigger Qu(X1, X2, X3, X4) and the short trigger Qu(Y1, Y2, Y3, Y4) on every bar. Each term multiplies a weighted range (for example |High[1] - Low[2]|) by the corresponding weight minus one hundred, exactly as in the original code.
  • Allow new entries only when the current hour falls between TradeStartHour and TradeEndHour (inclusive). This mimics the man ual trading window from the MQL expert (10:00 through 18:00 by default).
  • Use a single market order whose volume neutralises any opposite exposure before opening the new position. This mirrors the Mag ic Number filters from the MetaTrader implementation.

Risk management and trailing

  • The strategy converts the MetaTrader point-based stop distances to price units via the instrument PriceStep. The defaults (B uyStopLossPoints = 68, SellStopLossPoints = 72) match the MQL extern variables.
  • Once the bid (for long trades) or the ask (for short trades) moves beyond the existing stop by spread + 2 * stopDistance, th e trailing stop is advanced to currentPrice ± stopDistance, using live Level-1 data when available.
  • Open trades are force-closed after ExitHour. The default value (18) reproduces the original behaviour of closing the position s after 18:00 server time.
  • Protective exits use market orders because StockSharp's high-level API does not automatically generate stop orders. This keeps behaviour deterministic across brokers.

Parameters

Parameter Description
AllowBuy Enables long trades.
AllowSell Enables short trades.
X1, X2, X3, X4 Weights applied to the long-side range components inside the Qu formula.
BuyStopLossPoints Stop-loss distance for long trades expressed in MetaTrader points.
Y1, Y2, Y3, Y4 Weights applied to the short-side range components inside the Qu formula.
SellStopLossPoints Stop-loss distance for short trades expressed in MetaTrader points.
TradeVolume Base volume (lots) used for new entries. Extra volume is added automatically to close opposite exposure.
TradeStartHour First hour of the trading window (inclusive).
TradeEndHour Last hour of the trading window (inclusive).
ExitHour Hour after which all open trades are closed.
CandleType Timeframe of the candles used for analysis.

Notes

  • StockSharp portfolios are netting by default. When a new signal appears the strategy adds the volume required to flatten the ex isting position before opening the new trade, which reproduces the single-position-per-direction design from the MetaTrader expe rt.
  • The MQL script used the symbol spread reported by MarketInfo. The port retrieves the spread from Level-1 data when available and falls back to a single price step otherwise.
  • Because the trailing stop is evaluated on the close of each finished candle, slippage may occur compared to the tick-level stop updates performed by the original EA.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Translation of the MetaTrader "PROphet" expert advisor.
/// Uses a range-weighted signal from previous candles to enter trades.
/// </summary>
public class ProphetStrategy : Strategy
{
	private readonly StrategyParam<int> _x1;
	private readonly StrategyParam<int> _x2;
	private readonly StrategyParam<int> _x3;
	private readonly StrategyParam<int> _x4;
	private readonly StrategyParam<DataType> _candleType;

	private ICandleMessage _candle1;
	private ICandleMessage _candle2;
	private ICandleMessage _candle3;
	private decimal _entryPrice;

	public ProphetStrategy()
	{
		_x1 = Param(nameof(X1), 9)
			.SetDisplay("X1", "Weight applied to |High[1] - Low[2]|.", "Signal");

		_x2 = Param(nameof(X2), 29)
			.SetDisplay("X2", "Weight applied to |High[3] - Low[2]|.", "Signal");

		_x3 = Param(nameof(X3), 94)
			.SetDisplay("X3", "Weight applied to |High[2] - Low[1]|.", "Signal");

		_x4 = Param(nameof(X4), 125)
			.SetDisplay("X4", "Weight applied to |High[2] - Low[3]|.", "Signal");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations.", "General");
	}

	public int X1 { get => _x1.Value; set => _x1.Value = value; }
	public int X2 { get => _x2.Value; set => _x2.Value = value; }
	public int X3 { get => _x3.Value; set => _x3.Value = value; }
	public int X4 { get => _x4.Value; set => _x4.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_candle1 = null;
		_candle2 = null;
		_candle3 = null;
		_entryPrice = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Shift history
		_candle3 = _candle2;
		_candle2 = _candle1;
		_candle1 = candle;

		if (_candle1 == null || _candle2 == null || _candle3 == null)
			return;

		// Manage position - simple exit on reversal signal
		if (Position > 0)
		{
			var sellSignal = CalculateSignal(true);
			if (sellSignal > 0)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			var buySignal = CalculateSignal(false);
			if (buySignal > 0)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry
		if (Position == 0)
		{
			var buySignal = CalculateSignal(false);
			var sellSignal = CalculateSignal(true);

			if (buySignal > 0 && buySignal > sellSignal)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (sellSignal > 0 && sellSignal > buySignal)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
		}
	}

	private decimal CalculateSignal(bool isSell)
	{
		var term1 = Math.Abs(_candle1.HighPrice - _candle2.LowPrice);
		var term2 = Math.Abs(_candle3.HighPrice - _candle2.LowPrice);
		var term3 = Math.Abs(_candle2.HighPrice - _candle1.LowPrice);
		var term4 = Math.Abs(_candle2.HighPrice - _candle3.LowPrice);

		if (isSell)
		{
			// For sell, use inverted weights
			return (100 - X1) * term1 + (100 - X2) * term2 + (X3 - 100) * term3 + (X4 - 100) * term4;
		}

		// For buy, use standard weights
		return (X1 - 100) * term1 + (X2 - 100) * term2 + (100 - X3) * term3 + (100 - X4) * term4;
	}
}