Documentation

Applications

Dernière mise à jour le 21. 6. 2022 par Mark Fric

Filtrer par corrélation - exemple de plugin

Note

  • Cet exemple et la fonctionnalité qu'il requiert ne sont ajoutés qu'à partir de la version 136 Dev 2 de la SQ.
  • La fonctionnalité décrite est réservée aux utilisateurs "avancés" et spécialisés. Elle n'est pas entièrement documentée, il s'agit du premier exemple montrant cette possibilité.

 

Il s'agit d'un exemple sur la façon d'étendre les fonctionnalités de SQX avec votre propre plugin.

StrategyQuant X est conçu comme un système basé sur des plugins, chaque partie du programme est un plugin. Cependant, étendre SQX avec votre propre plugin n'était pas possible jusqu'à présent (Build 136 Dev 2), parce que vous aviez besoin d'accéder au code source complet, et vous n'aviez aucun moyen de compiler votre plugin.

SQX UI est fondamentalement une application web écrite en AngularJS, où le backend est en servlets Java.

Dans cet exemple, nous allons montrer étape par étape comment créer un simple plugin comprenant une interface utilisateur simple.

Le plugin complet est également disponible sous la forme d'un fichier ZIP joint à cet article.

 

Ce plugin implémente une fonctionnalité de filtrage des stratégies par corrélation. Il fonctionne de la manière suivante :

  • le plugin ajoutera un nouveau bouton Filtrer par corrélation à la banque de données
  • Lorsque l'on clique sur le bouton, une fenêtre de dialogue s'affiche, dans laquelle l'utilisateur configure la corrélation maximale autorisée.
  • une fois confirmé, le plugin appellera sa méthode backend et passera en revue toutes les stratégies de la banque de données actuelle en filtrant (supprimant) celles qui ont une corrélation supérieure à Max avec d'autres stratégies de cette banque de données.

 

Crédits - ce plugin est basé sur le code posté par AgentPot sur Discord dans le cadre de notre sessions de codage en directLe code est utilisé avec son accord.

 

Emplacement des plugins de l'utilisateur

Les plugins développés par l'utilisateur doivent être placés dans le dossier {Installation SQ}/utilisateur/extend/Plugins. Vous pouvez télécharger le fichier ZIP de ce plugin et l'extraire dans ce dossier.

Lorsque vous ouvrez CodeEditor, vous devriez y voir le dossier de ce plugin :

 

Ce plugin se compose de trois fichiers :

  • FilterByCorrelationServlet.java - qui met en œuvre la fonctionnalité de filtrage des stratégies par corrélation.
  • module.js - Fichier JavaScript contenant la définition du module dans Angular
  • popup.html - Fichier HTML contenant l'interface utilisateur de ce nouveau plugin

Examinons-les un par un, en commençant par les plus simples.

 

 

Définition de l'interface utilisateur du plugin - popup.html

L'interface utilisateur du plugin est définie dans le fichier popup.html. Il s'agit d'un fichier HTML standard qui s'affiche dans une fenêtre modale.

Il y a des DIV pour décorer la fenêtre, le "formulaire" proprement dit est défini à l'intérieur du DIV modal-body :

<!-- Modal window-->
<div class="modal centered" id="filterByCorrelationButtonModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title" id="myModalLabel" tsq>Filtrer par corrélation</h4>
            </div>
            <div class="modal-body" style="height: auto;">
                Fonctionnalité - toutes les stratégies dont la corrélation est supérieure à Max seront supprimées de la banque de données actuelle.
                <label tsq>Paramètres de filtrage</label>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Période de corrélation :</label>
                        </div>
                        <div class="col-sm-6">
                            <select id="fbc-correlation-period" class="setting-control">    
                                <option value="5" tsq>Heure</option>
                                <option value="10" selected tsq>Jour</option>
                                <option value="20" tsq>Semaine</option>
                                <option value="30" tsq>Mois</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Corrélation maximale :</label>
                        </div>
                        <div class="col-sm-6">
                            <div class="sqn-spinner setting-control">
                                <input type="text" class="sqn-input" id="fbc-max-correlation" min="0" max="1" step="0.01" value="0.5" />
                                <div class="sqn-spinner-btns">
                                    <div class="sqn-spinner-minus" onclick="sqnSpinnerMinus(this)"></div>
                                    <div class="sqn-spinner-plus" onclick="sqnSpinnerPlus(this)"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <a data-dismiss="modal" tsq>Fermer</a>
                <button type="button" class="btn btn-primary" onclick="onFilter()" tsq>Filtre</button>
            </div>
        </div>
    </div>
</div>

<style>
    #filterByCorrelationButtonModal .setting-control {
        width: 100px;
    }
</style>

<script>
    function onFilter(){
        let period = document.getElementById("fbc-correlation-period").value;
        let maxCorrelation = document.getElementById("fbc-max-correlation").value;

        const urlParams = new URLSearchParams({
            projectName: window.parent.filterByCorrelationData.projectName,
            databankName: window.parent.filterByCorrelationData.databankName,
            period,
            maxCorrelation
        });

        CustomPluginController.sendRequest("filterByCorrelation/filter?" + urlParams.toString(), "GET", null, function(response){
            let responseJSON = JSON.parse(response);
            console.error(responseJSON);
            if(responseJSON.error){
                alert(responseJSON.error);
            }
            else {
                alert(responseJSON.success);
                window.parent.hidePopup("#filterByCorrelationButtonModal");
            }
        });
    }
</script>

 

Ce qui est important ici, c'est l'id filterByCorrelationButtonModal qui est utilisé dans la première ligne. C'est important parce que cette fenêtre est ensuite référencée par cet identifiant dans la fonction JavaScript dans module.js.

Lorsque vous développerez votre propre plugin, vous devrez créer votre propre identifiant de la fenêtre contextuelle, et utiliser cet identifiant dans la rubrique module.js - voir plus tard.

 

Une autre spécialité est que nous utilisons onClick() JavaScript pour capturer la soumission du formulaire, et nous utiliserons ensuite un simple JS pour récupérer les valeurs configurées.

Elles sont ensuite envoyées au backend à l'aide d'une méthode spéciale CustomPluginController.sendRequest() qui adresse une demande à /filterByCorrelation/filter où il passe les paramètres du formulaire et où sa valeur de retour est affichée dans une alerte.

 

 

Définition du plugin dans module.js

Le plugin a besoin d'un module.js afin qu'il soit correctement reconnu et intégré dans l'interface utilisateur.

Il s'agit de code JavaScript Angular, mais vous n'avez pas besoin d'avoir des connaissances détaillées d'Angular, vous pouvez utiliser cet exemple de plugin et juste le copier & coller + le modifier si vous implémentez votre propre plugin.

Le code entier de module.js est :

angular.module('app.resultsdatabankactions.filterByCorrelation', ['sqplugin']).config(function(sqPluginProvider, $controllerProvider) {
    
    function callback(projectName, taskName, databankName, selectedStrategies){
        window.parent.filterByCorrelationData = {
            projectName,
            taskName,
            nom de la banque de données
        } ;
        window.parent.showPopup("#filterByCorrelationButtonModal") ;
    }

    let buttonController = new CustomPluginController('app.resultsdatabankactions.filterByCorrelation', $controllerProvider, callback).init() ;

    sqPluginProvider.plugin("ResultsDatabankAction", 100, {
        title : Ltsq("Tools:Filter by correlation"),
        class : 'btn btn-normal btn-default',
        controller : buttonController,
        id : "databank-action-filterbycorrelation"
    }) ;
    
    sqPluginProvider.addPopupWindow("plugins/FilterByCorrelation/popup.html", null, 'SQUANT') ;

}) ;

 

Examinons le code partie par partie.

La première partie consiste à enregistrer notre nouveau plugin en tant que module Angular :

angular.module('app.resultsdatabankactions.filterByCorrelation', ['sqplugin']).config(function(sqPluginProvider, $controllerProvider) {

 

En général, il suffit de lui donner un nom unique, par exemple app.resultsdatabankactions.filterByCorrelation.
Nous utilisons en interne la convention app.resultsdatabankaction.YourUniqueName

 

La deuxième partie consiste à créer un contrôleur de bouton et un rappel qui afficheront la fenêtre de dialogue.

function callback(projectName, taskName, databankName, selectedStrategies){
    window.parent.filterByCorrelationData = {
        projectName,
        taskName,
        nom de la banque de données
    } ;
    window.parent.showPopup("#filterByCorrelationButtonModal") ;
}

let buttonController = new CustomPluginController('app.resultsdatabankactions.filterByCorrelation', $controllerProvider, callback).init() ;

 

L'important ici est d'appeler la méthode window.parent.showPopup() à laquelle vous donnez un identifiant unique de la fenêtre contextuelle qui a été définie précédemment dans popup.html. Comme son nom l'indique, l'appel de cette fonction permet d'afficher la fenêtre contextuelle et notre formulaire,

et le contrôleur de bouton l'invoque lorsque le bouton est cliqué.

 

La troisième partie consiste à enregistrer ce plugin et à définir l'endroit où il sera affiché :

sqPluginProvider.plugin("ResultsDatabankAction", 100, {
    title : Ltsq("Filter by correlation"),
    class : 'btn btn-normal btn-default',
    controller : buttonController,
    id : "databank-action-filterbycorrelation"
}) ;

 

Ici, nous disons qu'il est connecté à ResultsDatabankAction ce qui signifie qu'il sera affiché sous forme de bouton dans la banque de données.

 

Ce qui est intéressant ici, c'est le titre - si vous ne voulez pas qu'il soit affiché comme un bouton séparé, mais comme un autre élément de la liste existante de Outils vous pouvez simplement utiliser le titre :

titre : Ltsq("Tools:Filter by correlation"),

Vous pouvez également voir que nous avons défini un contrôleur de bouton et que nous l'avons défini comme étant notre boutonContrôleur définis précédemment.

 

La dernière partie consiste à enregistrer notre fichier d'interface utilisateur dans le système SQX pugin - cela garantira que le fichier pooup.html sera chargé et fera partie de l'interface utilisateur.

sqPluginProvider.addPopupWindow("plugins/FilterByCorrelation/popup.html", null, 'SQUANT') ;

 

 

Implémentation du backend - FilterByCorrelationServlet.java

Maintenant que nous avons l'interface utilisateur, nous devons mettre en œuvre la fonctionnalité souhaitée sur le backend.

Le backend se présente sous la forme d'un servlet HTTP qui traite les requêtes HTTP provenant du frontend (UI) et exécute la fonctionnalité requise. Si vous vous souvenez, nous avons défini cette requête dans le fichier popup.html, elle utilise une méthode spéciale CustomPluginController.sendRequest() qui adresse une demande à /filterByCorrelation/filter.

Nous devons donc mettre en œuvre un servlet qui traite cette demande et fait ce que nous voulons.

Le servlet doit implémenter IServletPlugin l'interface. La méthode importante ici est getHandler()qui est appelé lorsque le backend reçoit une demande de /filterByCorrelation/.

La demande est ensuite traitée par notre FilterByCorrelation classe interne qui implémente la fonctionnalité.

 

La partie de l'extrait qui traite la demande et lui assigne un gestionnaire est donc la suivante :

@PluginImplementation
public class FilterByCorrelationServlet implements IServletPlugin {

    private static final Logger Log = LoggerFactory.getLogger(FilterByCorrelationServlet.class) ;
    private ServletContextHandler connectionContext ;

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

    @Override
    public String getProduct() {
        return SQConst.CODE_SQ ;
    }

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

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

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

    @Override
    public void initPlugin() throws Exception {
    }

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

    @Override
    public Handler getHandler() {
        if(connectionContext == null){
            connectionContext = new ServletContextHandler(ServletContextHandler.SESSIONS) ;
            connectionContext.setContextPath("/filterByCorrelation/") ;
            connectionContext.addServlet(new ServletHolder(new FilterByCorrelation()),"/*") ;
        }
        return connectionContext ;
    }

 

Classe interne FilterByCorrelation

Il s'agit de la classe qui traite la demande /filterByCorrelation/filter de l'interface utilisateur.

La première partie de cette classe vérifie quelle commande a été appelée - dans notre cas, il n'y a que filtre commande.

class FilterByCorrelation extends HttpJSONServlet {
    @Override
    protected String execute(String command, Map args, String method){
        switch(command) {
            case "filter" : return onFilter(args) ;
            par défaut :
                return apiErrorJSON("Execution failed. Unknown command '"+command+"'.", null) ;
        }
    }

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

    private String onFilter(Map args) {
        JSONObject response = new JSONObject() ;
        try {
            String projectName = tryGetParam(args, "projectName")[0] ;
            String databankName = tryGetParam(args, "databankName")[0] ;
            int period = Integer.parseInt(tryGetParam(args, "period")[0]) ;
            double maxCorrelation = Double.parseDouble(tryGetParam(args, "maxCorrelation")[0]) ;

            int removedCount = filterByCorrelation(projectName, databankName, period, maxCorrelation) ;

            response.put("success", "Stratégies filtrées, "+removedCount+" stratégies supprimées.") ;
            return response.toString() ;
        }
        catch(Exception e){
            Log.error("Error while filtering strategies by correlation", e) ;
            response.put("error", "Filtering failed - " + e.getMessage()) ;
            return response.toString() ;
        }
    }

 

Dès réception de cette commande, il exécute onFilter() qui :

  • obtient des paramètres tels que le nom du projet, le nom de la banque de données, la période et la corrélation maximale à partir des paramètres de la demande
  • effectue le filtrage par corrélation en appelant la méthode filterByCorrelation(projectName, databankName, period, maxCorrelation)
  • il renvoie alors un message indiquant le nombre réel de stratégies supprimées ou un message d'erreur en cas d'erreur.

 

 

Méthode filterByCorrelation()

cette méthode effectue le filtrage proprement dit - elle passe en revue toutes les stratégies de la banque de données réelle, calcule les corrélations entre les stratégies et supprime les stratégies pour lesquelles la corrélation > maxCorrelation.

La corrélation informatique mérite un article et un exemple à elle seule, mais vous pouvez suivre la façon dont elle est mise en œuvre dans le code :

    private int filterByCorrelation(String projectName, String databankName, int period, double maxCorrelation) throws Exception {
        Log.info("Filtrage par corrélation (Projet : " + nom du projet + ", banque de données : " + nom de la banque de données + ")...") ;
        SQProject project = ProjectEngine.get(projectName) ;
        Databank databank = project.getDatabanks().get(databankName) ;
        int removedCount = 0 ;

        // Recherche de stratégies corrélées en utilisant le score de fitness comme liste de classement/priorité
        ArrayList rgSortedCandidates = new ArrayList(databank.getRecords()) ;

        rgSortedCandidates.sort((o1, o2) -> Double.valueOf(o1.getFitness()).compareTo(Double.valueOf(o2.getFitness()))) ;

        ArrayList rgSortedCandidatesRanked = new ArrayList() ;

        for(ResultsGroup rgSC : rgSortedCandidates) {
            rgSortedCandidatesRanked.add(0, rgSC) ;
        }

        ArrayList rgUncorrelated = new ArrayList() ;
        // à faire : plusieurs stratégies peuvent avoir la même aptitude. il faut y remédier en procédant à des tris supplémentaires (c'est-à-dire aptitude > pf > ret/dd ... etc).

        while (rgSortedCandidatesRanked.size() > 0) {
            ResultsGroup focusStrat = rgSortedCandidatesRanked.get(0).clone() ;
            String focusStratName = focusStrat.getName() ;

            Log.info("Stratégie à comparer : {}", focusStratName) ;

            rgUncorrelated.add(focusStrat.clone()) ; // ajoute la meilleure stratégie du pool à la liste non corrélée comme point de départ
            rgSortedCandidatesRanked.remove(0) ;

            Iterator it = rgSortedCandidatesRanked.iterator() ;

            while(it.hasNext()) {
                ResultsGroup subStrat = it.next() ;

                String subStratName = subStrat.getName() ;

                if (subStratName != focusStratName) {
                    CorrelationPeriods corrPeriods = new CorrelationPeriods() ;

                    CorrelationType corrType = CorrelationTypes.getInstance().findClassByName("ProfitLoss") ;
                    CorrelationComputer correlationComputer = new CorrelationComputer() ;

                    // calculer les périodes de corrélation
                    CorrelationPeriods correlationPeriods = precomputePeriodsAP(focusStrat, subStrat, period, corrType) ;
                    double corrValue = SQUtils.round2(correlationComputer.computeCorrelation(false, focusStratName, subStratName, focusStrat.orders(), subStrat.orders(), correlationPeriods)) ;

                    Log.info(" - Correlation of '{}'' to focus strategy : {}", subStratName, corrValue) ;

                    if (corrValue > maxCorrelation) {
                        it.remove() ;
                        removedCount++ ;
                        Log.info(" - Removing strategy " + subStratName + " (correlation " + corrValue + " > " + maxCorrelation + ")...") ;
                        databank.remove(subStratName, true, true, true, true, true, true, null) ;
                    }
                } else {
                    it.remove() ;
                }
            }
        }

        return removedCount ;
    }

    //------------------------------------------------------------------------
    
    private CorrelationPeriods precomputePeriodsAP(ResultsGroup paramResultsGroup1, ResultsGroup paramResultsGroup2, int paramInt, CorrelationType paramCorrelationType) throws Exception {
        CorrelationPeriods correlationPeriods = new CorrelationPeriods() ;
        TimePeriod timePeriod = CorrelationLib.getPeriod(paramResultsGroup1.orders()) ;
        long l1 = timePeriod.from ;
        long l2 = timePeriod.to ;
        timePeriod = CorrelationLib.getPeriod(paramResultsGroup2.orders()) ;
        if (timePeriod.from  l2) {
            l2 = timePeriod.to ;
        }

        Périodes temporelles timePeriods1 = CorrelationLib.generatePeriods(paramInt, l1, l2) ;
        Périodes temporelles timePeriods2 = timePeriods1.clone() ;

        paramCorrelationType.computePeriods(paramResultsGroup1.orders(), paramInt, timePeriods1) ;
        correlationPeriods.put(paramResultsGroup1.getName(), timePeriods1) ;

        paramCorrelationType.computePeriods(paramResultsGroup2.orders(), paramInt, timePeriods2) ;
        correlationPeriods.put(paramResultsGroup2.getName(), timePeriods2) ;

        return correlationPeriods ;
    }        
}

 

 

Travailler avec des plugins dans l'éditeur de code

Nous avons ajouté une nouvelle fonctionnalité pour travailler avec des plugins définis par l'utilisateur dans SQX Build 136 Dev 2.

Si vous avez décompressé le plugin ci-joint dans le bon dossier ({installation SQ}/utilisateur/extend/Plugins) et ouvrez l'éditeur de code, vous verrez une nouvelle section Plugins avec notre nouveau plugin :

 

Vous pouvez normalement y créer et éditer des fichiers et des dossiers comme d'habitude. La nouvelle fonctionnalité est la possibilité de compiler le plugin.

Cliquez avec le bouton droit de la souris sur le dossier du plugin et sélectionnez Compiler le plugin :

 

Le plugin sera compilé et un nouveau fichier FilterByCorrelation.jar apparaîtra dans le dossier.

Redémarrez StrategyQuant et vous devriez voir un nouveau bouton de banque de données :

 

Lorsque vous cliquez dessus, la boîte de dialogue ci-dessous s'ouvre :

Configurez les paramètres de filtrage et cliquez sur le bouton Filtrer. Si vous avez quelques stratégies dans la banque de données, vous pouvez remarquer que certaines d'entre elles sont supprimées et qu'une alerte est affichée :

 

Notre plugin a donc fonctionné - il a calculé et comparé les corrélations entre toutes les stratégies de la banque de données actuelle et a supprimé 5 stratégies dont la corrélation était supérieure à la corrélation maximale configurée.

 

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

S'abonner
Notification pour
8 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
Emmanuel
22. 6. 2022 12:53 pm

Excellent ! !!!!!!!!!!!!!!!!!!!!!!!!!!! Merci beaucoup pour cette amélioration. ! !!
Il ouvre la porte à de nouveaux développements !

Emmanuel
24. 6. 2022 1:58 pm

C'est la première fois que nous voyons un exemple de plug in, c'est très utile pour comprendre comment fonctionnent les plug in et les corrélations. Nous avons besoin de plus d'exemples de plug-ins. Avec des exemples, nous sommes en mesure de développer et de partager des plug-ins. Merci pour cet exemple.

binhsir
3. 7. 2022 7:42 am

La déduplication est basée sur la corrélation des stratégies, ce qui est une fonctionnalité intéressante. Je dois dire que l'évolutivité de SQX est impressionnante, même si je ne suis pas un très bon développeur.

Jordanie
22. 9. 2022 2:42 pm

Cher Mark,Thank you, I appreciate it.

Jordanie
22. 9. 2022 2:50 pm

En même temps, j'espère que cette fonction pourra être utilisée comme une tâche disponible dans les projets personnalisés.

tnickel
17. 2. 2023 9:47 pm

J'ai testé ce plugin avec SQ 4.137 Dev2, il fonctionne bien. Je suis content, cette fonctionnalité devrait être dans la prochaine version du SQ.

tnickel
Répondre à  tnickel
26. 4. 2023 2:22 pm

Il serait utile que ce "filtre par corrélation" soit disponible en tant que nouvelle tâche dans un flux de travail en tant que bloc. Tant de choses peuvent être automatisées.

ytu
ytu
Répondre à  tnickel
24. 6. 2023 5:29 am

Une idée fantastique !