Guia Prático MQL5: Processamento do Evento BookEvent
Denis Kirichenko | 26 dezembro, 2014
Introdução
Como é bem conhecido, o terminal de negociação MetaTrader 5 é uma plataforma de multi-mercado, que facilita a negociação em Forex, mercados de ações, futuros e CFD´s. De acordo com as estatísticas da seção Freelance, o número de traders que negociam outros mercados além de Forex está crescendo.
Neste artigo eu gostaria de apresentar aos programadores iniciantes em MQL5 o manuseio do BookEvent. Este evento está conectado com a Profundidade do Mercado - um instrumento para negociação de ativos e seus derivativos. Os traders de forex, no entanto, podem achar útil a Profundidade do Mercado também. Nas contas ECN, os fornecedores de liquidez fornecem os dados sobre as ordens, apenas dentro de seu modelo de agregação. Estas contas estão se tornando mais populares.
1. BookEvent
De acordo com a Documentação, Este evento é gerado quando a situação da Profundidade do Mercado se altera. Vamos concordar que o BookEvent é o evento da Profundidade do Mercado
A Profundidade do Mercado é um array de ordens, que diferem na direção (compra e venda), preço e volume. Os preços aa Profundidade do Mercado são próximos aos do mercado e, portanto, são considerados como o melhor.
Fig.1 Profundidade do Mercado no MetaTrader 5
No MetaTrader 5 o "book de ofertas" é chamado de "Profundidade do Mercado" (Fig.1). Informações detalhadas sobre a Profundidade do Mercado podem ser encontradas na Guia do Usuário do Terminal Cliente.
A estrutura MqlBookInfo que fornece as informações sobre a profundidade do mercado deve ser mencionada separadamente.
struct MqlBookInfo { ENUM_BOOK_TYPE type; // order type from the ENUM_BOOK_TYPE enumeration double price; // price long volume; // volume };
Ela contém três campos. Os dados sobre o tipo de ordem, preço e volume podem ser obtidos através do processamento da estrutura de ordem.
2. Processamento do evento BookEvent
A função manipuladora do evento OnBookEvent() leva uma constante como parâmetro. É uma referência a um parâmetro de string.
void OnBookEvent (const string& symbol)
O parâmetro de string contém o nome do símbolo, para que o evento da profundidade de Mercado possa ocorrer.
O próprio manipulador de eventos requer uma preparação prévia. Para que o EA lide com o evento Profundidade do Mercado, este evento deve ser subscrito a um ativo, utilizando a função embutida MarketBookAdd(). Normalmente, ele está localizado no bloco de inicialização do EA. Se o evento Profundidade do Mercado não foi subscrito por nenhum ativo, então, o EA irá ignorá-lo.
Fornecer a possibilidade de cancelar o recebimento de eventos é considerada uma boa prática de programação. No deinitialization, é necessário cancelar o recebimento dos dados chamando a função MarketBookRelease().
Os mecanismos de subscrição e cancelamento do recebimento de dados é semelhante à criação e ao processamento de um timer, que tem que ser ativado antes de seu processamento.
3. Modelo de Processamento do evento BookEvent
Vamos criar um simples modelo do EA, que chama a função OnBookEvent(), chamado BookEventProcessor1.mq5.
O modelo conta com um conjunto mínimo de manipuladores, que são manipuladores de inicialização e de finalização do EA, bem como o manipulador de eventos da Profundidade do Mercado.
O manipulador do evento Profundidade do Mercado em si é muito simples:
//+------------------------------------------------------------------+ //| BookEvent function | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { Print("Book event for: "+symbol); //--- select the symbol if(symbol==_Symbol) { //--- array of the DOM structures MqlBookInfo last_bookArray[]; //--- get the book if(MarketBookGet(_Symbol,last_bookArray)) { //--- process book data for(int idx=0;idx<ArraySize(last_bookArray);idx++) { MqlBookInfo curr_info=last_bookArray[idx]; //--- print PrintFormat("Type: %s",EnumToString(curr_info.type)); PrintFormat("Price: %0."+IntegerToString(_Digits)+"f",curr_info.price); PrintFormat("Volume: %d",curr_info.volume); } } } }
Como este evento da Profundidade do Mercado é uma transmissão (após a subscrição irá aparecer para todos os símbolos), um instrumento obrigatório tem de ser especificado.
Note que que na últimas versões, algumas mudanças foram introduzidas no no funcionamento da Profundidade do Mercado. Na versão atual (build 975) eu não encontrou quaisquer sinais de transmissão. O manipulador foi chamado apenas para o símbolo atribuído. Para provar este fato, eu simplesmente exibi a inserção das informações no Log "Experts".
Para isso, vamos usar a função embutida MarketBookGet(). Ele irá retornar todas as informações sobre a Profundidade do Mercado, ou seja, o array de estruturas MqlBookInfo, que contém os registros da Profundidade do Mercado para o símbolo especificado. Deve-se notar, que este array irá variar em tamanho, dependendo da corretora.
O modelo permite exibir os valores do array de estruturas no Log.
A EA foi lançado para o futuro SBRF-12.14 em modo de depuração. A seqüência de registros do log Experts é a seguinte:
EL 0 11:24:32.250 BookEventProcessor1 (SBRF-12.14,M1) Book event for: SBRF-12.14 MF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL KP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7708 LJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 6 MP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL HF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7705 LP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 6 MJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL GL 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7704 ON 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 3 MD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL FR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7703 PD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 MN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL DH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7701 QQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 10 GH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL QM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7700 OK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 1011 ER 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL KS 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7698 EE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 50 OM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL KI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7696 IO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 21 QG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL LO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7695 MK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 1 QQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL KE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7694 QQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 5 QK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL PK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7691 LO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 QE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL HQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7688 OE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 106 MO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL RG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7686 IP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 18 GI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL FL 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7684 QI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 3 GS 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL IR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7681 LG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 4 GM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL JH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7680 RM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 GG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL DN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7679 HH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 19 IQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL ED 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7678 EQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 1 IK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL OJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7676 DO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 IE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_SELL RP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7675 EE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 1 QR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY LF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7671 LP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 40 QD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY KL 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7670 QJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 21 QN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY CR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7669 RD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 20 QP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY DH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7668 NN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 17 QJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY QN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7667 RK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 OL 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY DE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7666 MQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 151 QF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY OJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7665 RO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 OH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY FQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7664 EF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 49 OR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY GG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7663 OS 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 3 ED 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY JM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7662 PI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 4 CN 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY ES 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7661 LD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 13 CP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY FI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7660 LM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 2 IJ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY NO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7659 II 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 12 IL 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY ME 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7658 IP 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 3 GF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY NK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7657 FM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 15 GH 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY MQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7656 DD 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 6 MS 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY NG 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7655 KR 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 9 KE 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY OM 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7654 IK 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 14 KO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY NS 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7653 DF 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 534 MQ 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Type: BOOK_TYPE_BUY OI 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Price: 7652 IO 0 11:24:33.812 BookEventProcessor1 (SBRF-12.14,M1) Volume: 25
É o que a Profundidade do mercado parecia em um determinado momento do tempo. O primeiro elemento do array de estruturas é uma ordem para vender ao preço mais elevado (7708 Rub). O último elemento da matriz é uma ordem para comprar pelo menor preço (7652 Rub). Assim, os dados da Profundidade do Mercado são lidos no array de cima para baixo.
Por conveniência, eu juntei os dados da Tabela 1.
Tabela 1. Profundidade do Mercado para SBRF-12.14
O bloco superior destacado em vermelho contém ordens de venda (sell-limites). O bloco inferior destacadas em verde inclui ordens de compra (buy-limites).
É óbvio que, de todas as ordens de venda, a ordem nº 6 teve o maior volume, com o preço de 7700 Rub e um volume de 1011 lotes. A ordem #39 teve o maior volume de todas as ordens de compra com o preço de 7653 Rub e um volume de 534 lotes.
Os dados de origem na Profundidade do Mercado é a informação que um trader analisa para elaborar uma estratégia de negociação. A idéia mais simples de negociação é coletar e agrupar ordens com volumes significativos nos níveis de preços contíguos, criando zonas de suporte e resistência. Na seção seguinte, vamos criar um indicador que vai acompanhar as mudanças na Profundidade do Mercado.
4. Profundidade do Mercado
Há um monte de indicadores variados que trabalham com os dados da Profundidade do Mercado. Você pode encontrar alguns interessantes no Mercado do MetaTrader 5. Eu gosto, acima de tudo, do IShift. O desenvolvedor Yury Kulikov, conseguiu criar uma ferramenta compacta e informativa.
Todos os programas que trabalham com os dados da Profundidade do Mercado terá uma forma de um Expert Advisor, já que só os Expert Advisors apresentam o manipulador de eventos BookEvent. Existe a possibilidade, no entanto, de escrever um par "Expert - Indicador", em que o indicador pode receber os dados do EA e processar o estado da Profundidade do Mercado.
Vamos criar um pequeno programa que vai exibir ao vivo os dados da Profundidade do Mercado. Primeiramente, é necessário especificar os dados a serem exibidos. Será um painel, com as barras horizontais indicando o volume da ordem. O tamanho das barras, no entanto, será de natureza relativa. O volume máximo de todas as ordens atuais serão considerados como 100%. Fig. 2 exibe que a ordem ao preço de 7507 Rub tem o maior volume, sendo este de 519 lotes.
Para a inicialização do painel correto, a profundidade exata do Mercado ou o número de níveis tem de ser especificado (o parâmetro "profundidade DOM"). Este número difere de corretora para corretora.
Fig. 2 Painel Profundidade de Mercado
O código do programa para o painel foi escrito com a abordagem da Programação Orientada a Objetos. A classe responsável pela operação do painel foi chamada de CBookBarsPanel.
//+------------------------------------------------------------------+ //| Book bars class | //+------------------------------------------------------------------+ class CBookBarsPanel { private: //--- Data members CArrayObj m_obj_arr; uint m_arr_size; //--- uint m_width; uint m_height; //--- Methods public: void CBookBarsPanel(const uint _arr_size); void ~CBookBarsPanel(void){}; //--- bool Init(const uint _width,const uint _height); void Deinit(void){this.m_obj_arr.Clear();}; void Refresh(const MqlBookInfo &_bookArray[]); };
Esta classe contém quatro membros de dados.
- O atributo m_obj_arr atributo é um recipiente para ponteiros dos objetos do tipo CObject. Ele é usado para o armazenamento dos dados da Profundidade do Mercado. A classe separada será criada por último.
- O atributo m_arr_size é responsável pelo número de níveis da Profundidade do Mercado.
- Os atributos m_width e m_height armazenam as dimensões do painel (largura e altura em pixels).
A respeito dos métodos, o seu conjunto além do construtor padrão e destrutor incluem:
- Método de inicialização
- Método de desinicialização
- Método de atualização
Vamos criar a classe separada CBookRecord para cada linha do painel (nível da Profundidade do Mercado), especificando o preço, barra horizontal e volume.
Ela será composta por três ponteiros. Um deles será apontando para o objeto do tipo CChartObjectRectLabel (rótulo retangular para trabalhar com uma barra horizontal) e dois ponteiros do tipo CChartObjectLabel (rótulos de texto para o preço e volume).
//+------------------------------------------------------------------+ //| Book record class | //+------------------------------------------------------------------+ class CBookRecord : public CObject { private: //--- Data members CChartObjectRectLabel *m_rect; CChartObjectLabel *m_price; CChartObjectLabel *m_vol; //--- color m_color; //--- Methods public: void CBookRecord(void); void ~CBookRecord(void); bool Create(const string _name,const color _color,const int _X, const int _Y,const int _X_size,const int _Y_size); //--- data bool DataSet(const long _vol,const double _pr,const uint _len); bool DataGet(long &_vol,double &_pr) const; private: string StringVolumeFormat(const long _vol); };
Entre os métodos encontramos o seguinte:
- método de criação de um registro;
- método de definição dos dados;
- Método de recepção dos dados;
- método de formatação de grandes números.
Então, o manipulador do BookEvent para o EA e, essencialmente, o indicador, ficará da seguinte forma:
//+------------------------------------------------------------------+ //| BookEvent function | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { //--- select the symbol if(symbol==_Symbol) { //--- array of the DOM structures MqlBookInfo last_bookArray[]; //--- get the book if(MarketBookGet(_Symbol,last_bookArray)) //--- refresh panel myPanel.Refresh(last_bookArray); } }
Então, toda vez que o BookEvent for gerado, o painel irá atualizar seus dados. Estamos chamando a versão atualizada do programa como BookEventProcessor2.mq5.
No vídeo acima você pode ver como o EA está trabalhando.
Conclusão
Este artigo é dedicado a um outro evento do Terminal - o evento Profundidade do Mercado. Este evento é muitas vezes o centro de algoritmos da negociação de alta frequência (HFT). Este tipo de negociação está ganhando popularidade entre os traders.
Espero que os traders que estão começando a programar em MQL5 irá acha úteis estes exemplos inclusos de como lidar com o evento Profundidade do Mercado. Os arquivos do código fonte em anexo deste artigo podem ser colocados na pasta do projeto de forma bem conveniente. No meu caso, é \MQL5\Projects\BookEvent.