Documentation

Applications

Dernière mise à jour le 10. 11. 2021 par Mark Fric

Appeler Python depuis SQ (Java) - Introduction

Python est un langage très populaire, en particulier dans la communauté scientifique et quantique grâce à ses vastes bibliothèques numériques et statistiques.
Dans cet article, nous allons montrer quelques façons d'appeler Python à partir de l'extrait Java de StrategyQuant.

Script Python

Nous utiliserons un script Python très simple qui sera défini dans le fichier sqpython.py

print("Hello from Python !")

Si vous l'appelez par l'interpréteur Python, il renverra un message "Hello from Python !".

$ python sqpython.py
Bonjour de Python !

Dans cet exemple, nous appellerons ce script Python à partir du snippet CustomAnalysis - il appellera le script et stockera sa sortie dans une valeur spéciale.

Pour voir la sortie du script Python dans SQ, nous allons créer une colonne spéciale de la banque de données qui affichera cette valeur. Cet exemple est une variante de l'exemple d'analyse personnalisée : Exemple - analyse personnalisée par stratégie.

Suite à la Étape 6 dans l'exemple précédent, nous allons créer une colonne de banque de données personnalisée qui lira et affichera la valeur de la chaîne stockée dans la variable de résultat de la stratégie (cela sera fait par l'extrait d'analyse personnalisée après avoir appelé le script Python).

Le code de l'extrait de colonne de la nouvelle banque de données est le suivant :

package 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) ;
        
        renvoie la valeur ;
    }
}

 

Si vous compilez cet extrait, redémarrez SQ et ajoutez cette nouvelle colonne à votre banque de données, vous devriez voir la chaîne "N/A" dans cette colonne - c'est parce que nous n'avons pas encore appelé de script Python pour la remplir :

Colonne de la banque de données Sortie Python NA

Notre objectif avec Python est de remplir cette colonne avec les valeurs renvoyées par Python.

 

Appeler des scripts Python depuis Java avec Jython

1TP6La façon la plus simple d'appeler Python à partir de SQ est sans doute d'utiliser Jython. Jython est une implémentation Java de Python qui fonctionne sur la JVM, ce qui signifie qu'il s'agit d'une bibliothèque Java pure sans aucune dépendance externe.

Vous pouvez le télécharger (la version autonome JAR) à l'adresse suivante https://www.jython.org/download

et le mettre à votre {SQ installation}\user\libs dossier.

Il s'agit d'un dossier dans lequel des bibliothèques JAR personnalisées peuvent être placées ; elles sont localisées et chargées par SQ lors du démarrage.

Une fois que le fichier jython-standalone-2.7.2.jar est présent et que vous avez redémarré SQ, vous serez en mesure d'utiliser les fonctionnalités de Jython dans SQ.

Nous allons maintenant créer un nouveau snippet d'analyse personnalisée nommé CAJythonScript avec le code suivant :

package SQ.CustomAnalysis ;

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

import javax.script.* ;

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

import com.strategyquant.lib.app.MainApp ;
import com.strategyquant.tradinglib.CustomAnalysisMethod ;
import 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("Calling Jython Script") ;

        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("Chemin du script Jython : "+pythonScript) ;
        engine.eval(new FileReader(pythonScript), context) ;

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

        Log.info("Sortie du script Jython : "+résultat) ;

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

        return true ;
    }
}

Le code est assez simple, il utilise la fonctionnalité du moteur de script intégré à Java, et nous choisirons le moteur "jython" par son nom.

Dans ce cas, nous appellerons simplement engine.eval() et lui passer en paramètre le chemin d'accès à notre fichier de script Python.

Cet extrait exécute le script Python, lit sa sortie et l'enregistre sous la clé "PythonOutput" dans la carte des valeurs personnalisées de notre stratégie (ResultGroup).

Une fois la compilation terminée, redémarrez SQ et exécutez notre nouveau snippet d'analyse personnalisée.

Vous pouvez exécuter un extrait d'analyse personnalisé dans le cadre du processus Builder/Retester ou en tant que tâche personnalisée dans un projet personnalisé. Dans notre exemple, nous l'exécuterons simplement dans Retester :

Analyse personnalisée snippet python

Notez que le script d'analyse personnalisée est appelé pour chaque stratégie après son nouveau test, assurez-vous donc de cliquer sur Démarrer pour exécuter le nouveau test.

Vous devriez voir que la colonne TestPythonOutput de notre banque de données de contrôle contient maintenant le texte "Hello from Python !" - qui est la sortie de notre script Python :

Sortie python de la colonne de la banque de données

Cela signifie que le script Python a été correctement appelé et qu'il a renvoyé la valeur qui est maintenant affichée dans la colonne de notre banque de données personnalisée.

Avantages de Jython :

  • Appel relativement rapide - aucun processus externe ne doit être appelé, tout est géré en interne dans la JVM
  • Simple à utiliser - il suffit d'ajouter une bibliothèque .JAR à SQ

Inconvénients :

  • Jython est l'implémentation de Python pour Java, il peut ne pas contenir les mêmes sous-packages que Python natif.
  • Il n'est plus activement développé et n'est donc disponible que pour une ancienne version de Python.

Jython convient pour les scripts simples, mais si vous avez besoin de toute la puissance de Python, avec toutes les bibliothèques qu'il supporte, vous devez utiliser une autre méthode.

 

Appeler Python depuis Java en invoquant une installation externe de Python

Comme son titre l'indique, cette méthode invoquera un processus natif du système d'exploitation pour lancer Python et exécuter notre simple script.

Cela signifie que Python doit être installé sur votre ordinateur. L'installation de Python dépasse le cadre de cet article, vous pouvez suivre la documentation sur le site web de https://www.python.org/ site web.

Vous devez installer Python sur votre ordinateur afin de pouvoir exécuter normalement des scripts en appelant

$ python myscript.py

à partir de la ligne de commande.

Nous allons créer un nouvel extrait d'analyse personnalisée nommé CAPythonScript avec le code suivant :

package SQ.CustomAnalysis ;

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

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

import com.strategyquant.lib.app.MainApp ;
import com.strategyquant.tradinglib.CustomAnalysisMethod ;
import 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("Calling Python Script : "+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("Sortie du script Python : "+résultat) ;

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

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

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

        StringBuilder stringBuilder = new StringBuilder() ;

        try {
        String line ;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line) ;
        }

        stream.close() ;

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

Dans cet extrait, l'appel au script externe se fait à l'aide de ProcessBuilder, mais là encore, c'est simple à suivre.

Vous devez créer un ProcessBuilder avec des paramètres, puis l'exécuter en appelant la fonction .start() et de collecter ses résultats.

Cela appellera le Python installé en externe, qui peut donc contenir toutes les bibliothèques et tous les modules que vous pouvez utiliser dans votre Python standard.

 

Appeler Python par HTTP

Les deux exemples précédents concernaient l'exécution directe de scripts Python, comme vous pouvez le faire à partir de la ligne de commande.

Une autre façon d'appeler Python à partir de SQ consisterait à utiliser un protocole HTTP comme couche d'abstraction entre les deux langages.

Python est livré par défaut avec un simple serveur HTTP intégré que nous pouvons utiliser pour partager du contenu ou des fichiers via HTTP :

python -m http.server 9000

 

Si vous vous rendez à http://localhost:9000vous verrez la liste du contenu du répertoire dans lequel nous avons lancé la commande précédente.

Vous pouvez utiliser des frameworks web populaires tels que Flacon ou Django pour créer des points d'accès web en Python.
Une fois que vous avez les points de terminaison web en Python, vous pouvez les appeler à partir de SQ en utilisant l'une des bibliothèques Java HTTP.

Nous allons créer un nouvel extrait d'analyse personnalisé appelé CAPythonHttp qui appelle Python via HTTP :

package SQ.CustomAnalysis ;

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

import javax.script.ScriptContext ;
import javax.script.ScriptEngine ;
import javax.script.ScriptEngineManager ;
import javax.script.SimpleScriptContext ;
import java.io.* ;
import java.net.HttpURLConnection ;
import java.net.URL ;
import 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("Calling 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) ;

            // obtenir la réponse
            InputStream is = connection.getInputStream() ;
            BufferedReader rd = new BufferedReader(new InputStreamReader(is)) ;
            StringBuilder response = new StringBuilder() ; // ou StringBuffer si pas Java 5+
            Chaîne ligne ;
            while((line = rd.readLine()) != null) {
                response.append(line) ;
                response.append('\r') ;
            }
            rd.close() ;

            String result = response.toString() ;

            // supprimer les balises HTML de la réponse
            result = result.replaceAll("\N-<.*?\N-", "") ;

            Log.info("Sortie du script PythonHTTP : "+résultat) ;

            // nous ne stockerons que les 10 premiers caractères du résultat
            rg.specialValues().set("PythonOutput", result.substring(0, 10)) ;

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

        return true ;
    }
}

Là encore, il doit être facile à suivre. Le script crée une requête HTTP vers une URL donnée et lit la réponse.

Dans cet exemple, la réponse serait un long document HTML contenant la liste des répertoires. Nous supprimerons donc les balises HTML et raccourcirons le document à 10 caractères.

Vous devriez voir la sortie comme suit :

Colonne de la banque de données Python http

 

Transmission des paramètres

C'est ici que les choses deviennent intéressantes et plus compliquées. Appeler des scripts Python depuis SQ sans leur passer de paramètres n'a qu'une utilité très limitée.

Vous voulez normalement passer quelques (ou beaucoup) de paramètres de SQ à Python - nom de la stratégie, liste des transactions, résultats des tests, etc. afin que vous puissiez travailler avec eux dans Python et éventuellement renvoyer quelque chose en retour.

La manière de transmettre ces paramètres et d'obtenir des résultats de Python dépend de la méthode d'invocation de Python que vous utilisez.

Dans le cas d'un appel HTTP, la situation est simple.

Il est possible de transmettre de très grandes quantités de données entre SQ et Python à l'aide de requêtes/réponses HTTP standard.

Vous pouvez envoyer des données à votre point de terminaison web Python sous la forme de paramètres GET ou POST standard - Vous pouvez renvoyer des données à SQ à l'aide d'une réponse HTTP.

En cas d'appel d'un script Python externe (utilisant Jython ou une invocation Python)

Si les données transmises sont peu nombreuses (par exemple, seulement le nom de la stratégie), vous pouvez les transmettre en tant qu'arguments du script.

Lors de l'envoi de données plus importantes, une possibilité consiste à enregistrer les données dans un fichier temporaire dans le format de votre choix dans SQ, puis à charger les données à partir de ce fichier temporaire dans Python. Vous pouvez utiliser la même méthode pour envoyer des données plus importantes de Python à SQ.

 

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
Négociant en abeilles
10. 11. 2021 1:24 pm

merci

Emmanuel
13. 1. 2022 1:36 pm

merci

Postes connexes