Pular para o conteúdo principal

Guia de uso da API de Complementos do Bookmap para Framework Simplificado

Este tutorial abrange a maioria dos tópicos da API para criar um complemento rápido e confiável para o Bookmap, com qualquer nível de sofisticação na lógica de negócios. O tutorial começa com a instalação do ambiente de desenvolvimento e termina com um complemento ofuscado e licenciado oferecido no marketplace. Pressupõe-se apenas conhecimento básico de Java e alguma compreensão de Mecânica de Mercado. Desenvolvedores avançados podem ignorá-lo por completo ou usá-lo apenas como referência conforme necessário. O tutorial também fornece um framework com um conjunto de ferramentas que podem ajudar a projetar e implementar uma ampla gama de complementos.

O resultado deste tutorial é um complemento real [TODO: nome] que é oferecido gratuitamente no marketplace [TODO: link] e todo o seu código-fonte está publicado [TODO: aqui].

Ambiente & Pré-requisitos

Os desenvolvedores não estão restritos a um ambiente de desenvolvimento particular. Aqui usamos o seguinte arsenal de ferramentas:

Construir projeto demo existente

Para validar o ambiente de desenvolvimento, construa um projeto demo a partir do código-fonte. Abra uma janela de prompt de comando (no Windows, seja cmd.exe ou PowerShell), navegue até uma pasta vazia e execute:

git clone https://github.com/BookmapAPI/DemoStrategies.git
cd DemoStrategies/Strategies
gradle jar

Isso criará um arquivo jar em ./build/libs/

Instalar o complemento no Bookmap

  • Inicie o Bookmap em modo Realtime ou Replay [TODO: fornecer exemplos específicos. BTC em tempo real?]
  • Abra a janela de complementos clicando no botão Configure API plugins ou via menu: Settings -> Api plugins configuration
  • Clique em Add.., selecione o arquivo jar, depois selecione o complemento que deseja carregar. Note que um único arquivo jar pode conter vários complementos (todos podem ser adicionados um por um)
  • Ative / Desative o complemento usando a caixa de seleção correspondente.
  • Pressione o botão perto da caixa de seleção. Isso traz para frente o painel de configurações do complemento (se implementado pelo complemento). Ele também contém um botão "Remove" que desinstala o complemento.

Assumindo que o teste foi bem-sucedido, vamos construir um novo complemento do zero. Caso contrário [TODO: solução de problemas].

Complemento "Hello Bookmap" em 2 minutos

Criar um projeto

Crie uma pasta, por exemplo, C:\dev\bookmap-addons\bookmap-api-tutorial. Navegue até ela com a janela de prompt de comando e execute:

gradle init --type java-library

Pressione Enter sempre que for solicitado para selecionar opções de configuração do projeto (usaremos as opções padrão).

Importar o projeto para o Eclipse

No Eclipse selecione File -> Import -> Gradle -> Existing Gradle Project, e use o assistente para selecionar a localização e importar o projeto.

Para iniciantes: Por padrão, o Gradle cria um arquivo Java de exemplo Library.java e um arquivo de teste correspondente. Exclua esses arquivos e todo o pacote de teste, pois não os usaremos neste tutorial.

Importar o projeto para o Intellij IDEA

Se nenhum projeto estiver atualmente aberto no IntelliJ IDEA, clique em Open or Import na tela de boas-vindas. Caso contrário, selecione File | Open no menu principal. Na caixa de diálogo que se abre, selecione um diretório que contenha um projeto Gradle e clique em OK. O IntelliJ IDEA abre e sincroniza o projeto na IDE.

Dependências da API do Bookmap

Encontre o arquivo build.gradle na visualização do projeto no Eclipse ou Intellij e substitua todo o seu conteúdo pelo seguinte:

build.gradle

apply plugin: 'java'
apply plugin: 'eclipse'

repositories {
mavenCentral()
maven {
url "https://maven.bookmap.com/maven2/releases/"
}
}

eclipse.classpath.downloadJavadoc = true

dependencies {
compileOnly group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
compileOnly group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';
}

Avançado: Se seu complemento requer dependências adicionais, inclua-as de acordo com as regras comentadas neste exemplo:

build.gradle

dependencies {
// Use "compileOnly" para bibliotecas da API do Bookmap e para qualquer biblioteca externa que esteja
// incluída no tempo de execução do Bookmap. Essas bibliotecas estão localizadas em C:\Program Files\Bookmap\lib
compileOnly group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
compileOnly group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';

compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5';
compileOnly group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'

// Use "implementation" para qualquer outra dependência externa
implementation group: 'com.esotericsoftware', name: 'kryo', version: '5.0.0-RC4';

// Se você deseja executar o código da IDE sem o Bookmap, por exemplo, para teste ou depuração,
// marque todas as dependências "compileOnly" como "runtime" também
runtime group: 'com.bookmap.api', name: 'api-core', version: '7.1.0.35';
runtime group: 'com.bookmap.api', name: 'api-simplified', version: '7.1.0.35';

runtime group: 'com.google.code.gson', name: 'gson', version: '2.8.5';
runtime group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
}

Agora estamos prontos para criar alguns complementos.

Para iniciantes (Eclipse): Após atualizar o arquivo build.gradle, clique com o botão direito no projeto e clique em Gradle -> Refresh Gradle Project. Alternativamente, configure a atualização/sincronização automática: clique em Windows -> Preferences, digite "gradle" no campo de pesquisa e marque a caixa de seleção Automatic....

Para iniciantes (Intellij): Após atualizar o arquivo build.gradle, clique no ícone Load gradle changes que aparece sobre o código, ou pressione o atalho Ctrl+Shift+O no teclado.

Código Java "Hello Bookmap"

O mínimo necessário para uma classe ser um complemento do Bookmap é:

  • ter as anotações @Layer1SimpleAttachable e @Layer1ApiVersion
  • implementar a interface CustomModule ou o adaptador correspondente CustomModuleAdapter, que tem a implementação padrão (vazia).

HelloBookmapApiMinimum.java

@Layer1SimpleAttachable
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class HelloBookmapApiMinimum implements CustomModuleAdapter {
}

Vamos também implementar outro complemento Hello que registre "Hello" e "Bye" durante sua ativação e desativação, respectivamente. Log é um singleton embutido acessível aos complementos.

HelloBookmapApiWithLogs.java

@Layer1SimpleAttachable
@Layer1StrategyName("Hello Bookmap API with logs")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class HelloBookmapApiWithLogs implements CustomModule {

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
Log.info("Hello");
}

@Override
public void stop() {
Log.info("Bye");
}
}

Construir

Use a mesma janela de prompt de comando (presumindo que você não a fechou) e execute:

gradle jar

Isso cria um arquivo jar \build\libs\bookmap-api-tutorial.jar que inclui ambos os nossos complementos Hello....

Instalar o complemento no Bookmap

Refira-se à seção anterior aqui e instale os complementos, um por um. Observe que porque HelloBookmapApiMinimum.java não tem a anotação @Layer1StrategyName, o Bookmap o nomeia pelo seu nome de caminho completo.

Ative/desative ambos os complementos usando as caixas de seleção correspondentes. Para remover um complemento, desinstale-os usando o botão "Remove".

Você pode encontrar as entradas de log "Hello" e "Bye" no arquivo de log em C:\Bookmap\Logs ou via File -> Show log file.

Depuração

[TODO: também lembre-se de descarregar complementos para edição/reconstrução]

Dados de mercado

Complementos podem se inscrever para vários tipos de dados de mercado simultaneamente. Cada tipo de dado de mercado é representado por uma interface de ouvinte dedicada que determina as callbacks para esse tipo de dado. Cada interface de ouvinte tem a interface de adaptador correspondente com implementação padrão (vazia) dessas callbacks, por exemplo, TradesListener e TradesAdapter, TimeListener e TimeAdapter, CustomModule e CustomModuleAdapter, etc. Aqui nós cobriremos a maioria das interfaces de ouvintes uma por uma:

Ouvinte de timestamp

Complementos que implementam TimeListener receberão a callback onTimestamp antes de qualquer outra atualização. O timestamp fornecido por último é o verdadeiro timestamp para todas as atualizações seguintes.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TimeListener {

private long timestamp; // mantém o verdadeiro timestamp de qualquer outro evento de dados

@Override
public void onTimestamp(long nanoseconds) {
timestamp = nanoseconds;
}
}

Market by Price (MBP)

Inscreva-se para dados MBP implementando DepthDataListener. Aqui, também usamos as atualizações MBP através da callback onDepth e gerenciamos o Livro de Ofertas.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, DepthDataListener {

private final TreeMap<Integer, Integer> bids = new TreeMap<>(Comparator.reverseOrder());
private final TreeMap<Integer, Integer> asks = new TreeMap<>();

@Override
public void onDepth(boolean isBid, int price, int size) {
TreeMap<Integer, Integer> book = isBid ? bids : asks;
if (size == 0) {
book.remove(price);
} else {
book.put(price, size);
}
}
}

O Livro de Ofertas consiste em coleções de lances e ofertas de preços exclusivos e tamanho correspondente por preço, que é a soma dos tamanhos de todas as ordens de Compra ou Venda, respectivamente, nesse preço. Ambos os lados do Livro de Ofertas estão ordenados por preço, mas o lado de Lances está ordenado em ordem decrescente. Isso permite iterar sobre o lance e a oferta a partir do topo do livro ou obter o melhor preço/tamanho conforme o seguinte:

private void demoBestPriceSize() {
int bestBidPrice = bids.firstKey();
int bestAskPrice = asks.firstKey();
int bestBidSize = bids.firstEntry().getValue();
int bestAskSize = asks.firstEntry().getValue();
}

private int demoSumOfPriceLevels(boolean isBid, int numLevelsToSum) {
int sizeOfTopLevels = 0;
for (Integer size : (isBid ? bids : asks).values()) {
if (--numLevelsToSum < 0) {
break;
}
sizeOfTopLevels+= size;
}
return sizeOfTopLevels;
}

Dependendo da bolsa e do fornecedor de dados, alguns instrumentos podem fornecer apenas um número limitado de níveis de profundidade de mercado (por exemplo, 10 ou 20) em vez da profundidade de mercado completa. Além disso, em geral, o Livro de Ofertas pode estar vazio, por exemplo, fora do horário de negociação, ou justamente antes do complemento receber o instantâneo inicial, ou durante a reconstrução do Livro de Ofertas.

Market by Order (MBO)

Dados MBO são um tipo de dados Ordem-por-Ordem fornecido pela bolsa CME desde 2017. Existem múltiplas vantagens do MBO sobre o MBP, por exemplo, torna possível (não garantido) identificar ordens Iceberg e execuções de ordens Stop. Múltiplos fornecedores de dados fornecem dados da CME, mas atualmente (julho de 2020) apenas a Rithmic fornece os dados MBO.

Complementos podem receber dados MBO implementando 3 callbacks da interface MarketByOrderDepthDataListener conforme o seguinte:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, MarketByOrderDepthDataListener {

private final OrderBookMbo orderBook = new OrderBookMbo();

@Override
public void send(String orderId, boolean isBid, int price, int size) {
orderBook.send(orderId, isBid, price, size);
}

@Override
public void replace(String orderId, int price, int size) {
orderBook.replace(orderId, price, size);
}

@Override
public void cancel(String orderId) {
orderBook.cancel(orderId);
}
}

A implementação do Livro de Ofertas MBO é um pouco mais complicada do que o Livro de Ofertas MBP. Portanto, a API expõe uma implementação de tal classe: ◦velox.api.layer1.layers.utils.mbo.OrderBookMbo, que é usada no código acima.

No entanto, se você precisar de mais controle e acesso sobre o Livro de Ofertas, pode implementá-lo você mesmo. Aqui está um exemplo de tal classe:

OrderBook.java

public class OrderBook implements MarketByOrderDepthDataListener {

public static class Order {

public String orderId;
public boolean isBid;
public int price;
public int size;

public Order(String orderId, boolean isBid, int price, int size) {
this.orderId = orderId;
this.isBid = isBid;
this.price = price;
this.size = size;
}
}

public final TreeMap<Integer, Map<String, Order>> asks = new TreeMap<>((a, b) -> a - b);
public final TreeMap<Integer, Map<String, Order>> bids = new TreeMap<>((a, b) -> b - a);
public final Map<String, Order> orders = new HashMap<>();

@Override
public void send(String orderId, boolean isBid, int price, int size) {
Order order = new Order(orderId, isBid, price, size);
orders.put(orderId, order);
(isBid ? bids : asks).computeIfAbsent(price, k -> new LinkedHashMap<>()).put(orderId, order);
}

@Override
public void replace(String orderId, int price, int size) {
Order order = orders.get(orderId);
if (price == order.price && size < order.size) {
order.size = size;
} else { // se o tamanho do pedido aumentar, mova o pedido para o fim da fila independentemente da mudança de preço
cancel(orderId);
send(orderId, order.isBid, price, size);
}
}

@Override
public void cancel(String orderId) {
Order order = orders.remove(orderId);
Map<String, Order> priceLevel = (order.isBid ? bids : asks).get(order.price);
priceLevel.remove(orderId);
if (priceLevel.isEmpty()) {
(order.isBid ? bids : asks).remove(order.price);
}
}
}

Similar ao MBP, aqui você pode iterar sobre os lados de lance e oferta do Livro de Ofertas a partir do topo do livro. A diferença é que cada nível de preço não é apenas o tamanho total de suas ordens, mas uma fila de ordens individuais armazenada em LinkedHashMap. Isso permite iterar sobre a fila de ordens como usando uma List, isto é, de acordo com a ordem de inserção das ordens (como deve ser de acordo com o Algoritmo de Correspondência de Prioridade de Tempo de Preço), mas também encontrar e remover uma ordem da fila quase tão rápido quanto usando um HashMap.

Este Livro de Ofertas MBO pode processar ~4 milhões de atualizações por segundo em um laptop comum. Considerando que mesmo os instrumentos mais ativos, como futuros ES, têm 5-10 milhões de atualizações por dia, esta implementação é suficientemente rápida. Mas não é a implementação mais rápida. Você está convidado a desafiar-se melhorando seu desempenho.

Dica: Você pode computar o tamanho total em um nível de preço conforme o seguinte:

public int getTotalSizeAtPrice(boolean isBid, int price) {
return (isBid ? bids : asks).get(price).values().stream().mapToInt(order -> order.size).sum();
}

No entanto, se você precisar acessar esse método com frequência, considere encapsular o mapa de ordens do nível de preço em outra classe, por exemplo PriceLevel, que armazena em cache o tamanho total quando qualquer uma das ordens é atualizada.

Observe que os dados MBO podem ser convertidos em MBP, mas não o contrário. Portanto, quando o Bookmap está conectado aos dados da Rithmic CME (que são MBO), os complementos podem se inscrever para dados MBO ou MBP ou ambos (os dados MBP são gerados pelo Bookmap a partir do MBO). Mas quando o Bookmap está conectado a dados MBP, os complementos não recebem nenhum dado através das callbacks de MarketByOrderDepthDataListener.

Inscrição direta de dados de mercado

Provavelmente, você percebeu que a classe OrderBook.java acima implementa por si mesma MarketByOrderDepthDataListener e o complemento atua apenas como um proxy, o que parece inadequado. De fato, há outra maneira de alimentar o objeto do livro de ordenações com dados MBO:

MarketDataListener.java

@NoAutosubscription
@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter {

private final OrderBook orderBook = new OrderBook();

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
api.addMarketByOrderDepthDataListeners(orderBook);
}
}

Observe que tal método de assinatura requer uma anotação adicional: @NoAutosubscription

TradesListener

Para tornar este exemplo um pouco mais prático, vamos criar uma classe que calcule VWAP (Preço Médio Ponderado por Volume):

Vwap.java

public class Vwap {

public double priceSize = 0;
public long volume = 0;

public void onTrade(double price, int size) {
priceSize += price * size;
volume += size;
}
}

E aqui está um complemento que implementa TradeDataListener e imprime as estatísticas do VWAP no final da sessão:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}
}

Dados de mercado pré-inscrição

Suponha que você tenha iniciado o Bookmap no início do dia de negociação, mas não ativou o complemento VWAP acima. Basta adicionar HistoricalDataListener à sua lista de interfaces e, quando você ativar o complemento, ele receberá todos os dados desde a assinatura do instrumento. Esta interface não tem nenhuma callback:

MarketDataListener.java


@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener, HistoricalDataListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}
}

Observe que, para medir o VWAP "até agora", você pode ativá-lo, aguardar alguns segundos até que ele tenha recebido todos os dados, e então desativá-lo.

O complemento também pode ser notificado quando os dados de pré-inscrição são processados e os dados em tempo real começam. Para esse fim, use a interface HistoricalModeListener em vez de HistoricalDataListener e implemente a callback onRealtimeStart:

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, TradeDataListener, HistoricalModeListener {

private final Vwap buyers = new Vwap();
private final Vwap sellers = new Vwap();
private double minPriceIncrement;
private Api api;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
minPriceIncrement = info.pips;
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
(tradeInfo.isBidAggressor ? buyers : sellers).onTrade(price, size);
}

@Override
public void stop() {
double vwapBuy = minPriceIncrement * buyers.priceSize / buyers.volume;
double vwapSell = minPriceIncrement * sellers.priceSize / sellers.volume;
double vwap = minPriceIncrement * (buyers.priceSize + sellers.priceSize) / (buyers.volume + sellers.volume);
Log.info(String.format("VWAP Buy: %.2f, Sell: %.2f, Total: %.2f", vwapBuy, vwapSell, vwap));
}

@Override
public void onRealtimeStart() {
Log.info("Dados em tempo real iniciados. Descarregando...");
api.unload();
}
}

Nesse caso simples, o complemento descarrega-se automaticamente usando api.unload(), que aciona a callback stop() que imprime a informação necessária. No caso geral, a interface HistoricalModeListener é usada para fazer algo antes de continuar com os dados em tempo real (por exemplo, para mudar de indicator.addPoint(timestamp, value) para indicator.addPoint(value), que será abordado mais tarde).

Para evitar confusões, existem 3 tipos de dados históricos:

  • Dados de Pré-inscrição são os dados que foram coletados pelo Bookmap dos dados em tempo real antes que o complemento fosse ativado
  • Dados de Retificação são os dados de até 48 horas que precedem os dados em tempo real. Tipicamente, são buscados pelo Bookmap dos seus próprios servidores e podem não ser MBO, mesmo que os dados em tempo real sejam MBO
  • Dados Históricos são os mesmos que dados de Replay. Estão armazenados nos arquivos bmf do Bookmap na pasta C:/Bookmap/Feeds/ e podem ser reproduzidos pelo Bookmap. Esses arquivos de dados não contêm os dados de Retificação, mas apenas os dados em tempo real gravados.

Intervalos de tempo fixos

Às vezes, o complemento precisa fazer algum cálculo ou atualizar seu indicador, mas não queremos que ele faça isso com muita frequência (por exemplo, a cada atualização do MBO) do ponto de vista do desempenho (pode haver muitos milhares de atualizações do MBO por segundo). Para esse propósito, os complementos podem implementar o IntervalListener. Apenas lembre-se de que todos os timestamps no Bookmap estão em nanossegundos.

MarketDataListener.java

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, IntervalListener {

@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}

@Override
public void onInterval() {
// faça algo a cada 100 milissegundos
}
}

Outros tipos de dados

Outros tipos de dados são menos importantes porque são redundantes, isto é, podem ser facilmente computados dados os tipos de dados acima.

BBO

Para receber BBO (Melhor Oferta e Ask), o complemento deve implementar a interface BboListener, que inclui uma única callback onBbo, chamada sempre que qualquer um dos argumentos fornecidos tiver mudado.

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, BboListener {

@Override
public void onBbo(int bidPrice, int bidSize, int askPrice, int askSize) {
// Método gerado automaticamente
}
}

Barras (Candlesticks)

As barras representam informações sobre o volume negociado, agregado por um intervalo de tempo fixo. Portanto, o BarDataListener inclui o método getInterval, que é chamado pela API apenas uma vez. A callback onBar também fornece o OrderBook MBP do instrumento para conveniência.

@Layer1SimpleAttachable
@Layer1StrategyName("Market Data Listener")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class MarketDataListener implements CustomModuleAdapter, BarDataListener {

@Override
public long getInterval() {
return Intervals.INTERVAL_1_SECOND; // o tempo está sempre em nanossegundos
}

@Override
public void onBar(OrderBook orderBookMbp, Bar bar) {
// Método gerado automaticamente
}
}

Para iniciantes: Ctrl+Espaço abre uma lista de métodos disponíveis do objeto conforme mostrado aqui.

Exercício

Remova a anotação @Override do método onBar e, sem implementar BarDataListener, chame-o com seus argumentos esperados a cada 100 milissegundos.


// _Rolar para ver a solução_

OrderBookMbp.java (vamos torná-lo uma classe separada)

public class OrderBookMbp implements DepthDataListener {

TreeMap<Integer, Integer> bids = new TreeMap<>(Comparator.reverseOrder());
TreeMap<Integer, Integer> asks = new TreeMap<>();

public void onDepth(boolean isBid, int price, int size) {
TreeMap<Integer, Integer> book = isBid ? bids : asks;
Integer dummy = (size == 0) ? book.remove(price) : book.put(price, size);
}
}

ExerciseCustomOnBar.java, a solução

@Layer1SimpleAttachable
@Layer1StrategyName("Exercise: custom onBar")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class ExerciseCustomOnBar implements CustomModuleAdapter, IntervalListener, TradeDataListener, DepthDataListener {

private final OrderBookMbp orderBook = new OrderBookMbp();
private final Bar bar = new Bar();

private void onBar(OrderBookMbp orderBookMbp, Bar bar) {
// chamado como BarDataListener#onBar()
}

@Override
public void onInterval() {
onBar(orderBook, bar); // Chama onBar()
bar.startNext(); // reinicia (gira) a barra
}

@Override
public void onTrade(double price, int size, TradeInfo tradeInfo) {
bar.addTrade(tradeInfo.isBidAggressor, size, price);
}

@Override
public void onDepth(boolean isBid, int price, int size) {
orderBook.onDepth(isBid, price, size);
}

@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}
}

Exercício

Torne a solução do exercício anterior mais compacta usando a anotação @NoAutosubscription e o método de inscrição de dados correspondente


// _Rolar para ver a solução_

Esta solução usa a mesma classe OrderBookMbp.java e a fornece como DepthDataListener. Se expressões lambda não o assustarem, você pode achar essa abordagem mais eficaz em alguns casos.

@NoAutosubscription
@Layer1SimpleAttachable
@Layer1StrategyName("Exercise: custom onBar")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class ExerciseCustomOnBar implements CustomModuleAdapter {

private final OrderBookMbp orderBook = new OrderBookMbp();
private final Bar bar = new Bar();

private onBar = (orderBookMbp, bar) -> {
}

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
api.addDepthDataListeners(orderBook);
api.addTradeDataListeners((price, size, tradeInfo) -> bar.addTrade(tradeInfo.isBidAggressor, size, price));
api.addIntervalListeners(new IntervalListener() {
@Override
public void onInterval() {
onBar(orderBook, bar); // Chama onBar()
bar.startNext(); // Inicia o OPEN do próximo intervalo com o CLOSE atual
}
@Override
public long getInterval() {
return Intervals.INTERVAL_100_MILLISECONDS;
}
});
}
}

Renderizando Indicadores

Aqui está um exemplo simples. Este indicador calcula o VWAP durante intervalos de 10 segundos e exibe os resultados no final de cada intervalo.

@Layer1SimpleAttachable
@Layer1StrategyName("Vwap Indicator")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
public class VwapRendererWithBar implements CustomModuleAdapter, BarDataListener, HistoricalDataListener {

private Indicator vwapLine;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
vwapLine = api.registerIndicator("VWAP", GraphType.PRIMARY, Double.NaN);
}

@Override
public long getInterval() {
return Intervals.INTERVAL_10_SECONDS;
}

@Override
public void onBar(OrderBook orderBook, Bar bar) {
vwapLine.addPoint(bar.getVwap());
}
}

Observe o HistoricalDataListener. Ele informa ao Bookmap para fornecer os dados desde a inscrição do instrumento, para que possamos ver a linha VWAP imediatamente após ativar o complemento, veja a linha branca:

Regras de eixos

Grupos de eixos

Computando e atualizando indicadores

Desempenho

Configurações

Configurações declaradas por anotações

Configurações armazenadas pela API

GUI (Interface do Usuário)

EDT

Componentes e painéis

Callbacks

Janelas destacadas

Widgets personalizados

Dados históricos

Recarregar o complemento

Armazenamento de dados personalizados

Concorrência

Usando a API core L1

Produção

Licenciamento do seu complemento

Ofuscando seu complemento

Proguard

Configuração do Gradle

Marketplace do Bookmap

Unidades de preço, tamanho e tempo

Timestamp

Preços

Tamanho da ordem, volume e quantidades

Javadoc da API

Tópicos avançados

Renderização personalizada no gráfico