Documentazione

Applicazioni

Ultimo aggiornamento il 21. 6. 2022 da Mark Fric

Filtro per correlazione - esempio di plugin

Nota

  • Questo esempio e la funzionalità che richiede sono aggiunti solo a SQ Build 136 Dev 2 o successive.
  • La funzionalità descritta è riservata agli utenti "avanzati" e dedicati. Non è completamente documentata, questo è il primo esempio che mostra questa possibilità.

 

Questo è un esempio di come estendere le funzionalità di SQX con un proprio plugin.

StrategyQuant X è un sistema basato su plugin, ogni parte del programma è un plugin. Tuttavia, estendere SQX con un proprio plugin non era possibile fino ad ora (Build 136 Dev 2), perché era necessario accedere al codice sorgente completo e non c'era modo di compilare il proprio plugin.

SQX UI è fondamentalmente un'applicazione web scritta in AngularJS, il cui backend è costituito da servlet Java.

In questo esempio mostreremo passo dopo passo come creare un semplice plugin con una semplice interfaccia utente.

Il plugin completo è disponibile anche come file ZIP allegato a questo articolo.

 

Questo plugin implementa una funzionalità di filtraggio delle strategie per correlazione. Funziona in questo modo:

  • plugin che aggiungerà un nuovo pulsante Filtrare per correlazione alla banca dati
  • Facendo clic sul pulsante, viene visualizzata una finestra di dialogo a comparsa in cui l'utente configura la correlazione massima consentita.
  • Una volta confermato, il plugin chiamerà il suo metodo backend e passerà in rassegna tutte le strategie della banca dati attuale filtrando (rimuovendo) quelle che hanno una correlazione superiore a Max con altre strategie della banca dati.

 

Crediti - questo plugin è basato sul codice pubblicato da AgentePot su Discord come parte del nostro sessioni di codifica dal vivoIl codice viene utilizzato con il suo consenso.

 

Posizione dei plugin dell'utente

I plugin sviluppati dall'utente devono essere collocati nella cartella {installazione SQ}/user/extend/Plugins. Potete scaricare il file ZIP di questo plugin ed estrarlo in questa cartella.

Quando si apre CodeEditor, si dovrebbe vedere la cartella di questo plugin:

 

Questo plugin è composto da tre file:

  • FilterByCorrelationServlet.java - file java che implementa la funzionalità di filtraggio delle strategie per correlazione.
  • modulo.js - File JavaScript contenente la definizione del modulo in Angular
  • popup.html - File HTML che contiene l'interfaccia utente di questo nuovo plugin.

Esaminiamoli uno per uno, partendo dal più semplice.

 

 

Definizione dell'interfaccia utente del plugin - popup.html

L'interfaccia utente del plugin è definita nel file popup.html. Si tratta di un file HTML standard che viene visualizzato in una finestra modale.

Ci sono dei DIV per decorare la finestra, il "form" vero e proprio è definito all'interno del 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>Filtrare per correlazione</h4>
            </div>
            <div class="modal-body" style="height: auto;">
                Funzionalità - tutte le strategie che hanno una correlazione superiore a Max saranno rimosse dalla banca dati corrente.
                <label tsq>Impostazioni di filtraggio</label>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Periodo di correlazione:</label>
                        </div>
                        <div class="col-sm-6">
                            <select id="fbc-correlation-period" class="setting-control">    
                                <option value="5" tsq>Ora</option>
                                <option value="10" selected tsq>Giorno</option>
                                <option value="20" tsq>Settimana</option>
                                <option value="30" tsq>Mese</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Correlazione massima:</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>Chiudere</a>
                <button type="button" class="btn btn-primary" onclick="onFilter()" tsq>Filtro</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>

 

L'elemento importante è l'id pulsante filtroCorrelazioneModale che viene utilizzato nella prima riga. È importante perché questo popup viene poi referenziato da questo id nella funzione JavaScript in module.js.

Quando si sviluppa il proprio plugin, è necessario creare un proprio ID univoco del popup e utilizzare questo id in modulo.js - vedere più avanti.

 

Un'altra specialità è quella di utilizzare onClick() per catturare l'invio del modulo e poi useremo un semplice JS per recuperare i valori configurati.

Vengono quindi inviati al backend utilizzando un metodo speciale CustomPluginController.sendRequest() che fa una richiesta a /filtroPerCorrelazione/Filtro in cui passa i parametri del modulo e il suo valore di ritorno viene visualizzato in un avviso.

 

 

Definizione del plugin in module.js

Il plugin ha bisogno di uno speciale modulo.js in modo che venga riconosciuto correttamente e incorporato nell'interfaccia utente.

Si tratta di codice JavaScript Angular, ma non è necessario avere una conoscenza dettagliata di Angular, è possibile utilizzare questo plugin di esempio e copiarlo e incollarlo + modificarlo se si sta implementando il proprio plugin.

L'intero codice di module.js è:

angular.module('app.resultsdatabankactions.filterByCorrelation', ['sqplugin']).config(function(sqPluginProvider, $controllerProvider) {
    
    function callback(nomeprogetto, nomeattività, nomebanca dati, strategie selezionate){
        window.parent.filterByCorrelationData = {
            nomeprogetto,
            nome del compito,
            nomebanca dati
        };
        window.parent.showPopup("#filterByCorrelationButtonModal");
    }

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

    sqPluginProvider.plugin("ResultsDatabankAction", 100, {
        titolo: Ltsq("Strumenti:Filtro per correlazione"),
        classe: 'btn btn-normal btn-default',
        controller: buttonController,
        id: "databank-action-filtrarepercorrelazione"
    });
    
    sqPluginProvider.addPopupWindow("plugins/FilterByCorrelation/popup.html", null, 'SQUANT');

});

 

Esaminiamo il codice parte per parte.

La prima parte consiste nel registrare il nostro nuovo plugin come modulo Angular:

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

 

In genere è sufficiente assegnargli un nome univoco, come ad esempio app.resultsdatabankactions.filterByCorrelation.
Utilizziamo internamente la convenzione app.resultsdatabankaction.YourUniqueName

 

La seconda parte consiste nel creare un controllore e un callback per il pulsante che mostrerà la finestra di dialogo a comparsa.

function callback(nomeprogetto, nomeattività, nomebanca dati, strategie selezionate){
    window.parent.filterByCorrelationData = {
        nomeprogetto,
        nome del compito,
        nomebanca dati
    };
    window.parent.showPopup("#filterByCorrelationButtonModal");
}

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

 

L'importante è chiamare il metodo window.parent.showPopup() a cui si dà un ID univoco del popup definito in precedenza in popup.html. Come suggerisce il nome, chiamandolo si mostrerà il popup e il nostro modulo,

e il controllore del pulsante lo invoca quando il pulsante viene cliccato.

 

La terza parte consiste nel registrare questo plugin e nel definire dove verrà visualizzato:

sqPluginProvider.plugin("ResultsDatabankAction", 100, {
    titolo: Ltsq("Filtra per correlazione"),
    classe: 'btn btn-normal btn-default',
    controller: buttonController,
    id: "databank-action-filtrarepercorrelazione"
});

 

Qui diciamo che è collegato a RisultatiAzioneBancaDati significa che verrà visualizzato come pulsante nella banca dati.

 

L'aspetto interessante è il titolo: se non si vuole che venga visualizzato come pulsante separato, ma come un'altra voce nel menu esistente Strumenti è possibile utilizzare semplicemente il titolo:

titolo: Ltsq("Strumenti:Filtro per correlazione"),

Si può anche vedere che abbiamo definito il controllore del pulsante e lo abbiamo impostato sul nostro pulsanteController definito in precedenza.

 

L'ultima parte consiste nel registrare il file dell'interfaccia utente nel sistema pugin di SQX, in modo che il file pooup.html venga caricato e diventi parte dell'interfaccia utente.

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

 

 

Implementazione del backend - FilterByCorrelationServlet.java

Ora che abbiamo l'interfaccia utente, dobbiamo implementare le funzionalità desiderate nel backend.

Il backend è realizzato come servlet HTTP che elabora le richieste HTTP provenienti dal frontend (UI) ed esegue le funzionalità richieste. Se si ricorda che abbiamo definito questa richiesta nel file popup.html, essa utilizza un metodo speciale CustomPluginController.sendRequest() che fa una richiesta a /filtroPerCorrelazione/Filtro.

Dobbiamo quindi implementare una servlet che gestisca questa richiesta e faccia ciò che vogliamo.

La servlet deve implementare IServletPlugin interfaccia. Il metodo importante è getHandler()che viene richiamato quando il backend riceve una richiesta per /filtroPerCorrelazione/.

La richiesta viene quindi gestita dal nostro FiltroPerCorrelazione classe interna che implementa la funzionalità.

 

Quindi la parte dello snippet che si limita a gestire la richiesta e ad assegnarle un gestore è:

@ImplementazionePlugin
public class FilterByCorrelationServlet implements IServletPlugin {

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

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

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

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

    @Override
    public int getPreferredPosition() {
        return 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 interna FilterByCorrelation

Questa è la classe che gestisce la richiesta /filtroPerCorrelazione/Filtro dall'interfaccia utente.

La prima parte di questa classe controlla quale comando è stato esattamente richiamato: nel nostro caso c'è solo filtro comando.

class FilterByCorrelation extends HttpJSONServlet {
    @Override
    protected String execute(String command, Map args, String method){
        switch(command) {
            case "filter": return onFilter(args);
            default:
                return apiErrorJSON("Esecuzione fallita. Comando sconosciuto '"+comando+"'.", 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", "Strategie filtrate, "+removedCount+" strategie rimosse.");
            return response.toString();
        }
        catch(Exception e){
            Log.error("Errore durante il filtraggio delle strategie per correlazione", e);
            response.put("error", "Filtraggio fallito - " + e.getMessage());
            return response.toString();
        }
    }

 

Quando riceve questo comando, esegue onFilter() metodo che:

  • ottiene parametri come il nome del progetto, il nome della banca dati, il periodo e la maxCorrelation dai parametri della richiesta.
  • esegue l'effettivo filtraggio per correlazione chiamando il metodo filterByCorrelation(nomeprogetto, nomebanca dati, periodo, maxCorrelation)
  • invierà quindi un messaggio con il numero effettivo di strategie rimosse o un messaggio di errore in caso di errore.

 

 

metodo filterByCorrelation()

questo metodo esegue il filtraggio vero e proprio, passando in rassegna tutte le strategie della banca dati attuale, calcolando le correlazioni tra le strategie e rimuovendo le strategie in cui la correlazione > maxCorrelation.

La correlazione di calcolo merita un articolo e un esempio a sé stante, potete seguire come viene implementata nel codice:

    private int filterByCorrelation(String projectName, String databankName, int period, double maxCorrelation) throws Exception {
        Log.info("Filtro per correlazione (progetto: " + nome progetto + ", banca dati: " + nome banca dati + ")...");
        SQProject project = ProjectEngine.get(projectName);
        Databank databank = project.getDatabanks().get(databankName);
        int removedCount = 0;

        // Verificare la presenza di strategie correlate utilizzando il punteggio di fitness come una lista di priorità/classifica
        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();
        // da fare: più strategie potrebbero avere lo stesso fitness. questo dovrebbe essere affrontato usando un ordinamento aggiuntivo (cioè fitness > pf > ret/dd ... ecc.)

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

            Log.info("Strategia di focus da confrontare: {}", focusStratName);

            rgUncorrelated.add(focusStrat.clone()); // aggiunge la migliore strategia del pool all'elenco delle non correlate come punto di partenza
            rgSortedCandidatesRanked.remove(0);

            Iteratore 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();

                    // calcolo dei periodi di correlazione
                    CorrelationPeriods correlationPeriods = precomputePeriodsAP(focusStrat, subStrat, period, corrType);
                    double corrValue = SQUtils.round2(correlationComputer.computeCorrelation(false, focusStratName, subStratName, focusStrat.orders(), subStrat.orders(), correlationPeriods));

                    Log.info(" - Correlazione di '{}' con la strategia focus: {}", subStratName, corrValue);

                    se (corrValue > maxCorrelation) {
                        it.remove();
                        removedCount++;
                        Log.info(" - Rimozione della strategia " + subStratName + " (correlazione " + corrValue + " > " + maxCorrelation + ")...");
                        databank.remove(subStratName, 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());
        se (timePeriod.from  l2) {
            l2 = timePeriod.to;
        }

        TimePeriods timePeriods1 = CorrelationLib.generatePeriods(paramInt, l1, l2);
        TimePeriods 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);

        restituisce correlationPeriods;
    }        
}

 

 

Lavorare con i plugin nell'Editor del codice

Nella build 136 Dev 2 di SQX abbiamo aggiunto una nuova funzionalità per lavorare con i plugin definiti dall'utente.

Se si è decompresso il plugin allegato nella cartella corretta ({installazione SQ}/user/extend/Plugins) e aprire l'Editor del codice, si vedrà una nuova sezione Plugin con il nostro nuovo plugin:

 

È possibile creare e modificare file e cartelle come di consueto. La novità è la possibilità di compilare il plugin.

Fare clic con il pulsante destro del mouse sulla cartella dei plugin e selezionare Compila plugin:

 

Il plugin verrà compilato e nella cartella apparirà un nuovo file FilterByCorrelation.jar.

Ora riavviate StrategyQuant e dovreste vedere un nuovo pulsante per la banca dati:

 

Facendo clic su di esso, si aprirà la finestra di dialogo mostrata di seguito:

Configurare le impostazioni di filtraggio e fare clic sul pulsante Filtro. Se nella banca dati sono presenti alcune strategie, è possibile notare che alcune di esse vengono rimosse e viene visualizzato un avviso:

 

Il nostro plugin ha funzionato: ha calcolato e confrontato le correlazioni tra tutte le strategie presenti nella banca dati corrente e ha rimosso 5 strategie che presentavano correlazioni superiori alla correlazione massima configurata.

 

Questo articolo è stato utile? L'articolo è stato utile L'articolo non è stato utile

Abbonarsi
Notificami
8 Commenti
Il più vecchio
Più recente I più votati
Feedback in linea
Visualizza tutti i commenti
Emmanuel
22. 6. 2022 12:53 pm

Eccellente !!!!!!!!!!!!!!!!!!!!!!!!!!!! Grazie mille per questo miglioramento. !!!
Sta aprendo le porte a un nuovo sviluppo!

Emmanuel
24. 6. 2022 1:58 pm

È la prima volta che vediamo un esempio di plug in, davvero utile per capire come funzionano i plug in e le correlazioni. Abbiamo bisogno di altri esempi di plug in. Con gli esempi, siamo in grado di sviluppare e condividere anche i plug in. Grazie per questo esempio

binhsir
3. 7. 2022 7:42

Devo dire che la scalabilità di SQX è impressionante, anche se non sono uno sviluppatore molto bravo.

Giordania
22. 9. 2022 2:42 pm

Caro Mark, grazie, lo apprezzo molto.

Giordania
22. 9. 2022 2:50 pm

Allo stesso tempo, spero che questa funzione possa essere utilizzata come attività disponibile nei Progetti personalizzati.

tnickel
17. 2. 2023 21:47

Ho testato questo plugin con SQ 4.137 Dev2 e funziona bene. Sono contento, questa funzione dovrebbe essere presente nella prossima versione di SQ.

tnickel
Rispondi a  tnickel
26. 4. 2023 2:22 pm

Sarebbe utile se questo "filtro per correlazione" fosse disponibile come nuova attività in un flusso di lavoro come blocco. Così molte cose possono essere automatizzate.

ytu
ytu
Rispondi a  tnickel
24. 6. 2023 5:29

Idea fantastica!!!