Documentación

Aplicaciones

Última actualización el 22. 2. 2022 por Mark Fric

Ejecución programática de pruebas retrospectivas de estrategias

Este ejemplo mostrará cómo ejecutar backtests de estrategia programáticamente llamando a nuestro motor de backtesting. El ejemplo está hecho en forma de fragmento de análisis personalizado.

El código de backtesting puede incluirse teóricamente en cualquier otro snippet, sólo tiene que darse cuenta de que los backtests son lentos, por lo que definitivamente no se recomienda ejecutar backtests en columnas de bancos de datos y otros snippets similares que tienen que ser rápidos.

Puede descargar el fragmento completo como archivo adjunto en este artículo, su código fuente completo también se publicará al final del artículo.

 

Describiremos las partes más importantes del backtest paso a paso. El backtest en sí se implementa en el método de análisis personalizado procesarBancoDeDatos()donde realiza un backtest personalizado de la primera estrategia del banco de datos.

 

Uso de BacktestEngine para ejecutar backtest

Ejecutar backtest es relativamente sencillo, sólo es necesario inicializar BacktestEngine y luego llamar a su método runBacktest() y después getResults() para obtener el resultado del backtest.

// crear simulador de trading (posteriormente utilizado como motor de backtest)
ITradingSimulator simulator = new MetaTrader4Simulator();

// establecer la precisión de prueba para el simulador
simulator.setTestPrecision(Precisions.getPrecision(Precisions.PRECISION_BASE_TF));

// crear el motor de backtest utilizando el simulador
BacktestEngine backtestEngine = new BacktestEngine(simulator);
backtestEngine.setSingleThreaded(true);

// añadir configuración de backtest al motor
backtestEngine.addSetup(settings);

// ------------------------
// ejecutar backtest - esto ejecutará el backtest real utilizando los ajustes configurados anteriormente
// Dependiendo de la configuración puede tardar un poco.
// Cuando termine, devolverá un nuevo objeto ResultsGroup con el resultado del backtest.
// En caso de error se lanza una Excepción con la descripción del error
ResultsGroup backtestResultRG = backtestEngine.runBacktest().getResults();

 

Primero creamos un simulador de trading de un tipo determinado (MT4, MT5, Tradestation, etc.) y establecemos su precisión de backtest.

Entonces crearemos BacktestEngine utilizando este simulador. Tenemos que añadir ajustes que describan otros parámetros de backtest al motor utilizando addSetup() - los propios ajustes se explicarán más adelante.

Lo último es llamar runBacktest().getResults() para ejecutar el backtest real y obtener sus resultados.

 

Creación de ajustes para el backtest

BacktestEngine debe ser configurado con ajustes - un objeto SettingsMap. Es un mapa de pares clave -> valor, donde se pueden establecer varios parámetros del backtest.

SettingsMap ajustes = new SettingsMap();

// preparar la configuración del gráfico para el backtest - debe especificar el nombre de los datos, rango, etc
ChartSetup chartSetup = new ChartSetup(
        "History", // esto es constante
        "EURUSD_M1", // nombre del símbolo, debe coincidir con el nombre en el Gestor de Datos
        TimeframeManager.TF_H1, // timeframe
        SQTimeOld.toLong(2008, 4, 20), // fecha desde
        SQTimeOld.toLong(2009, 6, 29), // fecha hasta
        3.5, // margen
        Session.Forex_247 // sesión
);
settings.set(SettingsKeys.BacktestChart, chartSetup);


// preparar otros ajustes de backtest - esto pondrá otras partes opcionales/requeridas en el mapa de ajustes
prepareBacktestSettings(settings, strategyToRetest);

Puedes ver que primero creamos el AjustesMapa objeto. A continuación definimos ChartSetup - es una configuración de los datos utilizados para el backtest, y se añade a la configuración llamando a settings.set(SettingsKeys.BacktestChart, chartSetup).

La última llamada es el método prepareBacktestSettings(...) que establecerá el resto de la configuración para mantener el código más simple y legible.

 

Método prepareBacktestSettings()

Es el método que establece otros valores de configuración opcionales y obligatorios para los ajustes de backtest, como la estrategia, la gestión del dinero, el capital inicial, las opciones de negociación, etc.

private void prepareBacktestSettings(SettingsMap settings,ResultsGroup strategyToRetest) throws Exception {
    // crea un objeto de estrategia a partir del XML de estrategia almacenado en el ResultsGroup de origen
    String strategyName = strategyToRetest.getName();
    Elemento elStrategy = strategyToRetest.getStrategyXml();
    if(elStrategy == null) {
        // el resultado no tiene ninguna estrategia, no se puede probar
        throw new Exception("¡El resultado no tiene ninguna estrategia, no se puede probar!");
    }
    StrategyBase strategy = StrategyBase.createXmlStrategy(elStrategy.clone(), strategyName);
    settings.set(SettingsKeys.StrategyObject, strategy);


    settings.set(SettingsKeys.MinimumDistance, 0);

    // establecer el capital inicial y la gestión del dinero
    settings.set(SettingsKeys.CapitalInicial, 100000d);
    settings.set(SettingsKeys.MoneyManagement, MoneyManagementMethodsList.create("FixedSize", 0.1));
    // Nota - puede crear un método MoneyManagemtn diferente especificando su nombre (snippet)
    // y los parámetros en su orden exacto, por ejemplo:
    // MoneyManagementMethodsList.create("RiskFixedPctOfAccount", 5, 100, 0.1, 0.5))


    // crear y establecer las opciones de negociación necesarias
    TradingOptions options = createTradingOptions();
    settings.set(SettingsKeys.TradingOptions, options);
}

El código está comentado y debería ser capaz de entender lo que hace. Hay de nuevo un submétodo separado createTradingOptions() utilizado para mantener el código más organizado. Este método no se explicará aquí, está comentado en el código y devuelve una lista de opciones de negociación que deben aplicarse al backtest.

 

Trabajar con el resultado del backtest

Cuando su backtest haya finalizado con éxito obtendrá un nuevo Grupo de resultados con el resultado del backtest. A continuación, en general, puede hacer dos cosas con él:

  1. Lea sus métricas (número de operaciones, beneficio neto, Sharpe, etc.) para determinar si desea filtrar la estrategia
  2. Guardar el resultado en algún banco de datos o en un archivo

Ambas cosas son bastante sencillas:

// 1. Obtener los valores métricos y compararlos / filtrarlos de alguna manera, ejemplo:
int operaciones = backtestResultRG.portfolio().stats(Directions.Both, PlTypes.Money, SampleTypes.FullSample).getInt(StatsKey.NUMBER_OF_TRADES);
double profit = backtestResultRG.portfolio().stats(Directions.Both, PlTypes.Money, SampleTypes.FullSample).getDouble(StatsKey.NET_PROFIT);
	// ahora haga algo con estos valores
Log.info("Operaciones: {}, beneficio: {}", operaciones, beneficio);


// 2. Puede guardar el nuevo backtest en un banco de datos o archivo
SQProject proyecto = ProjectEngine.get(projectName);
if(proyecto == null) {
    throw new Exception("¡No se puede cargar el proyecto '"+nombredelproyecto+"'!");
}

Banco de datos targetDB = project.getDatabanks().get("Resultados");
if(targetDB == null) {
    throw new Exception("¡No se ha encontrado el banco de datos 'Resultados'!");
}

// añada la nueva estrategia+backtest a este banco de datos y actualice la cuadrícula del banco de datos
targetDB.add(backtestResultRG, true);

 

 

Código fuente completo del fragmento CAStrategyTestByProgramming:

paquete SQ.CustomAnalysis;

import SQ.TradingOptions.*;
import com.strategyquant.datalib.TimeframeManager;
import com.strategyquant.datalib.consts.Precisions;
import com.strategyquant.datalib.session.Session;
import com.strategyquant.lib.SettingsMap;
import com.strategyquant.lib.time.SQTimeOld;
import com.strategyquant.tradinglib.*;
import com.strategyquant.tradinglib.engine.BacktestEngine;
import com.strategyquant.tradinglib.moneymanagement.MoneyManagementMethodsList;
import com.strategyquant.tradinglib.options.TradingOptions;
import com.strategyquant.tradinglib.simulator.Engines;
import com.strategyquant.tradinglib.simulator.ITradingSimulator;
import com.strategyquant.tradinglib.simulator.impl.*;
import com.strategyquant.tradinglib.project.ProjectEngine;
import com.strategyquant.tradinglib.project.SQProject;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;

public class CAStrategyTestByProgramming extends CustomAnalysisMethod {
    public static final Logger Log = LoggerFactory.getLogger("CAStrategyTestByProgramming");

    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------

    public CAStrategyTestPorProgramación() {
        super("CAStrategyTestByProgramming", TYPE_PROCESS_DATABANK);
    }
    
    //------------------------------------------------------------------------
    
    @Override
    public boolean filterStrategy(String projectName, String task, String databankName, ResultsGroup rg) throws Exception {
        return false;
    }
    
    
    //------------------------------------------------------------------------
    
    @Override
    public ArrayList processDatabank(String projectName, String task, String databankName, ArrayList databankRG) throws Exception {
        if(databankRG.size() == 0) {
            return databankRG;
        }

        // obtener la primera estrategia del banco de datos para volver a probarla
        ResultsGroup strategyToRetest = databankRG.get(0);

        // almacenaremos todos los ajustes para el nuevo backtest en este SettingsMap
        SettingsMap settings = new SettingsMap();

        // preparar la configuración del gráfico para el backtest - debe especificar el nombre de los datos, rango, etc
        ChartSetup chartSetup = new ChartSetup(
                "History", // esto es constante
                "EURUSD_M1", // nombre del símbolo, debe coincidir con el nombre en el Gestor de Datos
                TimeframeManager.TF_H1, // timeframe
                SQTimeOld.toLong(2008, 4, 20), // fecha desde
                SQTimeOld.toLong(2009, 6, 29), // fecha hasta
                3.5, // margen
                Session.Forex_247 // sesión
        );
        settings.set(SettingsKeys.BacktestChart, chartSetup);


        // preparar otros ajustes de backtest - esto pondrá otras partes opcionales/requeridas en el mapa de ajustes
        prepareBacktestSettings(settings, strategyToRetest);


        // crear simulador de negociación (utilizado posteriormente para el motor de backtest)
        ITradingSimulator simulator = new MetaTrader4Simulator();
        // simuladores disponibles:
        //MetaTrader4Simulator()
        //MetaTrader5SimulatorHedging(OrderExecutionTypes.EXCHANGE)
        //MetaTrader5SimulatorNetting(OrderExecutionTypes.EXCHANGE)
        //TradestationSimulator()
        //Simulador MultiCharts()
        //JForexSimulator()


        //establecer la precisión de prueba para el simulador
        simulator.setTestPrecision(Precisions.getPrecision(Precisions.PRECISION_BASE_TF));
        // Precisiones disponibles (dependiendo también de los datos - no se puede utilizar la precisión de tick si no se tienen datos de tick):
        //PRECISION_SELECTED_TF = "Sólo marco temporal seleccionado (el más rápido)";
        //PRECISION_BASE_TF = "Simulación de ticks de datos de 1 minuto (lento)";
        //PRECISION_TICK_CUSTOM_SPREADS = "Tick real - spread personalizado (más lento)";
        //PRECISION_TICK_REAL_SPREADS = "Tick real - spread real (más lento)";
        //PRECISION_OPEN_PRICES = "Operar en apertura de barra";


        // crear motor de backtest usando simulador
        BacktestEngine backtestEngine = new BacktestEngine(simulador);
        backtestEngine.setSingleThreaded(true);

        // añadir configuración de backtest al motor
        backtestEngine.addSetup(settings);


        // ------------------------
        // ejecutar backtest - esto ejecutará el backtest real utilizando los ajustes configurados anteriormente
        // Dependiendo de la configuración puede tardar un poco.
        // Cuando termine, devolverá un nuevo objeto ResultsGroup con el resultado del backtest.
        // En caso de error se lanza una Excepción con la descripción del error
        ResultsGroup backtestResultRG = backtestEngine.runBacktest().getResults();

        // obtener los detalles de los resultados del backtest - aquí puede acceder a todos los resultados en ResultsGroup
        // y hacer algo con ellos.


        // Generalmente puedes hacer dos cosas:
        // 1. Obtener los valores métricos y compararlos / filtrarlos de alguna manera, ejemplo:
        int trades = backtestResultRG.portfolio().stats(Directions.Both, PlTypes.Money, SampleTypes.FullSample).getInt(StatsKey.NUMBER_OF_TRADES);
        double profit = backtestResultRG.portfolio().stats(Directions.Both, PlTypes.Money, SampleTypes.FullSample).getDouble(StatsKey.NET_PROFIT);
        // ahora haz algo con estos valores
        // Por ejemplo - puede utilizar el resultado de este backtest personalizado para filtrar esta estrategia en particular
        // del banco de datos, eliminándola de la matriz databankRG
        Log.info("Operaciones: {}, beneficio: {}", operaciones, beneficio);



        // 2. Puede guardar el nuevo backtest en un banco de datos o archivo
        SQProject proyecto = ProjectEngine.get(projectName);
        if(proyecto == null) {
            throw new Exception("¡No se puede cargar el proyecto '"+nombredelproyecto+"'!");
        }

        Banco de datos targetDB = project.getDatabanks().get("Resultados");
        if(targetDB == null) {
            throw new Exception("¡No se ha encontrado el banco de datos 'Resultados'!");
        }

        // añada la nueva estrategia+backtest a este banco de datos y actualice la cuadrícula del banco de datos
        targetDB.add(backtestResultRG, true);


        // el método debe devolver una lista de estrategias originales en el banco de datos que hayan pasado este filtro
        return databankRG;
    }

    //------------------------------------------------------------------------

    private void prepareBacktestSettings(SettingsMap settings,ResultsGroup strategyToRetest) throws Exception {
        // crea un objeto de estrategia a partir del XML de estrategia almacenado en el ResultsGroup de origen
        String strategyName = strategyToRetest.getName();
        Elemento elStrategy = strategyToRetest.getStrategyXml();
        if(elStrategy == null) {
            // el resultado no tiene ninguna estrategia, no se puede probar
            throw new Exception("¡El resultado no tiene ninguna estrategia, no se puede probar!");
        }
        StrategyBase strategy = StrategyBase.createXmlStrategy(elStrategy.clone(), strategyName);
        settings.set(SettingsKeys.StrategyObject, strategy);


        settings.set(SettingsKeys.MinimumDistance, 0);

        // establecer el capital inicial y la gestión del dinero
        settings.set(SettingsKeys.CapitalInicial, 100000d);
        settings.set(SettingsKeys.MoneyManagement, MoneyManagementMethodsList.create("FixedSize", 0.1));
        // Nota - puede crear un método MoneyManagemtn diferente especificando su nombre (snippet)
        // y los parámetros en su orden exacto, por ejemplo:
        // MoneyManagementMethodsList.create("RiskFixedPctOfAccount", 5, 100, 0.1, 0.5))


        // crear y establecer las opciones de negociación necesarias
        TradingOptions options = createTradingOptions();
        settings.set(SettingsKeys.TradingOptions, options);
    }

    //------------------------------------------------------------------------

    private TradingOptions createTradingOptions() {
        TradingOptions opciones = new TradingOptions();

        // todas las opciones de negociación se definen como fragmentos
        // en SQ.TradingOptions.* (visible en CodeEditor)
        // a continuación se muestra un ejemplo de algunas de ellas aplicadas
        ExitAtEndOfDay option = new ExitAtEndOfDay();
        option.ExitAtEndOfDay = true;
        option.EODExitTime = 0;
        options.add(option);

        ExitOnFriday option2 = new ExitOnFriday();
        option2.SalidaViernes = true;
        option2.HoraSalidaViernes = 0;
        options.add(option2);

        LimitTimeRange option3 = new LimitTimeRange();
        option3.LimitTimeRange = true;
        option3.SignalTimeRangeFrom = 700;
        option3.SignalTimeRangeTo = 1900;
        option3.ExitAtEndOfRange = true;
        options.add(option3);

        MinMaxSLPT optionMmSLPT = new MinMaxSLPT();
        optionMmSLPT.LímiteMínimo = 50;
        optionMmSLPT.MaximumSL = 100;
        optionMmSLPT.MínimoPT = 50;
        optionMmSLPT.MaximumPT = 100;
        options.add(optionMmSLPT);

        devolver opciones;
    }
}

 

 

¿Le ha resultado útil este artículo? El artículo era útil El artículo no era útil

Suscríbase a
Notificar a
3 Comentarios
Más antiguo
Más reciente Más votados
Feedbacks de Inline
Ver todos los comentarios
Emmanuel
25. 2. 2022 9:46 am

¡¡¡Excelente !!! ¡¡¡¡Muy buena idea y muy útil !!!! No sabía que podíamos pedir a SQX que ejecutara un backtest desde Java.

Incluso podemos seleccionar el motor, la estrategia # y el período. ¡¡¡¡Voy a utilizarlo !!!!

SQX es realmente increíble

Emmanuel
25. 2. 2022 9:47 am

¡¡¡¡¡Con ejemplos como este, estoy aprendiendo mucho , Gracias equipo SQX !!!!!

Emmanuel
25. 2. 2022 9:55 am

Me aparece un pequeño error "Import failed.Archivo esperado con extensión .sxp” ?
Tengo V 135.368".
Extraño, el archivo tiene la extensión sxp.tengo este error cuando lo importo
¿Qué debo hacer para evitar este error?

Puestos relacionados