Documentazione

Applicazioni

Ultimo aggiornamento il 10. 11. 2021 da Mark Fric

Chiamare Python da SQ (Java) - Introduzione

Python è un linguaggio molto popolare, in particolare nella comunità scientifica e quantistica, grazie alle sue ampie librerie numeriche e statistiche.
In questo articolo mostreremo alcuni modi in cui Python può essere richiamato dallo Snippet Java di StrategyQuant.

Script Python

Utilizzeremo uno script Python molto semplice che sarà definito nel file sqpython.py

print("Ciao da Python!")

Se lo si chiama con l'interprete Python, restituirà un output "Hello from Python!".

$ python sqpython.py
Ciao da Python!

In questo esempio chiameremo questo script Python dallo snippet CustomAnalysis: esso chiamerà lo script e memorizzerà il suo output in un valore speciale.

Per vedere l'output dello script Python in SQ, creeremo una colonna speciale della banca dati che mostrerà questo valore. Questo esempio è una variante dell'esempio di Analisi personalizzata: Esempio - analisi personalizzata per strategia.

In seguito alla Passo 6 nell'esempio precedente creeremo una colonna personalizzata della banca dati che leggerà e visualizzerà il valore della stringa memorizzata nella variabile del risultato della strategia (questo verrà fatto dallo snippet Analisi personalizzata dopo aver chiamato lo script Python).

L'intero codice del nuovo frammento di colonna della banca dati è:

pacchetto SQ.Columns.Databanks;

import com.strategyquant.tradinglib.DatabankColumn;
import com.strategyquant.tradinglib.ResultsGroup;
import com.strategyquant.tradinglib.ValueTypes;

public class TestPythonOutput extends DatabankColumn {

    public TestPythonOutput() {
        super("TestPythonOutput", DatabankColumn.Text, ValueTypes.Maximize, 0, 0, 100);
    }
  
  //------------------------------------------------------------------------
    
    @Override
    public String getValue(ResultsGroup rg, String resultKey, byte direction, byte plType, byte sampleType) throws Exception {
        String value = rg.specialValues().getString("PythonOutput", NOT_AVAILABLE);
        
        restituire il valore;
    }
}

 

Se compilate questo snippet, riavviate SQ e aggiungete questa nuova colonna al vostro database, dovreste vedere la stringa "N/A" in questa colonna - perché non abbiamo ancora chiamato nessuno script Python per riempirla:

Colonna della banca dati Uscita Python NA

L'obiettivo di Python è riempire questa colonna con i valori restituiti da Python.

 

Chiamare script Python da Java usando Jython

Probabilmente il modo più semplice per richiamare Python da SQ è quello di utilizzare il metodo Jython. Jython è un'implementazione Java di Python che gira sulla JVM, il che significa che si tratta di una libreria Java pura, senza dipendenze esterne.

È possibile scaricarlo (la versione standalone JAR) da https://www.jython.org/download

e metterlo a disposizione del vostro {installazione SQ}user}libs cartella.

Si tratta di una cartella in cui possono essere collocate le librerie JAR personalizzate, che vengono individuate e caricate da SQ durante l'avvio.

Una volta che il jython-standalone-2.7.2.jar è presente e si riavvia SQ, si potrà utilizzare la funzionalità Jython in SQ.

Ora creeremo un nuovo snippet di analisi personalizzata chiamato CAJythonScript con il seguente codice:

pacchetto SQ.CustomAnalysis;

importare java.io.FileReader;
importare java.io.StringWriter;
importare java.util.List;

importare javax.script.*;

importare org.slf4j.Logger;
importare org.slf4j.LoggerFactory;

import com.strategyquant.lib.app.MainApp;
import com.strategyquant.tradinglib.CustomAnalysisMethod;
importare com.strategyquant.tradinglib.Result;
import com.strategyquant.tradinglib.ResultsGroup;

public class CAJythonScript extends CustomAnalysisMethod {
    public static final Logger Log = LoggerFactory.getLogger(CAJythonScript.class);
    
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    
    public CAJythonScript() {
        super("CAJythonScript", TYPE_FILTER_STRATEGY);
    }
    
    //------------------------------------------------------------------------
    
    @Override
    public boolean filterStrategy(String project, String task, String databankName, ResultsGroup rg) throws Exception {
        Log.info("Chiamata allo script Jython");

        StringWriter writer = new StringWriter();
        ScriptContext context = new SimpleScriptContext();
        context.setWriter(writer);

        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("jython");
        String pythonScript = MainApp.getDataPath()+"sqpython.py";
        Log.info("Percorso script Jython: "+pythonScript);
        engine.eval(new FileReader(pythonScript), context);

        String result = writer.toString().trim();

        Log.info("Output dello script Jython: "+risultato);

        rg.specialValues().set("PythonOutput", result);

        restituisce true;
    }
}

Il codice è abbastanza semplice, utilizza le funzionalità del motore di script di Java e sceglieremo il motore "jython" come nome.

Allora chiameremo motore.eval() e passargli come parametro il percorso del nostro file di script Python.

Questo snippet esegue lo script Python, legge il suo output e lo salva sotto la chiave "PythonOutput" nella mappa dei valori personalizzata della nostra strategia (ResultGroup).

Una volta compilato, riavviate SQ ed eseguite il nostro nuovo snippet di analisi personalizzata.

È possibile eseguire uno snippet di analisi personalizzato come parte del processo di Builder/Retester o come task personalizzato in un progetto personalizzato. Nel nostro esempio lo eseguiremo semplicemente in Retester:

Snippet di analisi personalizzata in python

Si noti che lo script Analisi personalizzata viene richiamato per ogni strategia dopo il suo retest, quindi assicurarsi di premere Avvia per eseguire il retest.

Si dovrebbe notare che la colonna TestPythonOutput della nostra banca dati di controllo contiene ora il testo "Hello from Python!". - che è l'output del nostro script Python:

Uscita in python della colonna della banca dati

Ciò significa che lo script Python è stato chiamato correttamente e ha restituito il valore che ora viene visualizzato nella colonna della banca dati personalizzata.

Vantaggi di Jython:

  • Chiamata relativamente veloce: non è necessario richiamare alcun processo esterno, tutto viene gestito internamente alla JVM.
  • Semplice da usare: basta aggiungere una libreria .JAR a SQ

Svantaggi:

  • Jython è l'implementazione di Python per Java e potrebbe non contenere tutti gli stessi sottopacchetti di Python nativo.
  • Non è più sviluppato attivamente, quindi è disponibile solo per una vecchia versione di Python.

Jython è adatto per script semplici, ma se avete bisogno di tutta la potenza di Python, con tutte le sue librerie supportate, dovete usare un altro metodo.

 

Chiamare Python da Java invocando un'installazione Python esterna

Come suggerisce il titolo, questo metodo invocherà un processo nativo del sistema operativo per lanciare python ed eseguire il nostro semplice script.

Ciò significa che è necessario che Python sia installato sul computer. L'installazione di Python esula dallo scopo di questo articolo, si può seguire la documentazione sul sito web https://www.python.org/ sito web.

Dovete installare Python sul vostro computer in modo da poter eseguire normalmente gli script chiamando

$ python myscript.py

dalla riga di comando.

Creeremo un nuovo frammento di analisi personalizzata chiamato CAPythonScript con il seguente codice:

pacchetto SQ.CustomAnalysis;

importare java.io.BufferedReader;
importare java.io.IOException;
importare java.io.InputStream;
importare java.io.InputStreamReader;
importare java.util.List;
importare java.util.concurrent.TimeUnit;

importare org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.strategyquant.lib.app.MainApp;
import com.strategyquant.tradinglib.CustomAnalysisMethod;
importare com.strategyquant.tradinglib.Result;
import com.strategyquant.tradinglib.ResultsGroup;

public class CAPythonScript extends CustomAnalysisMethod {
    public static final Logger Log = LoggerFactory.getLogger(CAPythonScript.class);
    
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    
    public CAPythonScript() {
        super("CAPythonScript", TYPE_FILTER_STRATEGY);
    }
    
    //------------------------------------------------------------------------
    
    @Override
    public boolean filterStrategy(String project, String task, String databankName, ResultsGroup rg) throws Exception {
    
        String pythonScript = MainApp.getDataPath()+"sqpython.py";
        
        Log.info("Chiamata allo script Python: "+pythonScript);
        
        ProcessBuilder processBuilder = new ProcessBuilder("py", pythonScript);
        processBuilder.redirectErrorStream(true);

        Process process = processBuilder.start();
        String result = readProcessOutput(process.getInputStream());

        int exitCode = process.waitFor();
        
        Log.info("Output dello script Python: "+risultato);

        rg.specialValues().set("PythonOutput", result);
        
        restituisce true;
    }

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

    private String readProcessOutput(InputStream stream) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));

        StringBuilder stringBuilder = new StringBuilder();

        try {
        Linea di stringhe;
        while ((line = reader.readLine())= null) {
            stringBuilder.append(line);
        }

        stream.close();

        } catch (IOException ex) {
        Log.error("Errore: ", ex);
        }
        
        return stringBuilder.toString();
    }
}

In questo snippet la chiamata allo script esterno viene effettuata utilizzando ProcessBuilder, ma anche in questo caso è semplice da seguire.

È necessario creare un ProcessBuilder con i parametri indicati, quindi eseguirlo richiamando il metodo .start() e raccoglierne l'output.

Questo richiama il Python installato esternamente, quindi può contenere tutte le librerie e i moduli che si possono usare nel Python standard.

 

Chiamare Python via HTTP

I due esempi precedenti riguardavano l'esecuzione diretta di script Python, come si può fare dalla riga di comando.

Un modo diverso di chiamare Python da SQ sarebbe quello di usare un protocollo HTTP come livello di astrazione tra i due linguaggi.

Python viene fornito di default con un semplice server HTTP integrato, che possiamo utilizzare per condividere contenuti o file tramite HTTP:

python -m http.server 9000

 

Se andrete a http://localhost:9000, si vedrà il contenuto della directory in cui è stato lanciato il comando precedente.

È possibile utilizzare framework web popolari come Fiaschetta o Django per creare endpoint web in Python.
Una volta ottenuti gli endpoint web in Python, è possibile chiamarli da SQ utilizzando una delle librerie HTTP di Java.

Creeremo un nuovo snippet di analisi personalizzato, chiamato CAPythonHttp, che chiama Python via HTTP:

pacchetto SQ.CustomAnalysis;

importare com.strategyquant.lib.SQUtils;
importare com.strategyquant.lib.app.MainApp;
import com.strategyquant.tradinglib.CustomAnalysisMethod;
importare com.strategyquant.tradinglib.ResultsGroup;
importare org.slf4j.Logger;
import org.slf4j.LoggerFactory;

importare javax.script.ScriptContext;
importare javax.script.ScriptEngine;
importare javax.script.ScriptEngineManager;
importare javax.script.SimpleScriptContext;
importare java.io.*;
importare java.net.HttpURLConnection;
importare java.net.URL;
importare java.util.List;

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

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

    public CAPythonHttp() {
        super("CAJythonScript", TYPE_FILTER_STRATEGY);
    }
    
    //------------------------------------------------------------------------
    
    @Override
    public boolean filterStrategy(String project, String task, String databankName, ResultsGroup rg) throws Exception {
        HttpURLConnection connection = null;

        try {
            Log.info("Chiamata a Python via HTTP");

            URL url = new URL("http://localhost:9000");
            connection = (HttpURLConnection)url.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "text/html");
            connection.setRequestProperty("Content-Language", "en-US");

            connection.setUseCaches(false);
            connection.setDoOutput(true);

            // ottenere la risposta
            InputStream is = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            StringBuilder response = new StringBuilder(); // o StringBuffer se non è Java 5+
            Linea di stringhe;
            while((line = rd.readLine())= null) {
                response.append(line);
                response.append('\r');
            }
            rd.close();

            String result = response.toString();

            // rimuove i tag HTML dalla risposta
            result = result.replaceAll("\", "");

            Log.info("Output dello script PythonHTTP: "+risultato);

            // memorizzeremo solo i primi 10 caratteri del risultato
            rg.specialValues().set("PythonOutput", result.substring(0, 10));

        } catch (Exception e) {
            throw new Exception("Errore di richiesta: GET, " + e.getMessage());
        } finally {
            if(connection != null) {
                connection.disconnect();
            }
        }

        return true;
    }
}

Anche in questo caso, dovrebbe essere facile da seguire. Lo script crea una richiesta HTTP a un determinato URL e legge la risposta.

In questo esempio, la risposta sarebbe un lungo documento HTML contenente l'elenco delle directory, quindi rimuoveremo i tag HTML e lo accorceremo a 10 caratteri.

L'output dovrebbe essere il seguente:

Colonna della banca dati Python http

 

Passaggio di parametri

Qui le cose si fanno interessanti e più complicate. Chiamare script Python da SQ senza passare alcun parametro ha un uso molto limitato.

Normalmente si desidera passare alcuni (o molti) parametri da SQ a Python: nome della strategia, elenco delle operazioni, risultati dei test, ecc. in modo da poter lavorare con essi in Python ed eventualmente restituire qualcosa.

Il modo di passare questi parametri e di ottenere i risultati da Python dipende dal metodo di invocazione Python utilizzato.

Nel caso di una chiamata HTTP la situazione è semplice.

È possibile passare una quantità di dati anche molto grande tra SQ e Python utilizzando lo standard HTTP request/response.

È possibile inviare dati all'endpoint web di Python come parametri GET standard o dati POST - È possibile restituire i dati a SQ utilizzando la risposta HTTP

In caso di chiamata di uno script Python esterno (utilizzando Jython o l'invocazione Python)

Se i dati passati sono piccoli (ad esempio solo il nome della strategia), è possibile passarli come argomenti dello script.

Quando si inviano dati più grandi, una possibilità è quella di salvare i dati in un file temporaneo nel formato desiderato in SQ, e poi caricare i dati da questo file temporaneo in Python. È possibile utilizzare lo stesso metodo per inviare dati più grandi da Python a SQ.

 

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

Abbonarsi
Notificami
2 Commenti
Il più vecchio
Più recente I più votati
Feedback in linea
Visualizza tutti i commenti
Commerciante di api
10. 11. 2021 1:24 pm

merci

Emmanuel
13. 1. 2022 1:36 pm

Grazie

Messaggi correlati