Documentación

Aplicaciones

Última actualización el 21. 6. 2022 by Mark Fric

Filtro por correlación - ejemplo de plugin

Nota

  • Este ejemplo y la funcionalidad que requiere se añaden sólo a SQ Build 136 Dev 2 o posterior
  • La funcionalidad descrita es sólo para usuarios "avanzados" y dedicados. No está completamente documentada, este es el primer ejemplo que muestra esta posibilidad.

 

Este es un ejemplo de cómo extender la funcionalidad de SQX con su propio plugin.

StrategyQuant X está hecho como un sistema basado en plugins, cada parte del programa es un plugin. Sin embargo, la ampliación de SQX con su propio plugin no era posible hasta ahora (Build 136 Dev 2), porque se necesitaba acceso al código fuente completo, y no había manera de compilar su plugin.

SQX UI es básicamente una aplicación web escrita en AngularJS, donde el backend está en Java servlets.

En este ejemplo mostraremos paso a paso cómo crear un plugin sencillo que incluya una interfaz de usuario simple.

El plugin completo está disponible también como archivo ZIP adjunto a este artículo.

 

Este plugin implementará una funcionalidad de filtrado de estrategias por correlación. Funciona de la siguiente manera:

  • plugin añadirá un nuevo botón Filtrar por correlación al banco de datos
  • al hacer clic en el botón se muestra un cuadro de diálogo emergente, donde el usuario configura la correlación máxima permitida
  • cuando se confirma, plugin llamará a su método de backend y que va a ir a través de todas las estrategias en el banco de datos real de filtrado (eliminación) los que tienen más de Max correlación con otras estrategias en este banco de datos

 

Créditos - este plugin se basa en el código publicado por AgentePot en Discord como parte de nuestro sesiones de programación en directoel código se utiliza con su consentimiento.

 

Ubicación de los plugins de usuario

Los plugins desarrollados por el usuario deben ubicarse en la carpeta {Instalación de SQ}/usuario/extender/Plugins. Puede descargar el archivo ZIP de este plugin y extraerlo en esta carpeta.

Cuando abra CodeEditor, debería ver la carpeta de este plugin allí:

 

Este plugin consta de tres archivos:

  • FilterByCorrelationServlet.java - archivo java que implementa la funcionalidad de filtrado de estrategias por correlación.
  • módulo.js - Archivo JavaScript que contiene la definición del módulo en Angular
  • popup.html - Archivo HTML que contiene la interfaz de usuario de este nuevo plugin

Veámoslas una a una, empezando por las más sencillas.

 

 

Definir UI para el plugin - popup.html

La interfaz de usuario del plugin se define en el archivo popup.html. Se trata de un archivo HTML estándar que se muestra en una ventana modal.

Hay DIVs para decorar la ventana, el "formulario" real se define dentro 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>Filtrar por correlación</h4>
            </div>
            <div class="modal-body" style="height: auto;">
                Funcionalidad - todas las estrategias que tengan una correlación mayor que Max serán eliminadas de la base de datos actual.
                <label tsq>Ajustes de filtrado</label>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Periodo de correlación:</label>
                        </div>
                        <div class="col-sm-6">
                            <select id="fbc-correlation-period" class="setting-control">    
                                <option value="5" tsq>Hora</option>
                                <option value="10" selected tsq>Día</option>
                                <option value="20" tsq>Semana</option>
                                <option value="30" tsq>Mes</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Correlación máxima:</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>Cerrar</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>

 

Lo importante aquí es el id filterByCorrelationButtonModal que se utiliza en la primera línea. Es importante porque esta ventana emergente se hace referencia a continuación por este id en la función JavaScript en module.js.

Cuando desarrolles tu propio plugin debes crear tu propio ID único del popup, y usar este id en módulo.js - ver más adelante.

 

Otra especialidad es que utilizamos onClick() JavaScript para capturar el envío del formulario, y luego usaremos JS simple para recuperar los valores configurados.

A continuación, se envían al backend mediante un método especial CustomPluginController.sendRequest() que realiza una petición a /filtroPorCorrelación/filtro donde pasa los parámetros del formulario y su valor de retorno se muestra en alerta.

 

 

Definición del plugin en module.js

El plugin necesita un módulo.js para que sea reconocido correctamente e incorporado a la interfaz de usuario.

Se trata de código JavaScript Angular, pero no es necesario tener conocimientos detallados de Angular, puede utilizar este plugin de ejemplo y simplemente copiar y pegar + modificarlo si ae la aplicación de su propio plugin.

Todo el código de module.js es:

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

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

    sqPluginProvider.plugin("ResultsDatabankAction", 100, {
        title: Ltsq("Herramientas:Filtrar por correlación"),
        class: 'btn btn-normal btn-default',
        controlador: buttonController,
        id: "databank-action-filterbycorrelation"
    });
    
    sqPluginProvider.addPopupWindow("plugins/FilterByCorrelation/popup.html", null, 'SQUANT');

});

 

Repasemos el código parte por parte.

La primera parte es registrar nuestro nuevo plugin como módulo Angular:

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

 

Por lo general, sólo es necesario darle un nombre único, como app.resultsdatabankactions.filterByCorrelation.
Internamente utilizamos la convención app.resultsdatabankaction.YourUniqueName

 

La segunda parte es la creación de un controlador de botón y callback que mostrará el diálogo emergente.

function callback(projectName, taskName, databankName, selectedStrategies){
    window.parent.filterByCorrelationData = {
        projectName,
        taskName,
        databankName
    };
    window.parent.showPopup("#filterByCorrelationButtonModal");
}

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

 

Lo importante aquí es llamar al método window.parent.showPopup() al que se le da un ID único del popup que se definió previamente en popup.html. Como su nombre indica, al llamarlo se mostrará el popup y nuestro formulario,

y el controlador del botón lo invoca cuando se hace clic en el botón.

 

La tercera parte consiste en registrar este plugin y definir dónde se mostrará:

sqPluginProvider.plugin("ResultsDatabankAction", 100, {
    title: Ltsq("Filtrar por correlación"),
    class: 'btn btn-normal btn-default',
    controlador: buttonController,
    id: "databank-action-filterbycorrelation"
});

 

Aquí decimos que está conectado a ResultadosBancoDeDatosAcción punto de extensión, lo que significa que se mostrará como un botón en el banco de datos.

 

Lo interesante aquí es el título: si no desea que se muestre como un botón independiente, sino como un elemento más de los ya existentes Herramientas puede utilizar simplemente el título:

título: Ltsq("Herramientas:Filtrar por correlación"),

También puedes ver que hemos definido el controlador de botón y lo hemos establecido en nuestro buttonController definido anteriormente.

 

La última parte es registrar nuestro archivo UI en el sistema pugin de SQX - esto asegurará que el archivo pooup.html sea cargado y se convierta en parte del UI.

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

 

 

Implementación del backend - FilterByCorrelationServlet.java

Ahora que tenemos la interfaz de usuario, tenemos que implementar la funcionalidad deseada en el backend.

El backend está hecho como un servlet HTTP que procesa peticiones HTTP desde el frontend (UI) y realiza la funcionalidad requerida. Si recuerdas, definimos esta petición en el archivo popup.html, utiliza un método especial CustomPluginController.sendRequest() que realiza una petición a /filtroPorCorrelación/filtro.

Así que tenemos que implementar un servlet que maneje esta petición y haga lo que queremos.

El servlet debe implementar IServletPlugin interfaz. El método importante aquí es getHandler()que se ejecuta cuando el backend recibe una solicitud de /filtroPorCorrelación/.

A continuación, nuestra FiltrarPorCorrelación clase interna que implementa la funcionalidad.

 

Así que la parte del fragmento que sólo maneja la solicitud y le asigna un controlador es:

@PluginImplementación
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() {
        return 0;
    }

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

    @Override
    public void initPlugin() lanza Excepción {
    }

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

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

 

Clase interna FilterByCorrelation

Esta es la clase que gestiona la solicitud /filtroPorCorrelación/filtro de la interfaz de usuario.

La primera parte de esta clase comprueba a qué comando se ha llamado exactamente - en nuestro caso sólo hay filtro mando.

class FiltroPorCorrelación extends HttpJSONServlet {
    @Override
    protected String execute(String command, Map args, String method){
        switch(comando) {
            case "filtro": return onFilter(args);
            por defecto
                return apiErrorJSON("Error de ejecución. Comando desconocido '"+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("éxito", "Estrategias filtradas, "+removedCount+" estrategias eliminadas");
            return response.toString();
        }
        catch(Exception e){
            Log.error("Error al filtrar estrategias por correlación", e);
            response.put("error", "Error al filtrar - " + e.getMessage());
            return response.toString();
        }
    }

 

Al recibir esta orden ejecutará onFilter() método que:

  • obtiene parámetros como el nombre del proyecto, el nombre del banco de datos, el período y la correlación máxima a partir de los parámetros de la solicitud.
  • realiza el filtrado real por correlación llamando al método filterByCorrelation(projectName, databankName, period, maxCorrelation)
  • entonces enviará un mensaje con el número real de estrategias eliminadas o un mensaje de error en caso de error.

 

 

método filterByCorrelation()

este método realiza el filtrado real: recorre todas las estrategias de la base de datos real, calcula las correlaciones entre las estrategias y elimina las estrategias en las que la correlación > maxCorrelation.

La correlación informática merece un artículo y un ejemplo por sí misma, puedes seguir cómo se implementa en el código:

    private int filterByCorrelation(String projectName, String databankName, int period, double maxCorrelation) throws Exception {
        Log.info("Filtrando por correlación (Proyecto: " + projectName + ", banco de datos: " + databankName + ")...");
        SQProject proyecto = ProjectEngine.get(projectName);
        Databank databank = project.getDatabanks().get(databankName);
        int removedCount = 0;

        // Buscar estrategias correlacionadas utilizando la puntuación de aptitud como una lista de clasificación/prioridad
        ArrayList rgSortedCandidates = new ArrayList(databank.getRecords());

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

        ArrayList rgSortedCandidatesRanked = new ArrayList();

        for(GrupoResultados rgSC : rgCandidatosSeleccionados) {
            rgSortedCandidatesRanked.add(0, rgSC);
        }

        ArrayList rgUncorrelated = new ArrayList();
        // todo: varias estrategias podrían tener la misma aptitud. esto debería abordarse utilizando una ordenación adicional (es decir, aptitud > pf > ret/dd .. etc)

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

            Log.info("Estrategia de enfoque para comparar: {}", focusStratName);

            rgUncorrelated.add(focusStrat.clone()); // añadir la mejor estrategia del grupo a la lista no correlacionada como punto de partida
            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();

                    // calcular los periodos de correlación
                    CorrelationPeriods correlationPeriods = precomputePeriodsAP(focusStrat, subStrat, period, corrType);
                    double corrValue = SQUtils.round2(correlationComputer.computeCorrelation(false, focusStratName, subStratName, focusStrat.orders(), subStrat.orders(), correlationPeriods));

                    Log.info(" - Correlación de '{}'' con la estrategia focus: {}", subStratName, corrValue);

                    if (corrValue > maxCorrelation) {
                        it.remove();
                        removedCount++;
                        Log.info(" - Eliminar estrategia " + subStratName + " (correlación " + 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());
        if (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);

        return correlationPeriods;
    }        
}

 

 

Trabajar con plugins en el Editor de código

Hemos añadido una nueva funcionalidad para trabajar con plugins definidos por el usuario en SQX Build 136 Dev 2.

Si ha descomprimido el plugin adjunto en la carpeta correcta ({Instalación de SQ}/usuario/extender/Plugins) y abre el Editor de Código verás una nueva sección de Plugins con nuestro nuevo plugin allí:

 

Normalmente puedes crear y editar archivos y carpetas allí como de costumbre. La novedad es la posibilidad de compilar el plugin.

Haga clic con el botón derecho en la carpeta del plugin y seleccione Compilar plugin:

 

El plugin se compilará y aparecerá un nuevo archivo FilterByCorrelation.jar en la carpeta.

Ahora reinicie StrategyQuant y debería ver un botón de nuevo banco de datos:

 

Al hacer clic en él, se abrirá el cuadro de diálogo que se muestra a continuación:

Configure los ajustes de filtrado y haga clic en el botón Filtrar. Si usted tiene algunas estrategias en el banco de datos que usted puede notar que algunos de ellos serán eliminados y se muestra la alerta:

 

Así que nuestro plugin funcionó - calculó y comparó las correlaciones entre todas las estrategias en la base de datos actual y eliminó 5 estrategias que tenían una correlación mayor que la correlación máxima configurada.

 

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

Suscríbase a
Notificar a
8 Comentarios
Más antiguo
Más reciente Más votados
Feedbacks de Inline
Ver todos los comentarios
Emmanuel
22. 6. 2022 12:53 pm

¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡Excelente !!!!!!!!!!!!!!!!!!!!!!!!!!!! Muchas gracias por esta mejora . ¡¡¡!!!
Está abriendo la puerta a un nuevo desarrollo.

Emmanuel
24. 6. 2022 13:58

Esta es la primera vez que vemos un ejemplo de plug in, esto es realmente útil para entender cómo funcionan el plug in y la correlación. Necesitamos más plug in de ejemplo. Con ejemplos, somos capaces de desarrollar y compartir plug in también. Gracias por este ejemplo.

binhsir
3. 7. 2022 7:42 am

Deduplicación basada en la correlación de estrategias, que es una gran característica.Tengo que decir :escalabilidad de SQX es impresionante a pesar de que no soy un desarrollador muy bueno.

Jordan
22. 9. 2022 2:42 pm

Querido Mark, gracias, te lo agradezco.

Jordan
22. 9. 2022 2:50 pm

Al mismo tiempo, espero que esta función pueda utilizarse como una tarea disponible en los proyectos personalizados.

tnickel
17. 2. 2023 21:47

He probado este plugin con SQ 4.137 Dev2, funciona bien. Estoy feliz, esta característica debe estar en la próxima versión de la SQ.

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

Sería útil si este "filtro por correlación" estuviera disponible como nueva tarea en un flujo de trabajo como bloque. Así se podrían automatizar muchas cosas.

ytu
ytu
Responder a  tnickel
24. 6. 2023 5:29 am

¡Fantástica idea!