Documentación

Aplicaciones

Última actualización el 18. 5. 2022 by Mark Fric

Ejecución de optimizaciones mediante programación - actualización

Este ejemplo mostrará cómo ejecutar optimizaciones mediante programación llamando al motor de optimización SQ. El ejemplo se realiza en forma de fragmento de análisis personalizado.

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.

 

El código fuente del snippet está ampliamente comentado, describiremos las partes más importantes del mismo en el artículo. La optimización se implementa en el método de análisis personalizado procesarBancoDeDatos()donde realiza una optimización personalizada de la primera estrategia del banco de datos.

 

 

Implementación del método processDatabank() CA snippet

Este método simplemente obtiene la primera estrategia del banco de datos y llama al método performOptimization() es donde se realiza la optimización.

También obtendrá la base de datos de destino, que es donde se almacena el resultado de la optimización.
Depende de ti lo que harías con el resultado de la optimización, no necesitas almacenarlo en ningún sitio, puedes evaluar las métricas de optimización y aceptar/rechazar la estrategia original.

 

public ArrayList processDatabank(String project, String task, String databankName, ArrayList databankRG) throws Exception {
    if(databankRG.size() == 0) {
        return databankRG;
    }

    // obtener el banco de datos de destino para guardar el resultado final
    Databank targetDatabank = ProjectEngine.get(project).getDatabanks().get("Results");

    // obtener la primera estrategia del banco de datos para optimizarla
    ResultsGroup strategy = databankRG.get(0);

    // optimizar esta estrategia
    performOptimization(estrategia, bancoDeDatosObjetivo);

    return databankRG;
}

 

El núcleo - uso de OptimizationEngine en el método performOptimization()

Hay bastantes ajustes que tienen que ser preparados para ejecutar la optimización en la estrategia. Se desglosan en sus propios métodos que se explican más adelante.
La configuración completa es preparada y devuelta por el prepararConfiguración() método.

SettingsMap settings = prepareSettings(strategyToOptimize, dontSaveOriginalStr);

Cuando tenemos la configuración, el siguiente paso es utilizar el objeto SQ OptimizationEngine para realizar la optimización real.

OptimizationEngine engine = new OptimizationEngine("CustomCAOptimization", progressEngine, null, "OptimTask", null);

// se inicializa con la configuración preparada anteriormente
engine.initialize(configuración);

// establece el listener que será llamado cuando el resultado(s) de la optimización esté listo
engine.setNewResultListener(new INewResultListener() {
    public void newResult(ResultsGroup resultsGroup) throws Exception {
        processResult(resultsGroup, strategyToOptimize.getName(), targetDatabank, dontSaveOriginalStr);
    }
});

// establecer otros listeners que pueden ser ignorados por ahora.
// Puedes usar estos listeners para seguir el progreso de la optimización y los mensajes
engine.setLogListener(new IEngineLogListener() {
    public void processMessage(String mensaje) {
        Log.debug(mensaje);
    }
});
engine.setStepsListener(new IStepsListener() {
    @Override
    public void paso(int númeroPaso) {
    }
});

// iniciar la optimización - el método terminará cuando la optimización haya finalizado por completo
engine.optimize();

 

En principio, es relativamente sencillo:

  • crear OptimizaciónEngine objeto
  • inicializarlo con la configuración
  • registrar los oyentes según sea necesario
  • llame a optimizar()

El motor utiliza las escuchas para enviarle información sobre el progreso y los registros, así como los resultados finales de la optimización. El método procesarMensaje() utilizado aquí recibirá el resultado y lo almacenará en el banco de datos de destino, se describe más adelante.

 

Preparar la configuración de OptimizationEngine

Compruebe en el código comentado a continuación cómo se preparan todos los ajustes para la optimización. Hay pocos niveles de configuración:

AjustesMapa es lo que devuelve este método, contiene todos sus ajustes - incluyendo cosas como la función de fitness, opciones de negociación, gráfico (símbolo, marco temporal, rangos de fechas) y así sucesivamente.

ParámetrosConfiguración son los ajustes de los rangos y pasos de los parámetros.

OptimizationSettings son ajustes de la propia optimización - se elige el tipo de optimización (simple, WF, WFM) y sus parámetros.

Completo prepararConfiguración() código:

private SettingsMap prepareSettings(ResultsGroup strategyToOptimize, boolean dontSaveOriginalStr) throws Exception {
    try {

        // create parameter settings - configura rangos para parámetros optimizados
        ParametersSettings paramSettings = createParametersSettings(strategyToOptimize);

        // obtener el XML de la estrategia y crear un objeto de estrategia a partir del XML con variables para los parámetros
        Element elStrategyXml = strategyToOptimize.getStrategyXml();
        StrategyBase xmlS = StrategyBase.createXmlStrategy(elStrategyXml);
        xmlS.transformToVariables(paramSettings.symmetry, paramSettings.paramTypes);

        // crear la función fitness y las opciones de negociación utilizadas en la optimización
        IFitnessFunction fitnessFunction = createFitnessFunction();
        TradingOptions tradingOptions = createTradingOptions();

        // aplica la configuración de los parámetros a las variables de la estrategia
        applyParamSettingsToStrategy(paramSettings, xmlS, tradingOptions);

        // crea configuración de optimización - simple, WF? pasos etc. - busca en el método los parámetros correctos
        //OptimizationSettings optimizationSettings = createOptimizationSettingsSimple(paramSettings, dontSaveOriginalStr);
        OptimizationSettings optimizationSettings = createOptimizationSettingsWF(paramSettings, dontSaveOriginalStr);
        //OptimizationSettings optimizationSettings = createOptimizationSettingsWFM(paramSettings, dontSaveOriginalStr);

        // crea el SettingsMap final a devolver
        SettingsMap ajustes = new SettingsMap();

        // configuración estándar de backtesting
        settings.set(SettingsKeys.TestPrecision, Precisions.SelectedTF);
        settings.set(SettingsKeys.StrategyXml, elStrategyXml);
        settings.set(SettingsKeys.StrategyObject, xmlS);
        settings.set(SettingsKeys.StrategyName, strategyToOptimize.getName());
        settings.set(SettingsKeys.FitnessFunction, fitnessFunction);
        settings.set(SettingsKeys.TradingOptions, tradingOptions.getClone());
        settings.set(SettingsKeys.ATM, null);
        settings.set(SettingsKeys.CapitalInicial, 100000d);
        settings.set(SettingsKeys.Deslizamiento, 0d);
        settings.set(SettingsKeys.DistanciaMínima, 0d);

        // preparar la configuración del gráfico para el backtest
        ChartSetup chartSetup = new ChartSetup(
                "Historial",
                "GBPUSD_M1",
                TimeframeManager.TF_H1,
                SQTimeOld.toLong(2008, 1, 1),
                SQTimeOld.toLong(2018, 12, 31),
                3,
                Session.Forex_247);

        chartSetup.setTestPrecision(settings.getInt(SettingsKeys.TestPrecision));
        chartSetup.setBacktestEngine(Engines.MetaTrader4);

        ChartSetups chartSetups = new ChartSetups();
        chartSetups.add((ChartSetup) chartSetup);

        settings.set(SettingsKeys.BacktestChart, chartSetup);
        settings.set(SettingsKeys.ChartSetups, chartSetups);

        //OptimizationSettings jobOptimizationSettings = optimizationSettings.clone();

        settings.set(SettingsKeys.OptimizationSettings, optimizationSettings);
        settings.set(SettingsKeys.DateGenerated, strategyToOptimize.specialValues().getLong(SpecialValues.DateGenerated, -1));

        devolver ajustes;

    } catch(Exception e){
        throw new Exception("Error al preparar la configuración - " + e.getMessage(), e);
    }
}

 

Preparación de ParameterSettings

Este método crea y devuelve configuraciones para rangos de optimización de parámetros - qué parámetros optimizar, cuáles son sus rangos, etd.

Debido a que lo haremos general para ser usado por cualquier estrategia usaremos parámetros recomendados con ajustes automáticos de parámetros.

Debería ser equivalente a configurarlo en la interfaz de usuario:

 

Código completo del createParametersSettings() método:

private ParametersSettings createParametersSettings(ResultsGroup strategyToOptimize) throws Exception {
    ParametersSettings ajustes = new ParametersSettings();

    try {
        settings.paramTypes.set(ParametrizationTypes.ParamTypeRecommended, true);
        settings.symmetry = true;

        settings.params.clear();

        settings.distributionUp = 20;
        settings.distributionDown = 20;
        settings.maxSteps = 20;

        Elemento elStrategyXml = strategyToOptimize.getStrategyXml();

        StrategyBase strategy = StrategyBase.createXmlStrategy(elStrategyXml);
        strategy.transformToVariables(settings.symmetry, settings.paramTypes);

        ArrayList signals = XMLUtil.getNestedElements(elStrategyXml, "signal");

        for(Variable variable : strategy.variables()){
            if(!variable.getName().equals("MagicNumber") && !isSignalVariable(variable.getId(), signals)){
                settings.params.addParam(createParameter(variable, settings.distributionUp, settings.distributionDown, settings.maxSteps));
            }
        }

    } catch(Exception e){
        throw new Exception("No se puede cargar la configuración de parámetros. " + e.getMessage(), e);
    }

    return parámetros;
}

 

Preparación de OptimizationSettings

Este método crea y devuelve configuraciones de optimización - qué tipo de optimización ejecutar, y sus parámetros.

Hay tres ejemplos de este método en el código completo para la optimización Simple, WF y WFM. Usted puede simplemente descomentar el que desea utilizar en prepararConfiguración() método.

La configuración de la optimización es bastante sencilla y comprensible,

Código completo del createOptimizationSettingsWF() método:

private OptimizationSettings createOptimizationSettingsWF(ParametersSettings paramSettings, boolean dontSaveOriginalStr) {
    OptimizationSettings optimizationSettings = new OptimizationSettings("CustomCAOptimization");
    optimizationSettings.type = OptimizationTypes.WalkForward;
    optimizationSettings.optimizationMethod = OptimizationMethods.GeneticOptimization; //OptimizationMethods.BruteForce;
    optimizationSettings.optimizationType = OptimizationConst.WF_TYPE_SIMIS_EXACTOOS;
    optimizationSettings.maxOptimizationBacktests = 1000; // límite máximo de optimizaciones como en UI

    optimizationSettings.parameters = paramSettings.params;
    optimizationSettings.dontSaveOriginalStr = dontSaveOriginalStr;

    optimizationSettings.periodInPercent = true;

    optimizationSettings.param1Start = 20;
    optimizationSettings.param1Stop = 40;
    optimizationSettings.param1Step = 10;

    // param2XXX ajustes no se utilizan, pero deben establecerse correctamente
    optimizationSettings.param2Start = 5;
    optimizationSettings.param2Stop = 20;
    optimizationSettings.param2Step = 5;

    devolver optimizationSettings;
}

 

Procesamiento de los resultados de la optimización con procesarResultado() método

El último paso importante es poner en práctica lo que se quiere hacer con los resultados de la optimización. Como se ha dicho antes, OptimizaciónEngine utiliza el listener Nuevo resultado para enviarnos el resultado de la optimización, creamos un procesarResultado() al que llama este receptor.

Tenga en cuenta que este método puede ser llamado varias veces - el motor primero vuelve a probar la estrategia original y lo devuelve en esta devolución de llamada, a continuación, procede con la optimización añadir devuelve múltiples resultados (haciendo varias llamadas) cuando se utiliza la optimización Simple, o sólo un resultado final WF/WFM para Walk Forward o Walk Forward Matrix optimización.

Depende de nosotros lo que hagamos con estos resultados, en este ejemplo simplemente los guardaremos en el banco de datos de destino.

Hay un manejo especial aquí para (opcionalmente) no guardar el retest de la estrategia original, sólo guardará los nuevos resultados de la optimización.

Código completo del procesarResultado() método:

protected void processResult(ResultsGroup newRG, String originalStrategyName, Databank targetDatabank, boolean dontSaveOriginalStr) {
    try {
        if(SQProject.isMemoryProtectionUsed()) {
            MemoryUsageChecker.checkAvailableMemory();
        }

        if(newRG.getOptimizationProfile()!=null) {
            //newRG.getOptimizationProfile().reset();
        }

        if(newRG.getName().equals(originalStrategyName)){
            // se trata de una nueva prueba de la estrategia original
            if(!dontSaveOriginalStr) {
                targetDatabank.add(newRG, true);
            }

            return;
        }

        newRG.removeUnsavableSettings();
        //newRG.setLastSettings(lastSettingsXml);
        //newRG.computeEquityChartData();

        //Log.info("Guardando RG: {}", newRG.getName());
        targetDatabank.add(newRG, true);

    } catch(Exception e) {
        String message = "Error al procesar la estrategia '" + newRG.getName() + "'";
        if(newRG != null) {
            newRG.clear();
        }
        Log.error(message, e);
    } catch (OutOfMemoryError e) {
        if(newRG != null) {
            newRG.clear();
        }
    }
}

 

Clase de función de fitness personalizada

Los ajustes de la función de aptitud deben crearse en el prepararConfiguración() método. Hay una especialidad sobre el método de fitness - no estaba listo para ser utilizado como este, así que tuvimos que crear una clase especial FitnessFromStrategyResultForCA

que ponemos a la SQ.Utils paquete. Fue creado donly porque no somos capaces de utilizar las funciones de fitness incorporadas, y es una copia de la función de fitness por defecto de una métrica de SQ.

Código del FitnessFromStrategyResultForCA clase:

package SQ.Utils;

import com.strategyquant.lib.L;
import com.strategyquant.lib.XMLUtil;
import com.strategyquant.lib.snippets.NonexistingCustomClassException;
import com.strategyquant.tradinglib.*;
import com.strategyquant.tradinglib.databank.DatabankColumns;
import com.strategyquant.tradinglib.fitnessfunction.IFitnessFunction;
import com.strategyquant.tradinglib.results.stats.StatsComputer;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;

public class FitnessFromStrategyResultForCA implements IFitnessFunction {
    public static final Logger Log = LoggerFactory.getLogger(FitnessFromStrategyResultForCA.class);

    private String databankColumnName = null;
    private ArrayList<Goal> weightedGoals = new ArrayList<Goal>();

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

    public FitnessFromStrategyResultForCA() {
    }

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

    public FitnessFromStrategyResultForCA(String databankColumnName) {
        this.databankColumnName = databankColumnName;
    }

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

    @Override
    public double computeFitness(ResultsGroup results, byte direction, byte sampleType) throws Exception {
        return computeFitness(results, direction, sampleType, true);
    }

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

    public double computeFitness(ResultsGroup results, byte direction, byte sampleType, boolean useRatioByTrades) throws Exception {
        if (databankColumnName.equals("Weighted")) {
            double fitness = computeWeightedFitness(results, direction, sampleType, useRatioByTrades);
            if (fitness == 0) {
                return getFitnessValue(results, direction, sampleType, "NetProfit", 0, (byte) 0, useRatioByTrades);
            } else return fitness;
        }

        return getFitnessValue(results, direction, sampleType, databankColumnName, 0, (byte) 0, useRatioByTrades);
    }

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

    private double getFitnessValue(ResultsGroup results, byte direction, byte sampleType, String databankColumnName, double target, byte valueType, boolean useRatioByTrades) throws Exception {
        DatabankColumn c = DatabankColumns.get().findClassByName(databankColumnName);

        double val = c.getNumericValue(results, results.getMainResultKey(), direction, PlTypes.Money, sampleType);

        double fitness = c.transformToFitnessRange(val, target, valueType);

        if (fitness < 0) {
            fitness = 0;
        }

        if (fitness > 1) {
            fitness = 1;
        }

        if (!useRatioByTrades) {
            return fitness;
        }

        // special handling - if number of trades is too small, decrease also fitness
        SQStats stats = results.mainResult().statsOrNull(Directions.Both, PlTypes.Money, sampleType);
        int trades = 0;
        if (stats != null) {
            trades = stats.getInt(StatsKey.NUMBER_OF_TRADES);
        }

        if (trades < 20) {
            fitness *= 0.3;
        } else if (trades < 30) {
            fitness *= 0.4;
        } else if (trades < 50) {
            fitness *= 0.6;
        } else if (trades < 70) {
            fitness *= 0.8;
        } else if (trades < 100) {
            fitness *= 0.85;
        } else if (trades < 150) {
            fitness *= 0.9;
        }

        return fitness;
    }

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

    private double computeWeightedFitness(ResultsGroup results, byte direction, byte sampleType, boolean useRatioByTrades) throws Exception {
        // first compute total weight
        float totalWeight = 0;
        for (int i = 0; i < weightedGoals.size(); i++) {
            if (weightedGoals.get(i).use) {
                totalWeight += weightedGoals.get(i).weight;
            }
        }

        // now compute weighted fitness
        float weightedFitness = 0;
        for (int i = 0; i < weightedGoals.size(); i++) {
            Goal goal = weightedGoals.get(i);

            if (goal.use) {
                double rankingFitness = getFitnessValue(results, direction, sampleType, goal.statsValueName, goal.target, goal.valueType, useRatioByTrades);

                weightedFitness += (goal.weight / totalWeight) * rankingFitness;
            }
        }

        return weightedFitness;
    }

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

    @Override
    public void initFitnessFromXml(Element elSettings) {
        weightedGoals.clear();

        Element elRanking = elSettings.getChild("Ranking");
        this.databankColumnName = elRanking.getAttributeValue("type");

        for (Element elGoal : elRanking.getChildren("Goal")) {
            Goal goal = new Goal();
            String use = elGoal.getAttributeValue("use");
            goal.use = (use.equals("true") || use.equalsIgnoreCase("1") ? true : false);
            goal.statsValueName = elGoal.getAttributeValue("type");
            goal.weight = XMLUtil.getDoubleAttr(elGoal, "weight", 1);
            goal.valueType = XMLUtil.getByteAttr(elGoal, "valueType", (byte) 0);
            goal.target = XMLUtil.getDoubleAttr(elGoal, "target", 0);

            try {
                if (goal.use) {
                    DatabankColumn c = DatabankColumns.get().findClassByName(goal.statsValueName);

                    weightedGoals.add(goal);
                }
            } catch (Exception e) {
                Log.error("Ranking Criterium SKIPPED. Reason: ", e);
            }
        }
    }

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

    @Override
    public String getProduct() {
        return "SQUANT";
    }

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

    @Override
    public int getPreferredPosition() {
        return 0;
    }

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

    @Override
    public void initPlugin() throws Exception {
    }

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

    private class Goal {
        public byte valueType;
        public boolean use;
        public String statsValueName;
        public double weight;
        public double target;
    }

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

    @Override
    public byte getFitnessType() throws Exception {
        if (this.databankColumnName.equals("Weighted")) return ValueTypes.Maximize;
        else {
            return DatabankColumns.get().findClassByName(databankColumnName).valueType;
        }
    }

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

    @Override
    public String getFitnessKey() {
        return ComputeFromStrategyResult;
    }

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

    @Override
    public String getFitnessName() {
        return L.tsq("Main data backtest");
    }

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

    @Override
    public String getFitnessDatabankColumnName() {
        return databankColumnName;
    }

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

    @Override
    public ArrayList<DatabankColumn> getUsedStatValues() throws NonexistingCustomClassException {
        ArrayList<DatabankColumn> columnsUsed = new ArrayList<DatabankColumn>();

        // get list of all stat values (columns) used for computing fitness
        if (!databankColumnName.equals("Weighted")) {
            columnsUsed.add(DatabankColumns.get().findClassByName(databankColumnName));

        } else {
            for (int i = 0; i < weightedGoals.size(); i++) {
                Goal goal = weightedGoals.get(i);

                if (goal.use) {
                    columnsUsed.add(DatabankColumns.get().findClassByName(goal.statsValueName));
                }
            }
        }


        return StatsComputer.getAllDependentStatValues(columnsUsed);
    }

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

    @Override
    public String printWeightedGoals() {
        String goals = "";

        for (int i = 0; i < weightedGoals.size(); i++) {
            Goal goal = weightedGoals.get(i);

            if (goal.use) {
                goals += weightedGoals.get(i).statsValueName;

                if (i < weightedGoals.size() - 1) goals += ", ";
            }
        }

        return goals;
    }
}

 

 

Ejecución de este fragmento de análisis personalizado en SQ

A continuación puedes ver el resultado de nuestro snippet. Para el propósito de este ejemplo he creado un nuevo proyecto personalizado con una CustomAnalysis tarea. Cuando la ejecute, obtendrá la primera (y única) estrategia del banco de datos y realizará una optimización WF sobre ella.

La optimización puede tardar algún tiempo, dependiendo de su configuración, y el resultado se guarda en el mismo banco de datos.

Optimización mediante programación en SQ

 

 

Código completo del fragmento CAOptimizationByProgramming

package SQ.CustomAnalysis;

import SQ.TradingOptions.ExitAtEndOfDay;
import SQ.TradingOptions.ExitOnFriday;
import SQ.TradingOptions.LimitTimeRange;
import SQ.TradingOptions.MinMaxSLPT;
import SQ.Utils.FitnessFromStrategyResultForCA;
import com.strategyquant.datalib.TimeframeManager;
import com.strategyquant.datalib.consts.Precisions;
import com.strategyquant.datalib.session.Session;
import com.strategyquant.lib.SQTime;
import com.strategyquant.lib.SQUtils;
import com.strategyquant.lib.SettingsMap;
import com.strategyquant.lib.XMLUtil;
import com.strategyquant.lib.memory.MemoryUsageChecker;
import com.strategyquant.lib.time.SQTimeOld;
import com.strategyquant.tradinglib.*;
import com.strategyquant.tradinglib.engine.ChartSetups;
import com.strategyquant.tradinglib.fitnessfunction.IFitnessFunction;
import com.strategyquant.tradinglib.optimization.*;
import com.strategyquant.tradinglib.optimization.Parameter;
import com.strategyquant.tradinglib.options.TradingOptions;
import com.strategyquant.tradinglib.project.ProgressEngine;
import com.strategyquant.tradinglib.project.ProjectEngine;
import com.strategyquant.tradinglib.project.SQProject;
import com.strategyquant.tradinglib.results.SpecialValues;
import com.strategyquant.tradinglib.simulator.Engines;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

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

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

    public CAOptimizationByProgramming() {
        super("CAOptimizationByProgramming", TYPE_PROCESS_DATABANK);
    }

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

    @Override
    public boolean filterStrategy(String project, String task, String databankName, ResultsGroup rg) throws Exception {
        return false;
    }


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

    @Override
    public ArrayList<ResultsGroup> processDatabank(String project, String task, String databankName, ArrayList<ResultsGroup> databankRG) throws Exception {
        if(databankRG.size() == 0) {
            return databankRG;
        }

        // get target databank to save the final result
        Databank targetDatabank = ProjectEngine.get(project).getDatabanks().get("Results");

        // get first strategy in databank to perform optimization on it
        ResultsGroup strategy = databankRG.get(0);

        // perform optimization on this strategy
        performOptimization(strategy, targetDatabank);

        return databankRG;
    }

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

    private void performOptimization(ResultsGroup strategyToOptimize, Databank targetDatabank) throws Exception {

        // set to false if you want to save also test of strategy with original parameters
        boolean dontSaveOriginalStr = true;

        // prepare all optimization settings
        // here is where you define type of optimization (simple, WF, WFM), parameters range and such
        SettingsMap settings = prepareSettings(strategyToOptimize, dontSaveOriginalStr);

        // create required progress engine - although we'll not really use it in Custom analysis snippet
        ProgressEngine progressEngine = new ProgressEngine("CAOptimizationByProgramming");


        // ----------- The optimization engine is used to perform the optimization

        //  first OptimizationEngine object is created
        OptimizationEngine engine = new OptimizationEngine("CustomCAOptimization", progressEngine, null, "OptimTask", null);

        // it is initialized with settings prepared above
        engine.initialize(settings);

        // sets listener that will be called when the optimization result(s) is ready
        engine.setNewResultListener(new INewResultListener() {
            public void newResult(ResultsGroup resultsGroup) throws Exception {
                processResult(resultsGroup, strategyToOptimize.getName(), targetDatabank, dontSaveOriginalStr);
            }
        });

        // set other listeners that can be ignored for now.
        // You could use these listeners to track the optimization progress and messages
        engine.setLogListener(new IEngineLogListener() {
            public void processMessage(String message) {
                Log.debug(message);
            }
        });
        engine.setStepsListener(new IStepsListener() {
            @Override
            public void step(int stepNumber) {
            }
        });

        // start the optimization - method will finish after the optimization fully finished
        engine.optimize();
    }

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

    private SettingsMap prepareSettings(ResultsGroup strategyToOptimize, boolean dontSaveOriginalStr) throws Exception {
        try {

            // create parameter settings - configures ranges for optimized parameters
            ParametersSettings paramSettings = createParametersSettings(strategyToOptimize);

            // get strategy XML and create strategy object from XML with variables for parameters
            Element elStrategyXml = strategyToOptimize.getStrategyXml();
            StrategyBase xmlS = StrategyBase.createXmlStrategy(elStrategyXml);
            xmlS.transformToVariables(paramSettings.symmetry, paramSettings.paramTypes);

            // create fitness function and trading options used in optimization
            IFitnessFunction fitnessFunction = createFitnessFunction();
            TradingOptions tradingOptions = createTradingOptions();

            // applies parameter settings to strategy variables
            applyParamSettingsToStrategy(paramSettings, xmlS, tradingOptions);

            // creates optimization setting - simple, WF? steps etc. - look at the method for the correct parameters
            //OptimizationSettings optimizationSettings = createOptimizationSettingsSimple(paramSettings, dontSaveOriginalStr);
            OptimizationSettings optimizationSettings = createOptimizationSettingsWF(paramSettings, dontSaveOriginalStr);
            //OptimizationSettings optimizationSettings = createOptimizationSettingsWFM(paramSettings, dontSaveOriginalStr);

            // creates final SettingsMap to return
            SettingsMap settings = new SettingsMap();

            // standard backtesting settings
            settings.set(SettingsKeys.TestPrecision, Precisions.SelectedTF);
            settings.set(SettingsKeys.StrategyXml, elStrategyXml);
            settings.set(SettingsKeys.StrategyObject, xmlS);
            settings.set(SettingsKeys.StrategyName, strategyToOptimize.getName());
            settings.set(SettingsKeys.FitnessFunction, fitnessFunction);
            settings.set(SettingsKeys.TradingOptions, tradingOptions.getClone());
            settings.set(SettingsKeys.ATM, null);
            settings.set(SettingsKeys.InitialCapital, 100000d);
            settings.set(SettingsKeys.Slippage, 0d);
            settings.set(SettingsKeys.MinimumDistance, 0d);

            // prepare chart setup for backtest
            ChartSetup chartSetup = new ChartSetup(
                    "History",
                    "GBPUSD_M1",
                    TimeframeManager.TF_H1,
                    SQTimeOld.toLong(2008, 1, 1),
                    SQTimeOld.toLong(2018, 12, 31),
                    3,
                    Session.Forex_247);

            chartSetup.setTestPrecision(settings.getInt(SettingsKeys.TestPrecision));
            chartSetup.setBacktestEngine(Engines.MetaTrader4);

            ChartSetups chartSetups = new ChartSetups();
            chartSetups.add((ChartSetup) chartSetup);

            settings.set(SettingsKeys.BacktestChart, chartSetup);
            settings.set(SettingsKeys.ChartSetups, chartSetups);

            //OptimizationSettings jobOptimizationSettings = optimizationSettings.clone();

            settings.set(SettingsKeys.OptimizationSettings, optimizationSettings);
            settings.set(SettingsKeys.DateGenerated, strategyToOptimize.specialValues().getLong(SpecialValues.DateGenerated, -1));

            return settings;

        } catch(Exception e){
            throw new Exception("Error while preparing settings - " + e.getMessage(), e);
        }
    }

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

    /**
     * Optimization settings for Simple optimization
     */
    private OptimizationSettings createOptimizationSettingsSimple(ParametersSettings paramSettings, boolean dontSaveOriginalStr) {
        OptimizationSettings optimizationSettings = new OptimizationSettings("CustomCAOptimization");
        optimizationSettings.type = OptimizationTypes.Simple;
        optimizationSettings.optimizationMethod = OptimizationMethods.BruteForce; // or OptimizationMethods.GeneticOptimization
        optimizationSettings.maxOptimizationBacktests = 1000; // max optimizations limit as in UI

        optimizationSettings.parameters = paramSettings.params;
        optimizationSettings.dontSaveOriginalStr = dontSaveOriginalStr;

        return optimizationSettings;
    }

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

    /**
     * Optimization settings for Walk-Forward optimization
     */
    private OptimizationSettings createOptimizationSettingsWF(ParametersSettings paramSettings, boolean dontSaveOriginalStr) {
        OptimizationSettings optimizationSettings = new OptimizationSettings("CustomCAOptimization");
        optimizationSettings.type = OptimizationTypes.WalkForward;
        optimizationSettings.optimizationMethod = OptimizationMethods.GeneticOptimization; //OptimizationMethods.BruteForce;
        optimizationSettings.optimizationType = OptimizationConst.WF_TYPE_SIMIS_EXACTOOS;
        optimizationSettings.maxOptimizationBacktests = 1000; // max optimizations limit as in UI

        optimizationSettings.parameters = paramSettings.params;
        optimizationSettings.dontSaveOriginalStr = dontSaveOriginalStr;

        optimizationSettings.periodInPercent = true;

        optimizationSettings.param1Start = 20;
        optimizationSettings.param1Stop = 40;
        optimizationSettings.param1Step = 10;

        // param2XXX settings are not used, but must be set properly
        optimizationSettings.param2Start = 5;
        optimizationSettings.param2Stop = 20;
        optimizationSettings.param2Step = 5;

        return optimizationSettings;
    }

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

    /**
     * Optimization settings for Walk-Forward Matrix optimization
     */
    private OptimizationSettings createOptimizationSettingsWFM(ParametersSettings paramSettings, boolean dontSaveOriginalStr) {
        OptimizationSettings optimizationSettings = new OptimizationSettings("CustomCAOptimization");
        optimizationSettings.type = OptimizationTypes.WalkForwardMatrix;
        optimizationSettings.optimizationMethod = OptimizationMethods.BruteForce;
        optimizationSettings.optimizationType = OptimizationConst.WF_TYPE_SIMIS_EXACTOOS;
        optimizationSettings.maxOptimizationBacktests = 1000; // max optimizations limit as in UI

        optimizationSettings.parameters = paramSettings.params;
        optimizationSettings.dontSaveOriginalStr = dontSaveOriginalStr;

        optimizationSettings.periodInPercent = true;
        optimizationSettings.param1Start = 20;
        optimizationSettings.param1Stop = 40;
        optimizationSettings.param1Step = 10;

        optimizationSettings.param2Start = 5;
        optimizationSettings.param2Stop = 20;
        optimizationSettings.param2Step = 5;

        return optimizationSettings;
    }

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

    /**
     * creates settings for parameters ranges optimization.
     * Because we'll make it general we'll use automatic
     * @param strategyToOptimize
     * @return
     * @throws Exception
     */
    private ParametersSettings createParametersSettings(ResultsGroup strategyToOptimize) throws Exception {
        ParametersSettings settings = new ParametersSettings();

        try {
            settings.paramTypes.set(ParametrizationTypes.ParamTypeRecommended, true);
            settings.symmetry = true;

            settings.params.clear();

            settings.distributionUp = 20;
            settings.distributionDown = 20;
            settings.maxSteps = 20;

            Element elStrategyXml = strategyToOptimize.getStrategyXml();

            StrategyBase strategy = StrategyBase.createXmlStrategy(elStrategyXml);
            strategy.transformToVariables(settings.symmetry, settings.paramTypes);

            ArrayList<Element> signals = XMLUtil.getNestedElements(elStrategyXml, "signal");

            for(Variable variable : strategy.variables()){
                if(!variable.getName().equals("MagicNumber") && !isSignalVariable(variable.getId(), signals)){
                    settings.params.addParam(createParameter(variable, settings.distributionUp, settings.distributionDown, settings.maxSteps));
                }
            }

        } catch(Exception e){
            throw new Exception("Cannot load Parameters settings. " + e.getMessage(), e);
        }

        return settings;
    }

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

    /**
     * this method is called from Optimization engine (possibly multiple times) when the new optimization result is ready.
     * It is up to you what to do with the result. We will save it to the provided target databank.
     * @param newRG
     * @param originalStrategyName
     * @param targetDatabank
     * @param dontSaveOriginalStr
     */
    protected void processResult(ResultsGroup newRG, String originalStrategyName, Databank targetDatabank, boolean dontSaveOriginalStr) {
        try {
            if(SQProject.isMemoryProtectionUsed()) {
                MemoryUsageChecker.checkAvailableMemory();
            }

            if(newRG.getOptimizationProfile()!=null) {
                //newRG.getOptimizationProfile().reset();
            }

            if(newRG.getName().equals(originalStrategyName)){
                // it is retest of original strategy
                if(!dontSaveOriginalStr) {
                    targetDatabank.add(newRG, true);
                }

                return;
            }

            newRG.removeUnsavableSettings();
            //newRG.setLastSettings(lastSettingsXml);
            //newRG.computeEquityChartData();

            //Log.info("Saving RG: {}", newRG.getName());
            targetDatabank.add(newRG, true);

        } catch(Exception e) {
            String message = "Error while processing strategy '" + newRG.getName() + "'";
            if(newRG != null) {
                newRG.clear();
            }
            Log.error(message, e);
        } catch (OutOfMemoryError e) {
            if(newRG != null) {
                newRG.clear();
            }
        }
    }

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

    private void applyParamSettingsToStrategy(ParametersSettings paramSettings, StrategyBase xmlS, TradingOptions tradingOptions) throws Exception {
        //apply default parameter values to strategy if manual mode
        if(paramSettings.manualMode) {
            OptimizationParams params = paramSettings.params;

            for(int a=0; a<params.size(); a++) {
                Parameter param = params.get(a);
                Variable var = xmlS.variables().get(param.getName());

                if(var != null) {
                    if(ParametrizationTypes.ParamTypeTradingOptions.equals(var.getParamType())) {
                        String[] toNames = var.getId().split("\\.");
                        Object value = null;

                        if(param.getType() == Variable.TypeBoolean) {
                            value = param.getOriginalValue() == 1;
                        }
                        else if(param.getType() == Variable.TypeTime || param.getType() == Variable.TypeInt) {
                            value = (int) param.getOriginalValue();
                        }
                        else {
                            value = param.getOriginalValue();
                        }

                        for(int i=0; i<tradingOptions.size(); i++) {
                            TradingOption option = tradingOptions.get(i);
                            if(option.getClass().getSimpleName().equals(toNames[0])) {
                                option.setParameterValue(toNames[1], value.toString());
                            }
                        }
                    }
                    else {
                        if(param.getType() == Variable.TypeBoolean) {
                            var.setValue(param.getOriginalValue() == 1);
                        }
                        else if(param.getType() == Variable.TypeTime) {
                            var.setValue((int) param.getOriginalValue());
                        }
                        else {
                            var.setValue(param.getOriginalValue());
                        }
                    }
                }
                else {
                    Log.error("Unable to set original value of parameter " + param.getName() + " - Variable not found");
                }
            }
        }
    }

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

    private boolean isSignalVariable(String varId, ArrayList<Element> signals) {
        if(signals == null || signals.size() == 0) {
            return false;
        }

        for(int i=0; i<signals.size(); i++) {
            Element signal = signals.get(i);

            String variable = signal.getAttributeValue("variable");
            if(variable != null && variable.equals(varId)) {
                return true;
            }
        }

        return false;
    }

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

    public Parameter createParameter(Variable variable, int distributionUp, int distributionDown, int maxSteps) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        String name = variable.getName();
        byte type = variable.getInternalType();

        if(variable.getParamType() == null) variable.setParamType(ParametrizationTypes.ParamTypeOtherParam);

        double startValue = 0;
        double stopValue = 0;
        double stepValue = 0;
        double originalValue = 0;
        String varId = variable.getId();

        if(varId.contains("MinimumSL") || varId.contains("MaximumSL") || varId.contains("MinimumPT") || varId.contains("MaximumPT")) {
            // special handling for Min/MaxSL/PT
            originalValue = variable.getValueAsDouble();
            startValue = (int) 0;
            stopValue = (int) 500;

            stepValue = findStep((int) startValue, (int) stopValue, maxSteps);

        } else {
            if(type == Variable.TypeDouble){
                double value = variable.getValueAsDouble();
                double deltaUp = value / 100 * distributionUp;
                double deltaDown = value / 100 * distributionDown;

                int decimals = 2;

                originalValue = variable.getValueAsDouble();
                startValue = SQUtils.round(value - deltaDown, decimals);
                stopValue = SQUtils.round(value + deltaUp, decimals);
                stepValue = findStep(startValue, stopValue, decimals, maxSteps);
            }
            else if(type == Variable.TypeInt) {
                int varMin = (int) variable.getMin();
                int varMax = (int) variable.getMax();

                if(varMin != Integer.MIN_VALUE && varMax != Integer.MIN_VALUE) {
                    startValue = varMin;
                    stopValue = varMax;
                }
                else {
                    double value = variable.getValueAsDouble();
                    double deltaUp = value / 100 * distributionUp;
                    double deltaDown = value / 100 * distributionDown;

                    originalValue = variable.getValueAsDouble();
                    startValue = (int) (value - deltaDown);
                    stopValue = (int) (value + deltaUp);

                    if(value <= 3 & stopValue <= 4) {
                        stopValue = 6;
                    }
                }

                if(variable.getParamType().equals(ParametrizationTypes.ParamTypePeriod)){
                    startValue = startValue > 2 ? startValue : 2;
                }

                stepValue = findStep((int) startValue, (int) stopValue, maxSteps);
            }
            else if(type == Variable.TypeTime){
                originalValue = variable.getValueAsInt();
                double totalMinutes = SQTime.HHMMToMinutes((int) originalValue);
                int deltaUp = (int)(totalMinutes / 100 * distributionUp);
                int deltaDown = (int)(totalMinutes / 100 * distributionDown);

                startValue = totalMinutes - deltaDown;
                startValue = startValue < 0 ? 0 : startValue;

                stopValue = totalMinutes + deltaUp;
                stopValue = stopValue > 1439 ? 1439 : stopValue;

                stepValue = findStep((int) startValue, (int) stopValue, maxSteps);

                startValue = SQTime.minutesToHHMM((int) startValue);
                stopValue = SQTime.minutesToHHMM((int) stopValue);
                stepValue = SQTime.minutesToHHMM((int) stepValue);
            }
            else if(type == Variable.TypeBoolean){
                boolean value = variable.getValueAsBoolean();
                originalValue = value ? 1 : 0;
                startValue = originalValue;
            }
        }

        return createParameterObject(name, type, true, startValue, stopValue, stepValue, originalValue);
    }

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

    private Parameter createParameterObject(String name, byte type, boolean b, double startValue, double stopValue, double stepValue, double originalValue) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        // this is a Java hack necessary because Parameter constructor is not public in Builds until 136.
        // In Build 136 and above the whole code below can be replaced with:
        // return new Parameter(name, type, true, startValue, stopValue, stepValue, originalValue);

        Constructor<?>[] constructors = Parameter.class.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            constructor.setAccessible(true);
            return (Parameter) constructor.newInstance(name, type, true, startValue, stopValue, stepValue, originalValue);
        }

        throw new InstantiationException("Cannot create Parameter object!");
    }

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

    private int findStep(int start, int stop, int maxSteps){
        double step = ((double) stop - start) / (maxSteps - 1);

        if(Math.abs(step - (int) step) < 0.001){
            return (int) step;
        }
        else {
            if(stop - start > 0){
                step = step < 1 ? 1 : (int) step;
            }
            else {
                step = step > -1 ? -1 : (int) step;
            }
        }

        int steps = getStepsCount(start, stop, step);

        while(steps > maxSteps - 1){
            step += step > 0 ? 1 : -1;
            steps = getStepsCount(start, stop, step);
        }

        return (int) step;
    }

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

    private double findStep(double start, double stop, int decimals, int maxSteps){
        double step = (stop - start) / (maxSteps - 1);
        double roundedStep = SQUtils.round(step, decimals);

        double minDiff = 1.0d / Math.pow(10, decimals);

        if(roundedStep == step){
            return roundedStep;
        }

        if(roundedStep > step){
            step = step > 0 ? roundedStep - minDiff : roundedStep;
        }
        else {
            step = step > 0 ? roundedStep : roundedStep + minDiff;
        }

        int steps = getStepsCount(start, stop, step);

        while(steps > maxSteps - 1){
            step += step >= 0 ? minDiff : -minDiff;
            steps = getStepsCount(start, stop, step);
        }

        return SQUtils.round(step, decimals);
    }

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

    private int getStepsCount(double start, double stop, double step){
        return (int)((stop - start) / step);
    }

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

    private IFitnessFunction createFitnessFunction() {
        return new FitnessFromStrategyResultForCA("NetProfit");
    }

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

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

        ExitAtEndOfDay option = new ExitAtEndOfDay();
        option.ExitAtEndOfDay = true;
        option.EODExitTime = 0;
        options.add(option);

        ExitOnFriday option2 = new ExitOnFriday();
        option2.ExitOnFriday = true;
        option2.FridayExitTime = 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.MinimumSL = 50;
        optionMmSLPT.MaximumSL = 100;
        optionMmSLPT.MinimumPT = 50;
        optionMmSLPT.MaximumPT = 100;
        options.add(optionMmSLPT);

        return options;
    }
}

 

Nota - 29/9/2022 actualización del fragmento FitnessFromStrategyResultyForCA

En la versión SQX Build 136 Dev hemos ampliado el IFitnessFunction con nuevos miembros abstractos, y debido a esto un fragmento de ayuda FitnessFromStrategyResultForCA utilizado en este artículo no funcionará - dependiendo de la versión de SQX que esté utilizando.

Tienes que añadir algunos métodos que faltan al final de este fragmento DESPUÉS del último método printWeightedGoals() para que compile:

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

@Override
public ArrayList getMetricsForFitness() {
    ArrayList métricas = new ArrayList();

    for (int i = 0; i < weightedGoals.size(); i++) {
        Objetivo meta = weightedGoals.get(i);

        if (goal.use) {
            MetricForFitness m = nueva MetricForFitness();

            m.métrica = weightedGoals.get(i).statsValueName;
            m.peso = weightedGoals.get(i).peso;
            m.valueType = weightedGoals.get(i).valueType;
        }
    }

    return métricas;
}

@Override
public double getMetricValue(ResultsGroup results, byte direction, byte sampleType, String databankColumnName) throws Exception {
    return 0;
}

@Override
public IFitnessFunction clone() {
    FitnessFromStrategyResultForCA obj = new FitnessFromStrategyResultForCA();

    obj.databankColumnName = this.databankColumnName;
    obj.weightedGoals = cloneGoals();

    return obj;
}

private ArrayList cloneGoals() {
    ArrayList newWeightedGoals = new ArrayList();

    for(int i=0; i<weightedGoals.size(); i++) {
        Meta g = weightedGoals.get(i);
        Goal gNew = new Goal();
        gNew.valueType = g.valueType;
        gNew.use = g.use;
        gNuevo.statsValueName = g.statsValueName;
        gNuevo.peso = g.peso;
        gNuevo.objetivo = g.objetivo;

        newWeightedGoals.add(gNuevo);
    }
    return newWeightedGoals;
}

 

 

 

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

Suscríbase a
Notificar a
2 Comentarios
Más antiguo
Más reciente Más votados
Feedbacks de Inline
Ver todos los comentarios
Emmanuel
29. 9. 2022 12:47 pm

gracias por esta actualización ! gracias por este ejemplo

coensio
23. 12. 2022 17:56

Este es un gran ejemplo, ¡resuelve muchos problemas y limitaciones del SQX basado en GUI!
¿Puede facilitar más información sobre los siguientes puntos?
- Establecer todas las demás opciones de negociación como BarsBack, Número de operaciones por día, etc..
- Cómo utilizar el modo WFA flotante en lugar del fijo
- Añadir un segundo gráfico
- Filtrado de los resultados de WFM mediante N pasadas en la matriz X*Y (como en el optimizador WFM normal)

Gracias por este ejemplo.

Puestos relacionados