Documentación

Aplicaciones

Última actualización el 10. 11. 2021 por Mark Fric

Llamada a Python desde SQ (Java) - Introducción

Python es un lenguaje muy popular, sobre todo en la comunidad científica y cuántica, gracias a sus amplias bibliotecas numéricas y estadísticas.
En este artículo mostraremos algunas formas en que Python puede ser llamado desde StrategyQuant Java Snippet.

Script en Python

Utilizaremos un script Python muy sencillo que se definirá en el archivo sqpython.py

print("¡Hola desde Python!")

Si lo llamaras mediante el intérprete de Python devolvería una salida "¡Hola desde Python!".

$ python sqpython.py
¡Hola desde Python!

En este ejemplo vamos a llamar a este script de Python de CustomAnalysis fragmento - que llamará a la secuencia de comandos y almacena su salida a un valor especial.

Para ver la salida del script Python en SQ crearemos una columna especial del banco de datos que mostrará este valor. Este ejemplo es una variación de este ejemplo de Análisis Personalizado: Ejemplo - análisis personalizado por estrategia.

Tras la Paso 6 en el ejemplo anterior crearemos una columna personalizada del banco de datos que leerá y mostrará el valor de cadena almacenado en la variable de resultado de la estrategia (esto se hará mediante el fragmento de análisis personalizado después de llamar al script de Python).

Todo el código del nuevo fragmento de columna del banco de datos es:

paquete SQ.Columns.Databanks;

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

public clase 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);
        
        return valor;
    }
}

 

Si compilas este snippet, reinicias SQ y añades esta nueva columna a tu base de datos, deberías ver una cadena "N/A" en esta columna - es porque no hemos llamado a ningún script Python para rellenarla todavía:

Columna del banco de datos Salida Python NA

Nuestro objetivo con Python es llenar esta columna con el valor devuelto por Python.

 

Llamada a scripts Python desde Java mediante Jython

Probablemente la forma más sencilla de llamar a Python desde SQ es utilizando Jython. Jython es una implementación Java de Python que se ejecuta en la JVM, lo que significa que es una biblioteca Java pura sin dependencias externas.

Puede descargarlo (la versión independiente JAR) desde https://www.jython.org/download

y póngalo a su {Instalación SQ}\\libs carpeta.

Esta es una carpeta donde las librerías JAR personalizadas pueden ser colocadas y son localizadas y cargadas por SQ durante el arranque.

Una vez que el jython-standalone-2.7.2.jar esté ahí y reinicies SQ podrás usar la funcionalidad Jython en SQ.

Ahora crearemos un nuevo fragmento de análisis personalizado denominado CAJythonScript con el siguiente código:

paquete 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("Llamando al 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("Ruta del script Jython: "+pythonScript);
        engine.eval(new FileReader(pythonScript), context);

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

        Log.info("Salida del script Jython: "+resultado);

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

        return true;
    }
}

El código es bastante simple, utiliza la funcionalidad del motor de scripts incorporado en Java, y elegiremos el motor "jython" por su nombre.

Entonces llamaremos engine.eval() y le pasamos como parámetro la ruta a nuestro archivo de guión Python.

Este snippet ejecuta el script Python, lee su salida y guarda esta salida bajo la clave "PythonOutput" en el mapa de valores personalizado en nuestra estrategia (ResultGroup).

Cuando compile, reinicie SQ y ejecute nuestro nuevo fragmento de análisis personalizado.

Puede ejecutar un fragmento de análisis personalizado como parte del proceso Builder/Retester o como una tarea personalizada en un proyecto personalizado. En nuestro ejemplo simplemente lo ejecutaremos en Retester:

Fragmento de python de análisis personalizado

Tenga en cuenta que el script de Análisis Personalizado es llamado para cada estrategia después de su retest, así que asegúrese de pulsar Start para ejecutar el Retest.

Debería ver que nuestra columna del banco de datos de control TestPythonOutput contiene ahora el texto "¡Hola desde Python!" - que es la salida de nuestro script Python:

Columna del banco de datos salida python

Esto significa que el script de Python fue llamado correctamente y devolvió el valor que ahora se muestra en nuestra columna personalizada del banco de datos.

Ventajas de Jython:

  • Llamada relativamente rápida: no hay que llamar a ningún proceso externo, todo se gestiona internamente en la JVM.
  • Fácil de usar: basta con añadir una biblioteca .JAR a SQ

Desventajas:

  • Jython es la implementación de Python para Java, puede que no contenga todos los mismos subpaquetes que el Python nativo.
  • Ya no se desarrolla activamente, por lo que sólo está disponible para una versión antigua de Python

Jython es adecuado para scripts sencillos, pero si necesitas toda la potencia de Python, con todas sus librerías soportadas, debes usar un camino diferente.

 

Llamada a Python desde Java invocando una instalación Python externa

Como el título sugiere este método invocará un proceso nativo del sistema operativo para lanzar python y ejecutar nuestro sencillo script.

Esto significa que debe tener Python instalado en su ordenador. La instalación de Python está fuera del alcance de este artículo, puede seguir la documentación en la página https://www.python.org/ sitio web.

Deberías instalar Python en tu ordenador para poder ejecutar scripts normalmente llamando a

$ python myscript.py

desde la línea de comandos.

Crearemos un nuevo fragmento de análisis personalizado llamado CAPythonScript con el siguiente código:

paquete 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("Llamando a Python Script: "+pythonScript);
        
        ProcessBuilder processBuilder = new ProcessBuilder("py", pythonScript);
        processBuilder.redirectErrorStream(true);

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

        int exitCode = proceso.esperar();
        
        Log.info("Salida del script Python: "+resultado);

        rg.specialValues().set("SalidaPython", resultado);
        
        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(línea);
        }

        stream.close();

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

En este snippet la llamada del script externo se hace usando ProcessBuilder, pero de nuevo es simple de seguir.

Tienes que crear un ProcessBuilder con los parámetros dados, y luego ejecutarlo llamando a la función .start() y recoger su resultado.

Esto llamará al Python instalado externamente, por lo que puede contener cualquier librería y módulo que puedas usar en tu Python estándar.

 

Llamada a Python por HTTP

Los dos ejemplos anteriores trataban sobre la ejecución directa de scripts Python, ya que podrías hacerlo desde la línea de comandos.

Una forma diferente de llamar a Python desde SQ sería utilizando un protocolo HTTP como capa de abstracción entre los dos lenguajes diferentes.

Python incluye por defecto un sencillo servidor HTTP integrado que podemos utilizar para compartir contenidos o archivos a través de HTTP:

python -m http.servidor 9000

 

Si vas a http://localhost:9000verá el contenido del directorio donde ejecutó el comando anterior.

Puede utilizar marcos web populares como Frasco o Django para crear puntos finales web en Python.
Una vez que tengas los web endpoints en Python puedes llamarlos desde SQ usando una de las librerías HTTP de Java.

Crearemos un nuevo fragmento de análisis personalizado llamado CAPythonHttp que llama a Python a través de HTTP:

paquete 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 conexión = null;

        try {
            Log.info("Llamando a Python vía HTTP");

            URL url = nueva 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);

            // obtener respuesta
            InputStream is = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            StringBuilder response = new StringBuilder(); // o StringBuffer si no es Java 5+
            String line;
            while((line = rd.readLine()) != null) {
                response.append(line);
                response.append('\r');
            }
            rd.close();

            String result = response.toString();

            // eliminar las etiquetas HTML de la respuesta
            result = result.replaceAll("\", "");

            Log.info("Salida del script PythonHTTP: "+resultado);

            // almacenaremos sólo los 10 primeros caracteres del resultado
            rg.specialValues().set("PythonOutput", result.substring(0, 10));

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

        return true;
    }
}

De nuevo, debería ser fácil de seguir. El script creará una petición HTTP a una URL dada y leerá la respuesta.

En este ejemplo, la respuesta sería un largo documento HTML con el listado de directorios, por lo que eliminaremos las etiquetas HTML y lo acortaremos a 10 caracteres.

Usted debe ver la salida como:

Columna del banco de datos Python http

 

Pasar parámetros

Aquí es donde las cosas se ponen interesantes y más complicadas. Llamar a scripts Python desde SQ sin pasarles ningún parámetro tiene un uso muy limitado.

Normalmente se desea pasar algunos (o muchos) parámetros de SQ a Python - nombre de la estrategia, lista de operaciones, resultados de pruebas, etc. para poder trabajar con ellos en Python y posiblemente devolver algo.

La forma de pasar estos parámetros y obtener resultados de Python depende del método de invocación de Python que utilices.

En caso de llamada HTTP la situación es sencilla.

Puede pasar incluso una gran cantidad de datos entre SQ y Python utilizando la petición/respuesta HTTP estándar.

Puede enviar datos a su punto final web Python como parámetros GET estándar o datos POST- Puede devolver datos a SQ utilizando la respuesta HTTP

En caso de llamada a script Python externo (utilizando Jython o invocación Python)

Si los datos pasados son pequeños (por ejemplo, sólo el nombre de la estrategia) puede pasarlos como argumentos del script.

Al enviar datos más grandes, una posibilidad es guardar los datos en un archivo temporal en cualquier formato que desee en SQ, y luego cargar los datos de este archivo temporal en Python. Puedes usar la misma forma para enviar datos más grandes desde Python a SQ.

 

¿Le ha resultado útil este artículo? El artículo era útil El artículo no era útil

Suscríbase a
Notificar a
2 Comentarios
Más antiguo
Más reciente Más votados
Feedbacks de Inline
Ver todos los comentarios
Comerciante de abejas
10. 11. 2021 13:24

merci

Emmanuel
13. 1. 2022 1:36 pm

gracias

Puestos relacionados