Auf GitHub ansehen

PricerEA Strategy

Overview

The PricerEA Strategy recreates the behaviour of the MetaTrader 4 expert "PricerEA v1.0" using the StockSharp high-level API. It places up to four pending orders (buy stop, sell stop, buy limit and sell limit) at manually defined price levels. Once any pending order is filled the strategy attaches protective stop-loss and take-profit orders, optionally enabling a trailing stop and break-even adjustment to follow the original Expert Advisor.

How it works

  1. Pending orders – the strategy reads absolute price levels from the inputs and submits the corresponding pending orders only once at start-up. Optional expiration can be configured in minutes.
  2. Volume selection – users may keep the fixed manual lot size or switch to the automatic mode where the volume is derived from the portfolio balance and the MT4 risk factor analogue.
  3. Protection – after an entry order is filled the strategy creates stop-loss and take-profit orders at the configured distance (expressed in price points). When both trailing and break-even are enabled the stop follows the original MQL conditions: it is moved only after the price covers the break-even distance plus the initial stop.
  4. Order maintenance – pending orders are cancelled automatically when their lifetime expires or when the strategy stops.

Parameters

Parameter Description
BuyStopPrice, SellStopPrice, BuyLimitPrice, SellLimitPrice Absolute prices for the corresponding pending orders. A value of 0 disables the order.
TakeProfitPoints Distance from the entry price to the take-profit order, measured in price points (Security.PriceStep).
StopLossPoints Distance from the entry price to the stop-loss order, also measured in price points.
EnableTrailingStop Enables the trailing stop logic.
TrailingStepPoints Minimal movement (in points) required before the trailing stop is moved.
EnableBreakEven Enables the break-even rule which lifts the stop above/below the entry after sufficient profit.
BreakEvenTriggerPoints Extra profit (points) required before the stop is moved for break-even.
PendingExpiryMinutes Lifetime of the pending orders in minutes. 0 keeps them alive until filled or manually cancelled.
VolumeMode Chooses between manual volume and automatic sizing.
RiskFactor Risk multiplier used by automatic sizing (mirrors the MQL input).
ManualVolume Fixed lot size used when VolumeMode is set to Manual.

Differences vs. the MT4 version

  • The automatic volume calculation uses the StockSharp portfolio balance and the security contract multiplier. Different brokers may use distinct formulas, therefore the resulting value can differ slightly from MetaTrader.
  • Protective orders are placed via StockSharp helpers and respect the venue volume step, minimum and maximum volume.
  • Expiration is implemented inside the strategy (MetaTrader relies on server-side order expiration).

Usage notes

  • Configure the price levels before starting the strategy. Values equal to zero leave the corresponding order disabled.
  • To imitate the MT4 "Digits" logic the point-based parameters operate in Security.PriceStep units.
  • Combine the strategy with StockSharp's portfolio and logging tools to monitor pending orders and protective stops.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// PricerEA strategy: Bollinger Bands mean reversion with RSI filter.
/// Buys at lower band when RSI is oversold, sells at upper band when overbought.
/// </summary>
public class PricerEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bbPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public PricerEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_bbPeriod = Param(nameof(BbPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for adaptive band distance", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candlesSinceTrade = SignalCooldownCandles;
		var sma = new SimpleMovingAverage { Length = BbPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(sma, rsi, atr, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal sma, decimal rsi, decimal atr)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;
		var bandDistance = atr * 2.5m;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (Position > 0 && (close >= sma || rsi >= 50))
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position < 0 && (close <= sma || rsi <= 50))
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position == 0 && _candlesSinceTrade >= SignalCooldownCandles && close <= sma - bandDistance && rsi < 30)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position == 0 && _candlesSinceTrade >= SignalCooldownCandles && close >= sma + bandDistance && rsi > 70)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}