Documentation

Applications

Dernière mise à jour le 18. 5. 2022 par Mark Fric

Exécution programmatique des optimisations - mise à jour

Cet exemple montre comment exécuter des optimisations de manière programmatique en appelant le moteur d'optimisation de SQ. L'exemple est présenté sous la forme d'un extrait d'analyse personnalisée.

Vous pouvez télécharger l'extrait complet en pièce jointe à cet article, son code source complet sera également publié à la fin de l'article.

 

Le code source de l'extrait est largement commenté, nous en décrirons les parties les plus importantes dans cet article. L'optimisation est mise en œuvre dans la méthode d'analyse personnalisée processDatabank()où il effectue une optimisation personnalisée de la toute première stratégie de la banque de données.

 

 

Mise en œuvre de la méthode de l'extrait de CA processDatabank()

Cette méthode récupère simplement la première stratégie de la banque de données et appelle la fonction performOptimization() sur elle - c'est là que l'optimisation est effectuée.

Il obtiendra également la banque de données cible - c'est là que le résultat de l'optimisation est stocké.
C'est à vous de décider ce que vous ferez du résultat de l'optimisation, vous n'avez pas besoin de le stocker quelque part, vous pouvez évaluer les mesures d'optimisation et accepter/refuser la stratégie d'origine.

 

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

    // obtenir la banque de données cible pour enregistrer le résultat final
    Databank targetDatabank = ProjectEngine.get(project).getDatabanks().get("Results") ;

    // récupérer la première stratégie dans la banque de données pour l'optimiser
    ResultsGroup strategy = databankRG.get(0) ;

    // effectuer l'optimisation sur cette stratégie
    performOptimization(strategy, targetDatabank) ;

    return databankRG ;
}

 

Le noyau - utilisation de l'OptimizationEngine dans la méthode performOptimization()

Il y a un certain nombre de paramètres qui doivent être préparés afin d'optimiser la stratégie. Ils sont décomposés selon leurs propres méthodes, expliquées plus loin.
L'ensemble est préparé et renvoyé par le prepareSettings() méthode.

SettingsMap settings = prepareSettings(strategyToOptimize, dontSaveOriginalStr) ;

Une fois les paramètres définis, l'étape suivante consiste à utiliser l'objet SQ OptimizationEngine pour effectuer l'optimisation proprement dite.

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

// il est initialisé avec les paramètres préparés ci-dessus
engine.initialize(settings) ;

// définit l'écouteur qui sera appelé lorsque le(s) résultat(s) de l'optimisation sera(ont) prêt(s)
engine.setNewResultListener(new INewResultListener() {
    public void newResult(ResultsGroup resultsGroup) throws Exception {
        processResult(resultsGroup, strategyToOptimize.getName(), targetDatabank, dontSaveOriginalStr) ;
    }
}) ;

// définir d'autres récepteurs qui peuvent être ignorés pour l'instant.
// Vous pouvez utiliser ces écouteurs pour suivre la progression de l'optimisation et les messages
engine.setLogListener(new IEngineLogListener() {
    public void processMessage(String message) {
        Log.debug(message) ;
    }
}) ;
engine.setStepsListener(new IStepsListener() {
    @Override
    public void step(int stepNumber) {
    }
}) ;

// démarre l'optimisation - la méthode se terminera lorsque l'optimisation sera entièrement terminée
engine.optimize() ;

 

Le principe est relativement simple :

  • créer OptimizationEngine objet
  • l'initialiser avec des paramètres
  • enregistrer les auditeurs si nécessaire
  • appel optimiser()

Les récepteurs sont utilisés par le moteur pour vous envoyer des informations sur l'état d'avancement et des journaux, ainsi que les résultats finaux de l'optimisation. La méthode processMessage() utilisé ici recevra le résultat et le stockera dans la banque de données cible, il est décrit plus loin.

 

Préparation des paramètres pour OptimizationEngine

Le code commenté ci-dessous montre comment tous les paramètres d'optimisation sont préparés. Il y a plusieurs niveaux de paramètres :

Carte des paramètres est ce qui est renvoyé par cette méthode, il contient tous ses paramètres - y compris des éléments tels que la fonction de fitness, les options de trading, le graphique (symbole, période, plages de dates) et ainsi de suite.

ParametersSettings sont des réglages de plages et de pas de paramètres.

Paramètres d'optimisation sont des paramètres de l'optimisation elle-même - vous choisissez le type d'optimisation (simple, WF, WFM) et ses paramètres.

Complet prepareSettings() code :

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

        // créer les paramètres - configurer les plages pour les paramètres optimisés
        ParametersSettings paramSettings = createParametersSettings(strategyToOptimize) ;

        // récupérer le XML de la stratégie et créer un objet de stratégie à partir du XML avec des variables pour les paramètres
        Element elStrategyXml = strategyToOptimize.getStrategyXml() ;
        StrategyBase xmlS = StrategyBase.createXmlStrategy(elStrategyXml) ;
        xmlS.transformToVariables(paramSettings.symmetry, paramSettings.paramTypes) ;

        // créer la fonction d'aptitude et les options de négociation utilisées dans l'optimisation
        IFitnessFunction fitnessFunction = createFitnessFunction() ;
        TradingOptions tradingOptions = createTradingOptions() ;

        // applique les paramètres aux variables de la stratégie
        applyParamSettingsToStrategy(paramSettings, xmlS, tradingOptions) ;

        // crée un paramètre d'optimisation - simple, WF ? étapes, etc. - regarder la méthode pour les paramètres corrects
        //OptimizationSettings optimizationSettings = createOptimizationSettingsSimple(paramSettings, dontSaveOriginalStr) ;
        OptimizationSettings optimizationSettings = createOptimizationSettingsWF(paramSettings, dontSaveOriginalStr) ;
        //OptimizationSettings optimizationSettings = createOptimizationSettingsWFM(paramSettings, dontSaveOriginalStr) ;

        // crée un SettingsMap final à renvoyer
        SettingsMap settings = new SettingsMap() ;

        // paramètres de backtesting standard
        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) ;

        // préparer la configuration graphique pour le 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) ;
    }
}

 

Préparation des ParameterSettings

Cette méthode crée et renvoie des paramètres pour les plages d'optimisation des paramètres - quels paramètres optimiser, quelles sont leurs plages, etc.

Parce que nous voulons que le système soit utilisable par n'importe quelle stratégie, nous utiliserons des paramètres recommandés avec des réglages de paramètres automatiques.

Cela devrait être équivalent au réglage dans l'interface utilisateur :

 

Code complet de la createParametersSettings() méthode :

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 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){
        lancez une nouvelle Exception("Cannot load Parameters settings. " + e.getMessage(), e) ;
    }

    retourner les paramètres ;
}

 

Préparation des paramètres d'optimisation

Cette méthode crée et renvoie des paramètres d'optimisation - le type d'optimisation à exécuter et ses paramètres.

Il existe trois exemples de cette méthode dans le code complet pour les optimisations Simple, WF et WFM. Vous pouvez simplement décommenter celle que vous voulez utiliser dans prepareSettings() méthode.

Les paramètres d'optimisation sont relativement simples et compréhensibles,

Code complet de la createOptimizationSettingsWF() méthode :

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 ; // limite maximale d'optimisations comme dans l'interface utilisateur

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

    optimizationSettings.periodInPercent = true ;

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

    // les paramètres2XXX ne sont pas utilisés, mais doivent être réglés correctement
    optimizationSettings.param2Start = 5 ;
    optimizationSettings.param2Stop = 20 ;
    optimizationSettings.param2Step = 5 ;

    return optimizationSettings ;
}

 

Traitement des résultats de l'optimisation avec processResult() méthode

La dernière étape importante consiste à mettre en œuvre ce que vous voulez faire avec les résultats de l'optimisation. Comme nous l'avons déjà dit, OptimizationEngine utilise le New result listener pour nous envoyer le résultat de l'optimisation, nous avons créé un fichier processResult() appelée par cet auditeur.

Notez que cette méthode peut être appelée plusieurs fois - le moteur teste d'abord la stratégie originale et la renvoie dans ce rappel, puis procède à l'optimisation - et renvoie plusieurs résultats (en faisant plusieurs appels) lorsque l'optimisation simple est utilisée, ou un seul résultat WF/WFM final pour l'optimisation de la matrice de marche avant ou de la matrice de marche avant.

C'est à nous de décider ce que nous ferons de ces résultats. Dans cet exemple, nous les enregistrerons simplement dans la banque de données cible.

Il y a une manipulation spéciale ici qui permet (optionnellement) de ne pas sauvegarder le nouveau test de la stratégie originale, mais seulement les nouveaux résultats de l'optimisation.

Code complet de la processResult() méthode :

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)){
            // il s'agit d'un nouveau test de la stratégie originale
            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() ;
        }
    }
}

 

Classe de fonction de fitness personnalisée

Les paramètres de la fonction d'aptitude doivent être créés dans le prepareSettings() . La méthode Fitness présente une particularité : elle n'était pas prête à être utilisée de cette manière. Nous avons donc dû créer une classe spéciale FitnessFromStrategyResultForCA.

que nous soumettons à la SQ.Utils package. Elle a été créée parce que nous ne sommes pas en mesure d'utiliser les fonctions de fitness intégrées, et il s'agit d'une copie de la fonction de fitness unimétrique par défaut de SQ.

Code de la Résultat de la stratégie pour l'Afrique du Sud classe :

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;
    }
}

 

 

Exécution de cet extrait d'analyse personnalisée dans SQ

Vous pouvez voir ci-dessous le résultat de notre snippet. Pour les besoins de cet exemple, j'ai créé un nouveau projet personnalisé avec un seul fichier Analyse personnalisée tâche. Lorsque je l'exécute, il récupère la première (et la seule) stratégie de la banque de données et effectue une optimisation WF sur celle-ci.

L'optimisation peut prendre un certain temps, en fonction de vos paramètres, et le résultat est enregistré dans la même banque de données.

Optimisation par programmation en SQ

 

 

Code complet de l'extrait 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;
    }
}

 

Note - 29/9/2022 mise à jour du snippet FitnessFromStrategyResultForCA

Dans la version SQX Build 136 Dev, nous avons étendu la fonction IFitnessFunction avec de nouveaux membres abstraits, et à cause de cela un snippet d'aide Résultat de la stratégie pour l'Afrique du Sud utilisées dans cet article ne fonctionneront pas - selon la version de SQX que vous utilisez.

Vous devez ajouter quelques méthodes manquantes à la fin de ce snippets APRES la dernière méthode printWeightedGoals() afin de le faire compiler :

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

@Override
public ArrayList getMetricsForFitness() {
    ArrayList metrics = new ArrayList() ;

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

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

            m.metric = weightedGoals.get(i).statsValueName ;
            m.weight = weightedGoals.get(i).weight ;
            m.valueType = weightedGoals.get(i).valueType ;
        }
    }

    return metrics ;
}

@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++) {
        Goal g = weightedGoals.get(i) ;
        Goal gNew = new Goal() ;
        gNew.valueType = g.valueType ;
        gNew.use = g.use ;
        gNew.statsValueName = g.statsValueName ;
        gNew.weight = g.weight ;
        gNew.target = g.target ;

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

 

 

 

Cet article a-t-il été utile ? L'article était utile L'article n'était pas utile

S'abonner
Notification pour
2 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
Emmanuel
29. 9. 2022 12:47 pm

merci pour cette mise à jour ! merci pour cet exemple

coensio
23. 12. 2022 5:56 pm

C'est un excellent exemple, qui résout de nombreux problèmes et limitations de SQX basé sur l'interface graphique !
Pouvez-vous nous donner plus d'informations sur les points suivants :
- Paramétrage de toutes les autres options de trading comme BarsBack, Nombre de trades par jour, etc.
- Comment utiliser le mode flottant de l'AMF au lieu du mode fixe ?
- Ajout d'un deuxième graphique
- Filtrage des résultats WFM en utilisant N passes dans la matrice X*Y (comme dans l'optimiseur WFM normal)

Merci pour cet exemple !

Postes connexes