Documentação
Aplicações
Última atualização em 21. 6. 2022 por Mark Fric
Filtrar por correlação - exemplo de plugin
Conteúdo da página
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
Excelente !!!!!!!!!!!!!!!!!!!!!!!!!!!! Muito obrigado por esse aprimoramento. !!!
Isso está abrindo portas para novos desenvolvimentos!
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
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.
Prezado Mark,Obrigado, eu agradeço.
Ao mesmo tempo, espero que essa função possa ser usada como uma tarefa disponível nos projetos personalizados.
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.
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.
Ideia fantástica!!!