This strategy is a C# conversion of the MetaTrader expert advisor Autotrade (barabashkakvn's edition). It continuously maintains two symmetric stop entry orders around the current market price. Whenever the market remains flat and no position is open, the strategy refreshes both pending orders. When a stop order fills, the position is actively monitored: exits are triggered once the price action stabilizes or when an absolute profit/loss threshold is met. The implementation uses the high-level StockSharp API as required by the project guidelines.
Mapping from the original inputs
StockSharp parameter
MQL5 input
Description
IndentTicks
InpIndent
Distance (in price steps) between the current price and the stop entry orders.
MinProfit
MinProfit
Minimum floating profit (account currency) needed to exit during a quiet market phase.
ExpirationMinutes
ExpirationMinutes
Lifetime of the pending stop orders before they are cancelled and recreated.
AbsoluteFixation
AbsoluteFixation
Absolute profit or loss level (currency) that forces the position to close.
StabilizationTicks
InpStabilization
Maximum size of the previous candle body that is treated as a consolidation zone.
OrderVolume
Lots
Volume used for both the buy stop and the sell stop orders.
CandleType
Period()
Candle series that drives the logic (default 1-minute time frame).
All numerical inputs that represent price distances are converted from "points" to actual price steps through the Security.PriceStep value. Profit-based thresholds are calculated using Security.StepPrice, which mirrors the MQL profit calculations that operate in the deposit currency.
Trading logic
Pending order deployment
The strategy reacts only to finished candles (CandleStates.Finished).
The very first candle is used to seed historical data (previous open/close) and immediately schedule pending orders.
When no position is open, any inactive references are cleared and:
A buy stop is placed at Close + IndentTicks * PriceStep.
A sell stop is placed at Close - IndentTicks * PriceStep.
Each pending order receives an expiration timestamp equal to CloseTime + ExpirationMinutes minutes. When that time is reached the order is cancelled and recreated on the next candle.
Position management
Once either stop order is executed, the opposite pending order is cancelled to avoid unwanted hedging on the netting-based StockSharp account model.
The strategy keeps the previous candle body (|Open - Close|) to detect quiet market conditions.
For every candle with an open position:
Unrealized profit is estimated in currency using the price difference versus PositionAvgPrice, scaled by Security.PriceStep and Security.StepPrice.
If the profit exceeds MinProfitand the previous candle body is below StabilizationTicks * PriceStep, the position is closed at market.
Regardless of stabilization, if the absolute profit or loss exceeds AbsoluteFixation, the position is also closed at market.
Whenever the position returns to flat, all remaining pending orders are cleared.
Additional behaviors
Only one position is allowed at a time; order volumes are netted using OrderVolume.
Because StockSharp does not expose bid/ask during backtests in the same way as MetaTrader, the close price of the completed candle is used as the reference level for new stop orders.
The strategy automatically refreshes the cached Volume value whenever OrderVolume is adjusted via parameters or optimization.
Implementation notes and differences
Profit calculations rely on Security.PriceStep and Security.StepPrice. Ensure these fields are filled in the instrument metadata; otherwise the default value 1 is used as a fallback.
The original MQL version allowed temporary hedging (multiple orders in opposite directions). The StockSharp port cancels the unused stop immediately after a fill to comply with the platform's netting model.
Pending order expiration uses the candle's CloseTime. If historical data lacks close timestamps, adjust the feed to provide them or extend the code accordingly.
The strategy works with any candle data type by adjusting CandleType. Default candles are time-frame based (TimeSpan.FromMinutes(1).TimeFrame()).
Usage recommendations
Configure the candle series that matches the chart period used in MetaTrader.
Set IndentTicks, StabilizationTicks, and profit thresholds in relation to the instrument's tick size and tick value.
Verify that the portfolio uses hedging or netting as desired. The strategy assumes netting and will flat the book before rearming stop orders.
Use the provided parameters for optimization in StockSharp Designer or Backtester to adapt the behaviour to different markets.
Monitor the log output: the code relies on finished candles and market availability (IsFormedAndOnlineAndAllowTrading()) before it submits new orders.
Risk disclaimer
Automated trading involves substantial risk. Backtest thoroughly, validate the parameters on historical data, and confirm broker-specific requirements (such as minimum distances for stop orders) before deploying the strategy on a live account.
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>
/// Conversion of the MQL Autotrade strategy that places symmetric stop orders around the market.
/// Pending stop entries are refreshed on every candle while no position is open.
/// Positions are closed when the market calms down or when absolute profit/loss thresholds are reached.
/// </summary>
public class AutotradePendingStopsStrategy : Strategy
{
private readonly StrategyParam<int> _indentTicks;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<int> _expirationMinutes;
private readonly StrategyParam<decimal> _absoluteFixation;
private readonly StrategyParam<int> _stabilizationTicks;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevOpen;
private decimal _prevClose;
private bool _hasPrevCandle;
private decimal _tickSize = 1m;
private decimal _tickValue = 1m;
/// <summary>
/// Distance in price steps from the current market to the pending stop entries.
/// </summary>
public int IndentTicks
{
get => _indentTicks.Value;
set => _indentTicks.Value = value;
}
/// <summary>
/// Minimal profit in account currency required to exit when price action stabilizes.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Lifetime of pending stop orders in minutes.
/// </summary>
public int ExpirationMinutes
{
get => _expirationMinutes.Value;
set => _expirationMinutes.Value = value;
}
/// <summary>
/// Absolute profit or loss that forces the position to close.
/// </summary>
public decimal AbsoluteFixation
{
get => _absoluteFixation.Value;
set => _absoluteFixation.Value = value;
}
/// <summary>
/// Maximum size of the previous candle body that is treated as consolidation.
/// </summary>
public int StabilizationTicks
{
get => _stabilizationTicks.Value;
set => _stabilizationTicks.Value = value;
}
/// <summary>
/// Order volume used for entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set
{
_orderVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Candle type used to drive the strategy logic.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public AutotradePendingStopsStrategy()
{
_indentTicks = Param(nameof(IndentTicks), 200)
.SetGreaterThanZero()
.SetDisplay("Indent Ticks", "Distance in ticks between price and pending stop orders", "Entries");
_minProfit = Param(nameof(MinProfit), 2m)
.SetGreaterThanZero()
.SetDisplay("Min Profit", "Minimum profit to close during low volatility", "Risk");
_expirationMinutes = Param(nameof(ExpirationMinutes), 41)
.SetGreaterThanZero()
.SetDisplay("Order Expiration", "Lifetime of pending stops in minutes", "Entries");
_absoluteFixation = Param(nameof(AbsoluteFixation), 43m)
.SetGreaterThanZero()
.SetDisplay("Absolute Fixation", "Profit or loss in currency that forces exit", "Risk");
_stabilizationTicks = Param(nameof(StabilizationTicks), 25)
.SetGreaterThanZero()
.SetDisplay("Stabilization Ticks", "Maximum candle body considered as flat market", "Exits");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Default volume for both stop orders", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame that drives order refresh", "General");
Volume = _orderVolume.Value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Reset runtime state when the strategy is reloaded.
_prevOpen = 0m;
_prevClose = 0m;
_hasPrevCandle = false;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = _orderVolume.Value;
// Cache price step and tick value for fast profit calculations.
_tickSize = Security.PriceStep ?? 1m;
_tickValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? _tickSize;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
// Only act on completed candles to stay aligned with the original MQL logic.
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrevCandle)
{
// Store the first candle so that stabilization checks have history.
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_hasPrevCandle = true;
EnsurePendingOrders(candle);
return;
}
UpdatePendingOrdersLifetime(candle);
if (Position == 0)
{
// Refresh pending orders as soon as the market is flat.
EnsurePendingOrders(candle);
}
else
{
// Manage the active position and close it when required.
ManageOpenPosition(candle);
}
// Keep the previous candle body for stabilization checks on the next bar.
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
}
private decimal _entryPrice;
private void EnsurePendingOrders(ICandleMessage candle)
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
var indent = IndentTicks * _tickSize;
var buyPrice = candle.ClosePrice + indent;
var sellPrice = candle.ClosePrice - indent;
// Simulate stop-order breakout: if high breaches buy level, go long
if (candle.HighPrice >= buyPrice && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(OrderVolume);
_entryPrice = buyPrice;
}
// if low breaches sell level, go short
else if (candle.LowPrice <= sellPrice && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(OrderVolume);
_entryPrice = sellPrice;
}
}
private void UpdatePendingOrdersLifetime(ICandleMessage candle)
{
// No pending orders in simplified version - nothing to expire.
}
private void ManageOpenPosition(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice == 0)
return;
var priceDiff = Position > 0 ? candle.ClosePrice - entryPrice : entryPrice - candle.ClosePrice;
var prevBodySize = Math.Abs(_prevClose - _prevOpen);
// Exit if profitable and market consolidating, or if loss exceeds threshold
var exitByProfit = priceDiff > 0 && prevBodySize < candle.ClosePrice * 0.001m;
var exitByLoss = priceDiff < -candle.ClosePrice * 0.005m;
if (Position > 0 && (exitByProfit || exitByLoss))
{
SellMarket();
}
else if (Position < 0 && (exitByProfit || exitByLoss))
{
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class autotrade_pending_stops_strategy(Strategy):
def __init__(self):
super(autotrade_pending_stops_strategy, self).__init__()
self._indent_ticks = self.Param("IndentTicks", 200)
self._min_profit = self.Param("MinProfit", 2.0)
self._expiration_minutes = self.Param("ExpirationMinutes", 41)
self._absolute_fixation = self.Param("AbsoluteFixation", 43.0)
self._stabilization_ticks = self.Param("StabilizationTicks", 25)
self._order_volume = self.Param("OrderVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._tick_size = 1.0
self._entry_price = 0.0
@property
def IndentTicks(self):
return self._indent_ticks.Value
@IndentTicks.setter
def IndentTicks(self, value):
self._indent_ticks.Value = value
@property
def MinProfit(self):
return self._min_profit.Value
@MinProfit.setter
def MinProfit(self, value):
self._min_profit.Value = value
@property
def ExpirationMinutes(self):
return self._expiration_minutes.Value
@ExpirationMinutes.setter
def ExpirationMinutes(self, value):
self._expiration_minutes.Value = value
@property
def AbsoluteFixation(self):
return self._absolute_fixation.Value
@AbsoluteFixation.setter
def AbsoluteFixation(self, value):
self._absolute_fixation.Value = value
@property
def StabilizationTicks(self):
return self._stabilization_ticks.Value
@StabilizationTicks.setter
def StabilizationTicks(self, value):
self._stabilization_ticks.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(autotrade_pending_stops_strategy, self).OnStarted2(time)
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._entry_price = 0.0
self.Volume = self._order_volume.Value
self._tick_size = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self._tick_size <= 0.0:
self._tick_size = 1.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
if not self._has_prev_candle:
self._prev_open = open_price
self._prev_close = close
self._has_prev_candle = True
self._ensure_pending_orders(candle)
return
if self.Position == 0:
self._ensure_pending_orders(candle)
else:
self._manage_open_position(candle)
self._prev_open = open_price
self._prev_close = close
def _ensure_pending_orders(self, candle):
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
order_vol = float(self._order_volume.Value)
indent = int(self._indent_ticks.Value) * self._tick_size
buy_price = close + indent
sell_price = close - indent
pos = float(self.Position)
if high >= buy_price and pos <= 0:
if pos < 0:
self.BuyMarket(abs(pos))
self.BuyMarket(order_vol)
self._entry_price = buy_price
elif low <= sell_price and pos >= 0:
if pos > 0:
self.SellMarket(abs(pos))
self.SellMarket(order_vol)
self._entry_price = sell_price
def _manage_open_position(self, candle):
if self._entry_price == 0.0:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
price_diff = close - self._entry_price
else:
price_diff = self._entry_price - close
prev_body_size = abs(self._prev_close - self._prev_open)
exit_by_profit = price_diff > 0.0 and prev_body_size < close * 0.001
exit_by_loss = price_diff < -close * 0.005
if self.Position > 0 and (exit_by_profit or exit_by_loss):
self.SellMarket()
elif self.Position < 0 and (exit_by_profit or exit_by_loss):
self.BuyMarket()
def OnReseted(self):
super(autotrade_pending_stops_strategy, self).OnReseted()
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._tick_size = 1.0
self._entry_price = 0.0
def CreateClone(self):
return autotrade_pending_stops_strategy()