Backtesting de la estrategia del panel de asistente comercial
Descripción general
La Estrategia del panel Backtesting Trade Assistant es un asistente manual convertido del MetaTrader 4 asesor experto Backtesting Trade Assistant Panel V1.10. El script original creó un panel de control gráfico dentro del probador que permitía al operador cambiar el tamaño del lote, las distancias de parada de pérdidas y toma de ganancias, y enviar instantáneamente órdenes de mercado de COMPRA o VENTA. El puerto StockSharp ofrece el mismo flujo de trabajo dentro de un componente de estrategia al exponer parámetros fuertemente tipados y métodos de ayuda públicos en lugar de widgets en el gráfico.
Capacidades clave:
Mantenga un volumen de órdenes configurable junto con distancias de stop-loss y take-profit estilo MetaTrader (medidas en “puntos”).
Emita órdenes de mercado largas o cortas a pedido a través de los ayudantes ManualBuy() y ManualSell().
Adjunte automáticamente compensaciones de stop-loss y take-profit después de cada entrada manual utilizando los valores de puntos convertidos.
Proporcione métodos de utilidad que actualicen el volumen de operaciones y las distancias de riesgo en tiempo de ejecución, imitando los campos de texto editables del panel MT4.
Parámetros
Nombre
Descripción
Predeterminado
OrderVolume
Volumen en lotes aplicado a órdenes de mercado manuales. Cambiar el valor también actualiza la base Strategy.Volume.
0.1
StopLossPips
Distancia desde el precio de llenado hasta el tope de protección, expresada en MetaTrader puntos. Establezca en 0 para deshabilitar la colocación automática de stop-loss.
50
TakeProfitPips
Distancia desde el precio de cumplimiento hasta el objetivo de ganancias, expresada en MetaTrader puntos. Configúrelo en 0 para deshabilitar la colocación automática de obtención de ganancias.
100
MagicNumber
Identificador conservado del EA original para contabilidad. No lo utiliza directamente la lógica de ejecución StockSharp, pero se puede hacer referencia a él en extensiones personalizadas.
99
Operaciones manuales
El EA original se basaba en botones en los que se podía hacer clic. En StockSharp las mismas acciones están disponibles como métodos públicos:
SetOrderVolume(decimal volume): sincroniza el parámetro OrderVolume y el valor interno Strategy.Volume.
SetStopLoss(decimal pips) / SetTakeProfit(decimal pips): ajusta las distancias de protección mientras se ejecuta la estrategia. Los valores se interpretan en MetaTrader puntos, exactamente como los cuadros de texto MT4.
ManualBuy(): envía una orden de compra de mercado utilizando el volumen actual. Después de la ejecución, la estrategia convierte los puntos de stop-loss y take-profit configurados en compensaciones de precios (utilizando metadatos de símbolos) y llama a SetStopLoss/SetTakeProfit para registrar órdenes de protección para la posición neta resultante.
ManualSell() – ayudante simétrico para órdenes de venta de mercado.
CloseAllPositions() – cierra toda la exposición al precio de mercado. Esto refleja el flujo de trabajo en el que el evaluador podría aplanar posiciones manualmente.
Todas las distancias de protección se convierten con la misma heurística de tamaño de pip que en MT4: los símbolos de cinco y tres dígitos multiplican PriceStep por diez para obtener un único "punto", mientras que otros símbolos se basan en el PriceStep sin procesar. Si los datos de mercado no proporcionan los metadatos necesarios, se utiliza un tamaño alternativo de 0.0001 para preservar un comportamiento coherente.
Notas de comportamiento
La estrategia se suscribe a actualizaciones de Nivel 1 para realizar un seguimiento de la mejor oferta/demanda. Cuando esos precios no están disponibles, vuelve al último precio comercial antes de aplicar compensaciones protectoras.
No se generan señales comerciales automáticas; este módulo actúa estrictamente como un asistente de ejecución manual al igual que el panel MT4.
Debido a que StockSharp administra las órdenes de protección de forma nativa, no es necesario un número mágico explícito. El campo se incluye únicamente por igualdad con el asesor experto fuente.
Las distancias de stop-loss y take-profit se pueden ajustar en cualquier momento antes de activar ManualBuy()/ManualSell() para emular la edición de los campos de texto MT4 antes de presionar los botones.
Diferencias con el EA original
La interfaz de usuario MetaTrader se reemplaza por parámetros de estrategia y llamadas a métodos. Toda la funcionalidad está disponible mediante programación sin representar controles de gráficos.
El manejo del deslizamiento de la llamada MT4 OrderSend (fijado en 50 puntos) no se reproduce porque los ayudantes BuyMarket/SellMarket de StockSharp no exponen un argumento de deslizamiento directo. El entorno circundante debe gestionar la tolerancia de ejecución si es necesario.
Las órdenes de protección se crean con los ayudantes de alto nivel SetStopLoss/SetTakeProfit de StockSharp en lugar de llamadas directas de OrderSend, lo que mantiene la implementación coherente con las convenciones de StockSharp.
Consejos de uso
Configure el símbolo, la cartera y el conector que desee en StockSharp como de costumbre y luego inicie la estrategia.
Ajuste OrderVolume, StopLossPips y TakeProfitPips a través de la cuadrícula de parámetros o los métodos de configuración proporcionados.
Llame a ManualBuy() o ManualSell() cuando necesite una entrada discrecional. El ayudante adjuntará automáticamente las órdenes de protección solicitadas.
Utilice CloseAllPositions() para reducir la exposición instantáneamente durante pruebas retrospectivas o sesiones de negociación discrecional en 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()