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
- 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.
- 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.
- 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.
- 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;
}
}
}
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.Indicators import SimpleMovingAverage, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class pricer_ea_strategy(Strategy):
def __init__(self):
super(pricer_ea_strategy, self).__init__()
self._bb_period = self.Param("BbPeriod", 20) \
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period", "Indicators")
self._signal_cooldown = self.Param("SignalCooldownCandles", 8) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._sma = None
self._rsi = None
self._atr = None
self._candles_since_trade = 0
@property
def bb_period(self):
return self._bb_period.Value
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(pricer_ea_strategy, self).OnReseted()
self._sma = None
self._rsi = None
self._atr = None
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(pricer_ea_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.bb_period
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.rsi_period
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(30)))
subscription.Bind(self._sma, self._rsi, self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, sma_value, rsi_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._sma.IsFormed or not self._rsi.IsFormed or not self._atr.IsFormed:
return
close = float(candle.ClosePrice)
sma_val = float(sma_value)
rsi_val = float(rsi_value)
atr_val = float(atr_value)
band_distance = atr_val * 2.5
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
if self.Position > 0 and (close >= sma_val or rsi_val >= 50.0):
self.SellMarket()
self._candles_since_trade = 0
elif self.Position < 0 and (close <= sma_val or rsi_val <= 50.0):
self.BuyMarket()
self._candles_since_trade = 0
elif self.Position == 0 and self._candles_since_trade >= self.signal_cooldown and close <= sma_val - band_distance and rsi_val < 30.0:
self.BuyMarket()
self._candles_since_trade = 0
elif self.Position == 0 and self._candles_since_trade >= self.signal_cooldown and close >= sma_val + band_distance and rsi_val > 70.0:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return pricer_ea_strategy()