Documentação

Aplicações

Última atualização em 21. 6. 2022 por Mark Fric

Filtrar por correlação - exemplo de plugin

Nota

  • Esse exemplo e a funcionalidade que ele requer são adicionados somente ao SQ Build 136 Dev 2 ou posterior
  • A funcionalidade descrita é apenas para usuários "avançados" e dedicados. Ela não está completamente documentada; este é o primeiro exemplo que mostra essa possibilidade.

 

Este é um exemplo de como estender a funcionalidade do SQX com seu próprio plug-in.

O StrategyQuant X foi criado como um sistema baseado em plugins, cada parte do programa é um plugin. No entanto, estender o SQX com seu próprio plug-in não era possível até agora (Build 136 Dev 2), porque você precisava ter acesso ao código-fonte completo e não tinha como compilar seu plug-in.

A interface do usuário do SQX é basicamente um aplicativo da Web escrito em AngularJS, em que o backend está em servlets Java.

Neste exemplo, mostraremos passo a passo como criar um plug-in simples, incluindo uma interface de usuário simples.

O plug-in completo também está disponível como um arquivo ZIP anexado a este artigo.

 

Esse plug-in implementará uma funcionalidade de filtragem de estratégias por correlação. Ele funciona da seguinte forma:

  • plugin, ele adicionará um novo botão Filtrar por correlação para o banco de dados
  • Ao clicar no botão, uma caixa de diálogo pop-up é exibida, na qual o usuário configura a correlação máxima permitida
  • Quando confirmado, o plug-in chamará seu método de backend e examinará todas as estratégias no banco de dados real, filtrando (removendo) as que tiverem correlação maior que a máxima com outras estratégias nesse banco de dados

 

Créditos - este plug-in é baseado no código postado por AgentePot no Discord como parte de nosso sessões de codificação ao vivoO código é usado com seu consentimento.

 

Localização dos plug-ins do usuário

Os plug-ins desenvolvidos pelo usuário devem estar localizados na pasta {Instalação do SQ}/user/extend/Plugins. Você pode fazer o download do arquivo ZIP desse plug-in e extraí-lo para essa pasta.

Ao abrir o CodeEditor, você deverá ver a pasta desse plug-in:

 

Esse plug-in consiste em três arquivos:

  • FilterByCorrelationServlet.java - arquivo java que implementa a funcionalidade de filtragem de estratégias por correlação.
  • module.js - Arquivo JavaScript que contém a definição do módulo no Angular
  • popup.html - Arquivo HTML que contém a interface do usuário desse novo plug-in

Vamos examiná-los um a um, começando pelo mais simples.

 

 

Definição da interface do usuário para o plug-in - popup.html

A interface do usuário do plug-in é definida no arquivo popup.html. É um arquivo HTML padrão que é exibido em uma janela modal.

Há DIVs para decorar a janela, mas o "formulário" real é definido dentro do 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 correlação</h4>
            </div>
            <div class="modal-body" style="height: auto;">
                Funcionalidade - todas as estratégias que tiverem correlação maior que Max serão removidas do banco de dados atual.
                <label tsq>Configurações de filtragem</label>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Período de correlação:</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>Dia</option>
                                <option value="20" tsq>Semana</option>
                                <option value="30" tsq>Mês</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="col-sm-6">
                            <label class="sqn-label" tsq>Correlação 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>Fechar</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>

 

O importante aqui é o id filterByCorrelationButtonModal que é usado na primeira linha. Isso é importante porque esse pop-up é referenciado por esse ID na função JavaScript em module.js.

Quando estiver desenvolvendo seu próprio plug-in, você deverá criar seu próprio ID exclusivo do pop-up e usar esse ID em module.js - ver mais tarde.

 

Outra especialidade é que usamos onClick() evento JavaScript para capturar o envio do formulário e, em seguida, usaremos JS simples para recuperar os valores configurados.

Em seguida, eles são enviados para o backend usando um método especial CustomPluginController.sendRequest() que faz uma solicitação para /filterByCorrelation/filtro em que ele passa os parâmetros do formulário e seu valor de retorno é exibido em um alerta.

 

 

Definição do plug-in em module.js

O plug-in precisa de um module.js para que ele seja devidamente reconhecido e incorporado à interface do usuário.

É um código Angular em JavaScript, mas você não precisa ter conhecimento detalhado do Angular. Você pode usar esse plug-in de exemplo e simplesmente copiar e colar + modificá-lo se estiver implementando seu próprio plug-in.

O código completo do module.js é:

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("Tools:Filter by correlation"),
        class: 'btn btn-normal btn-default',
        controlador: buttonController,
        id: "databank-action-filterbycorrelation"
    });
    
    sqPluginProvider.addPopupWindow("plugins/FilterByCorrelation/popup.html", null, 'SQUANT');

});

 

Vamos analisar o código parte por parte.

A primeira parte é registrar nosso novo plug-in como módulo Angular:

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

 

Geralmente, você só precisa dar a ele um nome exclusivo, como app.resultsdatabankactions.filterByCorrelation.
Internamente, usamos a convenção app.resultsdatabankaction.YourUniqueName

 

A segunda parte é criar um controlador de botão e um retorno de chamada que exibirá a caixa de diálogo pop-up.

função 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();

 

O importante aqui é chamar o método window.parent.showPopup() ao qual você fornece um ID exclusivo do pop-up que foi definido anteriormente em popup.html. Como o nome sugere, chamar isso mostrará o popup e o nosso formulário,

e o controlador do botão o invoca quando o botão é clicado.

 

A terceira parte é registrar esse plug-in e definir onde ele será exibido:

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

 

Aqui dizemos que ele está conectado a ResultsDatabankAction ponto de extensão, o que significa que ele será exibido como um botão no banco de dados.

 

O que é interessante aqui é o título - se você não quiser que ele seja exibido como um botão separado, mas como outro item no Ferramentas você pode simplesmente usar o título:

título: Ltsq("Tools:Filter by correlation"),

Você também pode ver que definimos o controlador de botão e o configuramos para o nosso buttonController definido anteriormente.

 

A última parte é o registro do nosso arquivo de interface do usuário no sistema SQX pugin - isso garantirá que o arquivo pooup.html seja carregado e se torne parte da interface do usuário.

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

 

 

Implementação do backend - FilterByCorrelationServlet.java

Agora que temos a interface do usuário, precisamos implementar a funcionalidade desejada no backend.

O backend é feito como um servlet HTTP que processa solicitações HTTP do frontend (UI) e executa a funcionalidade necessária. Se você se lembrar, definimos essa solicitação no arquivo popup.html, que usa um método especial CustomPluginController.sendRequest() que faz uma solicitação para /filterByCorrelation/filtro.

Portanto, temos que implementar um servlet que lide com essa solicitação e faça o que desejamos.

O servlet deve implementar IServletPlugin interface. O método importante aqui é getHandler()que é chamado quando o backend recebe uma solicitação para /filterByCorrelation/.

A solicitação é então tratada por nosso FilterByCorrelation classe interna que implementa a funcionalidade.

 

Portanto, a parte do snippet que apenas trata a solicitação e atribui um manipulador a ela é:

@PluginImplementation
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() throws Exception {
    }

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

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

 

Classe interna FilterByCorrelation

Esta é a classe que trata a solicitação /filterByCorrelation/filtro da interface do usuário.

A primeira parte dessa classe verifica qual comando exatamente foi chamado - em nosso caso, há apenas 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("Falha na execução. comando desconhecido '"+command+"'.", 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", "Strategies filtered, "+removedCount+" strategies removed.");
            return response.toString();
        }
        catch(Exception e){
            Log.error("Error while filtering strategies by correlation", e);
            response.put("error", "Filtering failed - " + e.getMessage());
            return response.toString();
        }
    }

 

Ao receber esse comando, ele executará onFilter() método que:

  • obtém parâmetros como nome do projeto, nome do banco de dados, período e maxCorrelation dos parâmetros da solicitação
  • realiza a filtragem real por correlação chamando o método filterByCorrelation(projectName, databankName, period, maxCorrelation)
  • ele enviará de volta uma mensagem com o número real de estratégias removidas ou uma mensagem de erro em caso de erro.

 

 

método filterByCorrelation()

esse método executa a filtragem real, analisando todas as estratégias no banco de dados real, calculando as correlações entre as estratégias e removendo as estratégias em que a correlação é maior que maxCorrelation.

A correlação de computação merece um artigo e um exemplo por si só, mas você pode acompanhar como ela é implementada no código:

    private int filterByCorrelation(String projectName, String databankName, int period, double maxCorrelation) throws Exception {
        Log.info("Filtragem por correlação (Projeto: " + projectName + ", banco de dados: " + databankName + ")...");
        SQProject project = ProjectEngine.get(projectName);
        Banco de dados databank = project.getDatabanks().get(databankName);
        int removedCount = 0;

        // Verificação de estratégias correlacionadas usando a pontuação de adequação como uma lista de classificação/prioridade
        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();
        // Isso deve ser resolvido usando uma classificação adicional (ou seja, fitness > pf > ret/dd ... etc.)

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

            Log.info("Estratégia de foco para comparar: {}", focusStratName);

            rgUncorrelated.add(focusStrat.clone()); // adiciona a melhor estratégia do conjunto à lista não correlacionada como ponto de partida
            rgSortedCandidatesRanked.remove(0);

            Iterator it = rgSortedCandidatesRanked.iterator();

            while(it.hasNext()) {
                ResultsGroup subStrat = it.next();

                String subStratName = subStrat.getName();

                se (subStratName != focusStratName) {
                    CorrelationPeriods corrPeriods = new CorrelationPeriods();

                    CorrelationType corrType = CorrelationTypes.getInstance().findClassByName("ProfitLoss");
                    CorrelationComputer correlationComputer = new CorrelationComputer();

                    // computar períodos de correlação
                    CorrelationPeriods correlationPeriods = precomputePeriodsAP(focusStrat, subStrat, period, corrType);
                    double corrValue = SQUtils.round2(correlationComputer.computeCorrelation(false, focusStratName, subStratName, focusStrat.orders(), subStrat.orders(), correlationPeriods));

                    Log.info(" - Correlação de '{}'' com a estratégia de foco: {}", subStratName, corrValue);

                    se (corrValue > maxCorrelation) {
                        it.remove();
                        removedCount++;
                        Log.info(" - Removendo a estratégia " + subStratName + " (correlação " + corrValue + " > " + maxCorrelation + ")...");
                        banco de dados.remove(subStratName, true, 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);

        return correlationPeriods;
    }        
}

 

 

Trabalhar com plug-ins no Code Editor

Adicionamos uma nova funcionalidade para trabalhar com plug-ins definidos pelo usuário no SQX Build 136 Dev 2.

Se você descompactou o plug-in anexado na pasta correta ({SQ installation}/user/extend/Plugins) e abra o Code Editor, você verá uma nova seção Plugins com nosso novo plug-in:

 

Normalmente, você pode criar e editar arquivos e pastas lá, como de costume. O novo recurso é a possibilidade de compilar o plug-in.

Clique com o botão direito do mouse na pasta do plug-in e selecione Compilar plug-in:

 

O plug-in será compilado e um novo arquivo FilterByCorrelation.jar aparecerá na pasta.

Agora reinicie o StrategyQuant e você verá um novo botão de banco de dados:

 

Quando você clicar nele, deverá abrir a caixa de diálogo mostrada abaixo:

Defina as configurações de filtragem e clique no botão Filter (Filtrar). Se você tiver algumas estratégias no banco de dados, poderá notar que algumas delas serão removidas e um alerta será exibido:

 

Portanto, nosso plug-in funcionou: ele calculou e comparou as correlações entre todas as estratégias no banco de dados atual e removeu cinco estratégias que tinham correlação maior do que a correlação máxima configurada.

 

Este artigo foi útil? O artigo foi útil O artigo não foi útil

Assine
Notificação de
8 Comentários
Mais antigo
Novidades Mais Votados
Feedbacks em linha
Ver todos os comentários
Emmanuel
22. 6. 2022 12:53 pm

Excelente !!!!!!!!!!!!!!!!!!!!!!!!!!!! Muito obrigado por esse aprimoramento. !!!
Isso está abrindo portas para novos desenvolvimentos!

Emmanuel
24. 6. 2022 1:58 pm

Esta é a primeira vez que vemos um exemplo de plug-in, que é realmente útil para entender como o plug-in e a correlação estão funcionando. Precisamos de mais exemplos de plug-ins. Com exemplos, podemos desenvolver e compartilhar plug-ins também. Obrigado por esse exemplo

binhsir
3. 7. 2022 7:42 am

Deduplicação baseada na correlação de estratégias, que é um ótimo recurso. Devo dizer que a escalabilidade do SQX é impressionante, embora eu não seja um desenvolvedor muito bom.

Jordânia
22. 9. 2022 2:42 pm

Prezado Mark,Obrigado, eu agradeço.

Jordânia
22. 9. 2022 2:50 pm

Ao mesmo tempo, espero que essa função possa ser usada como uma tarefa disponível nos projetos personalizados.

tníquel
17. 2. 2023 9:47 pm

Testei esse plug-in com o SQ 4.137 Dev2 e ele está funcionando bem. Estou satisfeito, pois esse recurso deve estar na próxima versão do SQ.

tníquel
Responder a  tníquel
26. 4. 2023 2:22 pm

Seria útil se esse "filtro por correlação" estivesse disponível como uma nova tarefa em um fluxo de trabalho como um bloco. Muitas coisas podem ser automatizadas.

ytu
ytu
Responder a  tníquel
24. 6. 2023 5:29 am

Ideia fantástica!!!