Estratégia do painel de assistentes comerciais de backtesting
Visão geral
A Estratégia do Backtesting Trade Assistant Panel é um auxiliar manual convertido do MetaTrader 4 consultor especialista Backtesting Trade Assistant Panel V1.10. O script original criava um painel de controle gráfico dentro do testador que permitia ao operador alterar o tamanho do lote, as distâncias de stop-loss e take-profit e enviar instantaneamente ordens de mercado de COMPRA ou VENDA. A porta StockSharp oferece o mesmo fluxo de trabalho dentro de um componente de estratégia, expondo parâmetros fortemente tipados e métodos auxiliares públicos em vez de widgets no gráfico.
Principais capacidades:
Mantenha o volume de pedidos configurável junto com distâncias de stop-loss e take-profit no estilo MetaTrader (medidas em “pontos”).
Emita ordens de mercado longas ou curtas sob demanda por meio dos ajudantes ManualBuy() e ManualSell().
Anexe automaticamente compensações de stop-loss e take-profit após cada entrada manual usando os valores de pontos convertidos.
Fornece métodos utilitários que atualizam o volume de negociação e as distâncias de risco em tempo de execução, imitando os campos de texto editáveis do painel MT4.
Parâmetros
Nome
Descrição
Padrão
OrderVolume
Volume em lotes aplicado em ordens manuais de mercado. Alterar o valor também atualiza a base Strategy.Volume.
0.1
StopLossPips
Distância do preço de preenchimento até o stop de proteção, expressa em MetaTrader pontos. Defina como 0 para desativar a colocação automática de stop-loss.
50
TakeProfitPips
Distância do preço de preenchimento até a meta de lucro, expressa em MetaTrader pontos. Defina como 0 para desativar a colocação automática de lucro.
100
MagicNumber
Identificador preservado do original EA para escrituração contábil. Não é usado diretamente pela lógica de execução StockSharp, mas pode ser referenciado em extensões personalizadas.
99
Operações manuais
O EA original dependia de botões clicáveis. Em StockSharp as mesmas ações estão disponíveis como métodos públicos:
SetOrderVolume(decimal volume) – sincroniza o parâmetro OrderVolume e o valor interno Strategy.Volume.
SetStopLoss(decimal pips) / SetTakeProfit(decimal pips) – ajuste as distâncias de proteção enquanto a estratégia está em execução. Os valores são interpretados em MetaTrader pontos, exatamente como as caixas de texto MT4.
ManualBuy() – envia uma ordem de compra a mercado usando o volume atual. Após a execução, a estratégia converte os pontos de stop-loss e take-profit configurados em compensações de preço (usando metadados de símbolos) e chama SetStopLoss/SetTakeProfit para registrar ordens de proteção para a posição líquida resultante.
ManualSell() – auxiliar simétrico para ordens de venda a mercado.
CloseAllPositions() – fecha toda a exposição ao preço de mercado. Isso reflete o fluxo de trabalho em que o testador pode nivelar as posições manualmente.
Todas as distâncias de proteção são convertidas com a mesma heurística de tamanho de pip do MT4: símbolos de cinco e três dígitos multiplicam PriceStep por dez para obter um único “ponto”, enquanto outros símbolos dependem do PriceStep bruto. Se os dados de mercado não fornecerem os metadados necessários, um tamanho substituto de 0.0001 será usado para preservar um comportamento consistente.
Notas comportamentais
A estratégia assina atualizações de Nível 1 para acompanhar o melhor lance/venda. Quando esses preços não estão disponíveis, ele volta ao último preço comercial antes de anexar compensações de proteção.
Nenhum sinal de negociação automático é gerado – este módulo atua estritamente como um assistente de execução manual, assim como o painel MT4.
Como StockSharp gerencia ordens de proteção nativamente, não há necessidade de um número mágico explícito. O campo é incluído apenas por questão de paridade com o consultor especialista de origem.
As distâncias de stop-loss e take-profit podem ser ajustadas a qualquer momento antes de acionar ManualBuy()/ManualSell() para emular a edição dos campos de texto MT4 antes de pressionar os botões.
Diferenças do original EA
A interface do usuário MetaTrader é substituída por parâmetros de estratégia e chamadas de método. Todas as funcionalidades estão disponíveis programaticamente sem renderizar controles de gráfico.
O tratamento de deslizamento da chamada MT4 OrderSend (fixado em 50 pontos) não é reproduzido porque os ajudantes BuyMarket/SellMarket de StockSharp não expõem um argumento de deslizamento direto. O ambiente circundante deve gerenciar a tolerância de execução, se necessário.
As ordens de proteção são criadas com ajudantes SetStopLoss/SetTakeProfit de alto nível de StockSharp em vez de chamadas OrderSend diretas, mantendo a implementação consistente com as convenções de StockSharp.
Dicas de uso
Configure o símbolo, portfólio e conector desejados em StockSharp como de costume e, em seguida, inicie a estratégia.
Ajuste OrderVolume, StopLossPips e TakeProfitPips por meio da grade de parâmetros ou dos métodos setter fornecidos.
Ligue para ManualBuy() ou ManualSell() sempre que uma entrada discricionária for necessária. O ajudante anexará automaticamente as ordens de proteção solicitadas.
Use CloseAllPositions() para nivelar a exposição instantaneamente durante backtests ou sessões de negociação discricionárias ao vivo.
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()