The Price Extreme Strategy replicates the MetaTrader expert adviser Price_Extreme_Strategy using the StockSharp high-level API. The system monitors a sliding channel derived from the highest high and lowest low over a configurable number of completed candles. Breakout signals are generated whenever the selected reference candle closes above the upper boundary or below the lower boundary. The logic can optionally be inverted to transform breakout conditions into fade entries.
This conversion keeps the trading workflow event-driven. Orders are submitted immediately after the close of each finished candle, matching the behaviour of the original MQL algorithm that reacted on the opening tick of the next bar.
Indicator Logic
The Price Extreme channel is rebuilt on every finished candle using StockSharp's Highest and Lowest indicators:
Highest tracks the maximum high over the last N candles.
Lowest tracks the minimum low over the last N candles.
These buffers emulate the Price_Extreme_Indicator custom study bundled with the original expert adviser. The indicator length is exposed through the Level Length parameter.
A separate Signal Shift parameter defines which closed candle is used to evaluate the breakout condition. A shift of 1 means "use the candle that just closed" (default). Larger values allow waiting for additional confirmation by referencing older bars.
Trading Rules
Recalculate upper and lower channel values for every finished candle.
Retrieve the candle specified by Signal Shift from the internal history buffer.
Generate directional intents:
Breakout up: the candle's close is above the upper channel value.
Breakout down: the candle's close is below the lower channel value.
Apply optional inversion with Reverse Signals:
If disabled, trade in the breakout direction (buy on breakout up, sell on breakout down).
If enabled, swap the reactions (sell on breakout up, buy on breakout down).
Respect Enable Long and Enable Short permissions before submitting orders.
Automatically close any opposite position before opening a new trade so that only one net position exists at any time.
Risk Management
The strategy provides stop-loss and take-profit handling that mirrors the point-based controls of the MQL version:
Stop Loss and Take Profit are expressed in price steps (Security.PriceStep).
Target prices are recalculated whenever the net position size changes.
If a finished candle overlaps the protective levels (low below the stop for longs, high above the stop for shorts, etc.), the position is closed via market order and the protective targets are cleared.
StartProtection() is enabled during OnStarted to leverage built-in StockSharp safeguards.
Parameters
Parameter
Description
Default
Group
LevelLength
Number of completed candles considered when computing the extreme channel.
5
Indicator
SignalShift
Index of the closed candle used for breakout validation (1 = last closed candle).
1
Indicator
EnableLong
Allows buying when true.
true
Trading
EnableShort
Allows selling when true.
true
Trading
ReverseSignals
Inverts breakout reactions (buy on breakdown, sell on breakout).
false
Trading
OrderVolume
Volume sent with each market order. Must be greater than zero.
1
Trading
StopLossPoints
Stop-loss distance measured in price steps. A value of 0 disables the stop.
0
Risk
TakeProfitPoints
Take-profit distance measured in price steps. A value of 0 disables the target.
0
Risk
CandleType
Primary timeframe for data subscription.
5-minute candles
Data
All parameters use StrategyParam<T> with UI metadata so they can be optimized or modified from the Designer.
Usage Guidelines
Attach the strategy to a security and set the Candle Type to match the timeframe used in the original MetaTrader setup.
Adjust Level Length if a wider or narrower Price Extreme channel is desired.
Configure Signal Shift to control how many closed candles to wait before evaluating the breakout.
Select desired trade directions via Enable Long, Enable Short, and Reverse Signals`.
Define Order Volume, Stop Loss, and Take Profit to match risk preferences. Remember that both protective values operate in price steps.
Launch the strategy. Candles, indicator bands, and executed trades are plotted automatically when a chart area is available.
Additional Notes
The strategy intentionally operates on a single net position, mirroring the hedging logic of the MQL expert by flattening the opposite side before entering a new trade.
Protective stops and targets are evaluated on completed candles. When live trading, this approximates the server-side protective orders used by the original script.
No Python port is included, as requested.
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 based on the Price Extreme indicator.
/// </summary>
public class PriceExtremeStrategy : Strategy
{
private readonly StrategyParam<int> _levelLength;
private readonly StrategyParam<int> _signalShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<bool> _reverseSignals;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _history = new();
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevPosition;
private decimal _entryPrice;
private decimal _prevUpper;
private decimal _prevLower;
/// <summary>
/// Number of candles used to build extreme levels.
/// </summary>
public int LevelLength
{
get => _levelLength.Value;
set => _levelLength.Value = value;
}
/// <summary>
/// Shift in candles used for the breakout signal.
/// </summary>
public int SignalShift
{
get => _signalShift.Value;
set => _signalShift.Value = value;
}
/// <summary>
/// Enable long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Enable short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Reverse long and short signals.
/// </summary>
public bool ReverseSignals
{
get => _reverseSignals.Value;
set => _reverseSignals.Value = value;
}
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="PriceExtremeStrategy"/>.
/// </summary>
public PriceExtremeStrategy()
{
_levelLength = Param(nameof(LevelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Level Length", "Number of candles for price extremes", "Indicator")
.SetOptimize(3, 30, 1);
_signalShift = Param(nameof(SignalShift), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Closed candles used for breakout", "Indicator");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow buying trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow selling trades", "Trading");
_reverseSignals = Param(nameof(ReverseSignals), false)
.SetDisplay("Reverse Signals", "Invert breakout direction", "Trading");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume sent with market orders", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetDisplay("Stop Loss", "Protective stop in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetDisplay("Take Profit", "Profit target in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_highs.Clear();
_lows.Clear();
_prevUpper = 0m;
_prevLower = 0m;
_entryPrice = 0m;
_prevPosition = 0m;
ResetTargets();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool CanOpenLong => EnableLong && OrderVolume > 0m;
private bool CanOpenShort => EnableShort && OrderVolume > 0m;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_history.Add(candle);
var maxHistory = Math.Max(LevelLength + SignalShift + 2, 10);
if (_history.Count > maxHistory)
{
var removeCount = _history.Count - maxHistory;
_history.RemoveRange(0, removeCount);
_highs.RemoveRange(0, removeCount);
_lows.RemoveRange(0, removeCount);
}
if (_highs.Count < LevelLength)
return;
var upper = decimal.MinValue;
var lower = decimal.MaxValue;
for (var i = _highs.Count - LevelLength; i < _highs.Count; i++)
{
if (_highs[i] > upper) upper = _highs[i];
if (_lows[i] < lower) lower = _lows[i];
}
if (_history.Count < SignalShift)
return;
var signalCandle = _history[_history.Count - SignalShift];
var breakoutUp = candle.ClosePrice > _prevUpper && _prevUpper > 0;
var breakoutDown = candle.ClosePrice < _prevLower && _prevLower > 0;
_prevUpper = upper;
_prevLower = lower;
var wantLong = ReverseSignals ? breakoutDown : breakoutUp;
var wantShort = ReverseSignals ? breakoutUp : breakoutDown;
if (wantLong && CanOpenLong && Position == 0)
{
BuyMarket();
}
else if (wantShort && CanOpenShort && Position == 0)
{
SellMarket();
}
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = 0m;
}
private void UpdateTargets()
{
_stopPrice = null;
_takePrice = null;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m || Position == 0m)
return;
if (Position > 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice - StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice + TakeProfitPoints * step;
}
else if (Position < 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice + StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice - TakeProfitPoints * step;
}
}
private void ApplyRiskManagement(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
else if (Position < 0m)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
}
private void ResetTargets()
{
_stopPrice = null;
_takePrice = null;
_prevPosition = Position;
}
}
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 price_extreme_strategy(Strategy):
def __init__(self):
super(price_extreme_strategy, self).__init__()
self._level_length = self.Param("LevelLength", 20)
self._signal_shift = self.Param("SignalShift", 1)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LevelLength(self):
return self._level_length.Value
@property
def SignalShift(self):
return self._signal_shift.Value
@property
def EnableLong(self):
return self._enable_long.Value
@property
def EnableShort(self):
return self._enable_short.Value
@property
def ReverseSignals(self):
return self._reverse_signals.Value
def OnStarted2(self, time):
super(price_extreme_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
self.StartProtection(
Unit(3, UnitTypes.Percent),
Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
self._history.append(c)
max_hist = max(self.LevelLength + self.SignalShift + 2, 10)
while len(self._history) > max_hist:
self._history.pop(0)
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < self.LevelLength:
return
upper = max(self._highs[-self.LevelLength:])
lower = min(self._lows[-self.LevelLength:])
if len(self._history) < self.SignalShift:
self._prev_upper = upper
self._prev_lower = lower
return
breakout_up = c > self._prev_upper and self._prev_upper > 0
breakout_down = c < self._prev_lower and self._prev_lower > 0
self._prev_upper = upper
self._prev_lower = lower
want_long = breakout_down if self.ReverseSignals else breakout_up
want_short = breakout_up if self.ReverseSignals else breakout_down
if want_long and self.EnableLong and self.Position == 0:
self.BuyMarket()
elif want_short and self.EnableShort and self.Position == 0:
self.SellMarket()
def OnReseted(self):
super(price_extreme_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
def CreateClone(self):
return price_extreme_strategy()