The Backtesting Trade Assistant Panel Strategy is a manual helper converted from the MetaTrader 4 expert advisor Backtesting Trade Assistant Panel V1.10. The original script created a graphical control panel inside the tester that let the operator change lot size, stop-loss and take-profit distances, and instantly submit BUY or SELL market orders. The StockSharp port offers the same workflow inside a strategy component by exposing strongly typed parameters and public helper methods instead of on-chart widgets.
Key capabilities:
Maintain configurable order volume together with MetaTrader-style stop-loss and take-profit distances (measured in “points”).
Issue long or short market orders on demand through the ManualBuy() and ManualSell() helpers.
Automatically attach stop-loss and take-profit offsets after each manual entry using the converted point values.
Provide utility methods that update the trading volume and risk distances at runtime, mimicking the editable text fields of the MT4 panel.
Parameters
Name
Description
Default
OrderVolume
Volume in lots applied to manual market orders. Changing the value also updates the base Strategy.Volume.
0.1
StopLossPips
Distance from the fill price to the protective stop, expressed in MetaTrader points. Set to 0 to disable automatic stop-loss placement.
50
TakeProfitPips
Distance from the fill price to the profit target, expressed in MetaTrader points. Set to 0 to disable automatic take-profit placement.
100
MagicNumber
Identifier preserved from the original EA for bookkeeping. It is not used directly by StockSharp execution logic but can be referenced in custom extensions.
99
Manual operations
The original EA relied on clickable buttons. In StockSharp the same actions are available as public methods:
SetOrderVolume(decimal volume) – synchronizes the OrderVolume parameter and the internal Strategy.Volume value.
SetStopLoss(decimal pips) / SetTakeProfit(decimal pips) – adjust the protective distances while the strategy is running. Values are interpreted in MetaTrader points, exactly like the MT4 text boxes.
ManualBuy() – submits a market buy order using the current volume. After execution the strategy converts the configured stop-loss and take-profit points into price offsets (using symbol metadata) and calls SetStopLoss/SetTakeProfit to register protective orders for the resulting net position.
ManualSell() – symmetric helper for market sell orders.
CloseAllPositions() – closes the entire exposure at market price. This mirrors the workflow where the tester could flatten positions manually.
All protective distances are converted with the same pip-size heuristic as in MT4: five- and three-digit symbols multiply PriceStep by ten to obtain a single “point”, while other symbols rely on the raw PriceStep. If market data does not provide the necessary metadata, a fallback size of 0.0001 is used to preserve consistent behaviour.
Behavioural notes
The strategy subscribes to Level1 updates to keep track of the best bid/ask. When those prices are unavailable it falls back to the last trade price before attaching protective offsets.
No automatic trading signals are generated—this module acts strictly as a manual execution assistant just like the MT4 panel.
Because StockSharp manages protective orders natively, there is no need for an explicit magic number. The field is included purely for parity with the source expert advisor.
Stop-loss and take-profit distances can be adjusted at any time before triggering ManualBuy()/ManualSell() to emulate editing the MT4 text fields prior to pressing the buttons.
Differences from the original EA
The MetaTrader user interface is replaced by strategy parameters and method calls. All functionality is available programmatically without rendering chart controls.
Slippage handling from the MT4 OrderSend call (fixed at 50 points) is not reproduced because StockSharp’s BuyMarket/SellMarket helpers do not expose a direct slippage argument. The surrounding environment should manage execution tolerance if required.
Protective orders are created with StockSharp’s high-level SetStopLoss/SetTakeProfit helpers instead of direct OrderSend calls, keeping the implementation consistent with StockSharp conventions.
Usage tips
Configure the desired symbol, portfolio, and connector in StockSharp as usual, then start the strategy.
Adjust OrderVolume, StopLossPips, and TakeProfitPips through the parameter grid or the provided setter methods.
Call ManualBuy() or ManualSell() whenever a discretionary entry is needed. The helper will attach the requested protective orders automatically.
Use CloseAllPositions() to flatten the exposure instantly during backtests or live discretionary trading sessions.
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>
/// Trade assistant strategy with configurable stop-loss and take-profit.
/// Simplified from the backtesting trade assistant panel.
/// </summary>
public class BacktestingTradeAssistantPanelStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _sma;
private decimal _pipSize;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Candle type for signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public BacktestingTradeAssistantPanelStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series for trading signals", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_pipSize = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
_sma = new SimpleMovingAverage { Length = 20 };
SubscribeCandles(CandleType)
.Bind(_sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var price = candle.ClosePrice;
// Check stop-loss and take-profit
if (Position != 0 && _entryPrice > 0m)
{
if (Position > 0)
{
if (_stopPrice.HasValue && price <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPosition();
return;
}
if (_takePrice.HasValue && price >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPosition();
return;
}
}
else if (Position < 0)
{
if (_stopPrice.HasValue && price >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPosition();
return;
}
if (_takePrice.HasValue && price <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPosition();
return;
}
}
}
// Entry: SMA crossover
if (Position == 0)
{
var pip = _pipSize > 0m ? _pipSize : 1m;
if (price > smaValue)
{
BuyMarket();
_entryPrice = price;
_stopPrice = StopLossPips > 0m ? price - StopLossPips * pip : null;
_takePrice = TakeProfitPips > 0m ? price + TakeProfitPips * pip : null;
}
else if (price < smaValue)
{
SellMarket();
_entryPrice = price;
_stopPrice = StopLossPips > 0m ? price + StopLossPips * pip : null;
_takePrice = TakeProfitPips > 0m ? price - TakeProfitPips * pip : null;
}
}
}
private void ResetPosition()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0.0001m;
var decimals = Security?.Decimals ?? 0;
return decimals is 5 or 3 ? step * 10m : step;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class backtesting_trade_assistant_panel_strategy(Strategy):
def __init__(self):
super(backtesting_trade_assistant_panel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._stop_loss_pips = self.Param("StopLossPips", 50.0)
self._take_profit_pips = self.Param("TakeProfitPips", 100.0)
self._sma = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@TakeProfitPips.setter
def TakeProfitPips(self, value):
self._take_profit_pips.Value = value
def OnReseted(self):
super(backtesting_trade_assistant_panel_strategy, self).OnReseted()
self._sma = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def _calculate_pip_size(self):
sec = self.Security
if sec is None:
return 0.0001
step = sec.PriceStep
if step is None or float(step) <= 0:
return 0.0001
step_val = float(step)
decimals = sec.Decimals
if decimals is not None and (int(decimals) == 5 or int(decimals) == 3):
return step_val * 10.0
return step_val
def OnStarted2(self, time):
super(backtesting_trade_assistant_panel_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
self._sma = SimpleMovingAverage()
self._sma.Length = 20
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._process_candle).Start()
def _reset_position(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
price = float(candle.ClosePrice)
sma_val = float(sma_value)
# Check stop-loss and take-profit
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
if self._stop_price is not None and price <= self._stop_price:
self.SellMarket(abs(float(self.Position)))
self._reset_position()
return
if self._take_price is not None and price >= self._take_price:
self.SellMarket(abs(float(self.Position)))
self._reset_position()
return
elif self.Position < 0:
if self._stop_price is not None and price >= self._stop_price:
self.BuyMarket(abs(float(self.Position)))
self._reset_position()
return
if self._take_price is not None and price <= self._take_price:
self.BuyMarket(abs(float(self.Position)))
self._reset_position()
return
# Entry: SMA crossover
if self.Position == 0:
pip = self._pip_size if self._pip_size > 0 else 1.0
sl_pips = float(self.StopLossPips)
tp_pips = float(self.TakeProfitPips)
if price > sma_val:
self.BuyMarket()
self._entry_price = price
self._stop_price = price - sl_pips * pip if sl_pips > 0 else None
self._take_price = price + tp_pips * pip if tp_pips > 0 else None
elif price < sma_val:
self.SellMarket()
self._entry_price = price
self._stop_price = price + sl_pips * pip if sl_pips > 0 else None
self._take_price = price - tp_pips * pip if tp_pips > 0 else None
def CreateClone(self):
return backtesting_trade_assistant_panel_strategy()