The source content for blog.juliobiason.me
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

653 lines
23 KiB

+++
title = "Decodificando o Protocolo FAST"
date = 2022-01-10
updated = 2022-01-13
[taxonomies]
tags = ["finanças", "binário", "protocolo", "fix", "fast"]
+++
Recentemente tive que trabalhar com um FAST (FIX Adapted for Streaming) e
como a documentação está espalhada, decidi colocar as coisas que descobri em um
único lugar para (minha) referência futura.
<!-- more -->
{% note() %}
Como isso é baseado na minha experiência pessoal e eu tive contato com uma
única instância disso até agora, existem algumas coisas que estão incompletas
e/ou erradas. Vou continuar atualizando este post enquanto descubro coisas
novas.
O changelog está no final deste post.
{% end %}
# O que é o FAST
[FAST](https://en.wikipedia.org/wiki/FAST_protocol) é, basicamente, um método
de compressão para o protocolo FIX.
# E o que é FIX
[FIX](https://en.wikipedia.org/wiki/Financial_Information_eXchange) é um
protocolo criado para as instituições financeiras trocarem informações. Embora
não haja nada "financeiramente" relacionado a ele - você poderia usar o
protocolo para qualquer coisa, basicamente - a maioria das empresas financeiras
o usa.
FIX é um protocolo muito simples: você tem pares de "campo ID" e "valor"
separados por um "`=`" (sinal de igual) e os pares são separados pelo caractere
ASCII com código 1 (que é representado por `^A` em alguns editores). Alguns IDs
de campo são definidos pelo protocolo (há uma lista completa
[aqui](https://www.inforeachinc.com/fix-dictionary/index.html)), mas cada
central pode criar seus próprios IDs.
Por exemplo, se você tiver o campo MsgType (ID 35) com valor "`y`" e o campo
Security ID (ID 48) com valor " 123456", a mensagem recebida seria
```
35=y^A48=123456
```
# E de Volta ao FAST
Uma das coisas para que o FAST foi projetado é remover conteúdo duplicado e/ou
constante. Por exemplo, MsgType (ID 35) identifica que a mensagem é
"SecurityList" (lista de ativos), que contém informações sobre todos os
3 years ago
símbolos (seus IDs de segurança) disponíveis na Exchange. Como a Exchange é a
mesma em todos os símbolos, o FAST permite definir os campos relacionados a ela
(Source, campo ID 22, e Exchange, campo ID 207) para valores constantes, para
que não precisem ser transmitidos e, na decodificação FAST de volta a FIX, o
decodificador simplesmente adiciona o valor constante.
Para saber quais campos são constantes e quais não (e algumas outras
informações), o protocolo define um modelo, que possui um esquema bem definido,
para relatar essas informações.
# O Template
O template é um arquivo XML que descreve os campos, seus tipos, nomes, IDs e
3 years ago
operadores. O protocolo em si não fornece nenhuma maneira padrão de realmente
receber esse campo e, portanto, é deixado para que a Exchange defina a forma
dos clientes encontrar este arquivo.
Note que o modelo descreve os IDs de campo e seus tipos, e os dados recebidos
possuem apenas os valores. Se usarmos a descrição FIX acima, o modelo define o
lado esquerdo do par, enquanto os dados de entrada têm apenas o lado direito.
## Tipos de Campos
O protocolo define arquivos tipos de dados: Inteiros de 32 e 64 bits sem sinal,
Inteiros de 32 e 64 bits com sinal, strings em ASCII, strings em UTF8,
sequências, decimais e um tipo especial chamando "Mapa de Presença" (Presence
Map).
Uma coisa que o processamento de todos os campos é que estes usam um formato de
"bit de parada". O formato é bem parecido com o formato de UTF8, embora UTF8
use um "bit de continuação" ao invés de "bit de parada", mas o processo de
leitura é o mesmo:
- Ler um byte;
- O bit de mais alta ordem é 0?
- Sim: Continue lendo;
- Não: Pare de ler o valor do campo.
Eu vou mostrar alguns exemplos mais a frente neste post.
## Definição dos Campos
O template define os campos com seus tipo nome (opcional), ID,
um indicador de presença e um operador (opcional).
Por exemplo, para descrever um inteiro sem sinal de 32 bits, chamado "MsgType"
com ID "35", ele seria descrito no template como
```xml
<uInt32 name="MsgType" id="35"/>
```
Como não há indicador de presença, é assumido que o campo é "mandatório" e deve
sempre deve ter um valor. Por outro lado, se a definição do campo for algo do
tipo
```xml
<int32 name="ImpliedMarketIndicator" id="1144" presence="optional"/>
```
... então o campo pode não ter valor. Isso também é definido como campo
"nulável" (nullable).
## Campos Mandatórios e Opcionais
A diferença entre campos opcionais e mandatórios é que campos mandatórios
*sempre* tem um valor, mesmo que seja 0. Campos opcionais, por outro lado,
podem ser null e não ter valor algum.
{% note() %}
3 years ago
Eu tenho a impressão que isso foi feito para que a Exchange possa marcar um
campo como "não parece na versão FIX"; então mesmo que o campo tenha uma
definição, o resultado em FIX não teria o campo.
Então é algo mais como "compatibilidade com versões anteriores" do que qualquer
outra coisa.
{% end %}
## Tipos
### Tipos: Inteiros
Para ler um inteiro, o decodificador pegaria os 7 bits mais baixos (tudo exceto
o bit de alta ordem) e mover os mesmos para o valor resultante. Se o bit de
parada estiver presente, a leitura está completa; se não estiver, deve ser
feito um "shift" de 7 bits no valor resultado e movidos os 7 bits do próximo
byte e assim por diante, até que seja encontrado um byte com o bit de parada.
Inteiros de 32 e 64 bits definem apenas os valores máximos de um campo e não
necessariamente usados como "número de bits a serem lidos" -- justamente por
causa do bit de parada. Se o valor excede 32 ou 64 bits, isso é considerado um
erro e o processamento deve ser abortado.
Inteiros com sinal funcionam exatamente igual, mas com complemento de 2.
Por exemplo, se os dados de entrada tem os seguintes bytes (em binário, para
ficar mais fácil de ler; eu adicionei um underscore entre cada 4
valores, também para ficar mais fácil de ler):
```
0000_0001 1001_0010
```
... o decodificador irá ler o primeiro byte e ver que o bit de mais alta ordem
não está ligado, então ele mantém apenas o "1" do valor e move 7 bits à
esquerda. O segundo byte então é ligado; este tem o bit de alta ordem ligado,
então os bits restantes (no caso, "001_0010") são adicionados no valor final e
temos `1001_0010` -- ou 146.
Números negativos são representados por complemento de 2, então se o
decodificador receber, por exemplo:
```
0000_0011 0111_1110 1110_1110
```
... que, quando os bits de alta ordem são removidos, deve gerar "`1111_1111
0110_1110`", que é -146 (em 16 bits, apenas para ficar mais fácil de ler).
Quando um campo inteiro é opcional, o valor resultante deve se decrementado
em 1. O motivo é que deve haver uma forma de diferenciar entre 0 e Null. Assim,
um inteiro que seja decodificado para 0 é, na verdade, Null; para obter o valor
0 num inteiro, é enviado o valor 1, que é decrementando por 1 e volta a zero.
No template, os campos aparecem como
- `<uInt32/>`: Inteiro sem sinal de 32 bits.
- `<uInt64/>`: Inteiro sem sinal de 64 bits.
- `<int32/>`: Inteiro com sinal de 32 bits.
- `<int64/>`: Inteiro sem sinal de 64 bits.
### Tipos: Strings
Strings em ASCII são bem simples de serem lidas: Novamente, o decodificador
fica lendo os dados de entrada até que encontre um byte com o bit de mais alta
ordem ligado (novamente, o bit de parada) e converte cada byte para o
respectivo caractere em ASCII.
Por exemplo
```
0100_1000 0110_0101 0110_1100 0110_1100 1110_1111
```
... iria gerar os bytes 72, 101, 108, 108 e 111, que usando a tabela ASCII
geraria o valor "Hello". Note que o bit de parada aqui representa "acabou a
string" e os bytes não devem ser agrupados como em Inteiros.
{% note() %}
Até aqui eu não tive nenhum contato com strings UTF8, então eu não tenho
certeza de como estes devem ser lidos. Com certeza há documentação em como
devem ser lidos, mas como esta é a minha experiência pessoal com o protocolo,
eu decidi não comentar aqui.
{% end %}
Strings opcionais são Null quando o primeiro byte tiver o bit de mais alta
ordem ligado e todos os demais desligados.
No template, um campo de string aparece como `<string>`.
### Tipos: Sequências
Sequências são basicamente arrays. O primeiro campo de uma sequência é o
"tamanho" (com o tipo "`<length>`" no template) com o número de registros
presentes. Dentro da sequência, você tem uma lista de definições de campos, que
podem até mesmo incluir outras sequências.
Sequências opcionais afetam a forma como o campo de tamanho é lido: Se a
3 years ago
sequência é opcional, o tamanho deve ser tratado como um inteiro opcional e,
portanto, decrementando em 1.
No template, a sequência aparece como `<sequence>`, com o tamanho logo depois.
Um exemplo é
```xml
<sequence name="Sequence">
<length name="NoSequence" id="123"/>
</sequence>
```
{% note() %}
Eu descrevo a maior parte dos campos de tamanho com um nome que começa com
"No". Isso é porque a especificação do FIX define os tamanhos com esse prefixo.
{% end %}
### Tipos: Decimais
Decimais são formados por dois campos: Expoente e Mantissa. A forma como isso
funciona é que se você tiver um Expoente de "-2" e uma Mantissa de "1020", o
valor resultante é `1020 * 10 ^ -2` ("1020 vezes 10 na potência -2") ou
"10.20".
Tanto o Expoente quanto a Mantissa são lidos como Inteiros com sinal.
Um Decimal opcional significa que o Expoente é opcional. A documentação diz que
a Mantissa é sempre mandatória, mas tem uma pegadinha: Se o Expoente é Null,
então a Mantissa não está presente nos dados de entrada e o Decimal inteiro é
Null; caso contrário, o decodificador deve ler a Mantissa e aplicar a forma
acima.
Ainda, como o Expoente e a Mantissa são dois campos, eles podem ter operadores
diferentes. Eu vou mostrar exemplos depois de explicar os Operadores,
principalmente porque eu vi os dois com operadores diferentes e a leitura é
complicada.
No template, um campo decimal aparece como `<decimal>`, com o expoente e a
mantissa como campos internos.
```xml
<decimal name="ADecimal" id="123">
<exponent/>
<mantissa/>
</decimal>
```
### Tipo: Mapa de Presença
Mapas de Presença são usados em conjunto com operadores. Eles são lidos da
mesma forma como Inteiros sem sinal (ler os bytes até encontrar o byte com o
bit de mais alta ordem ligado, remover os bits de alta ordem e colocar os bits
em sequência) mas não há uma conversão em si. Cada vez que o decodificador
precisa verificar se um campo está presente no Mapa de Presença, o bit é
consumido e a linha avança.
Mapas de Presença não estão presentes no template e a existência do Mapa ocorre
se pelo menos um dos campos precisa do Mapa de Presença. Por exemplo, se os campos
de uma sequência são todos mandatórios, não haverá Mapa de Presença nos dados
de entrada.
Os bits do Mapa de Presença sequem a ordem dos campos no template. Por exemplo,
se um template tem
1. Um campo mandatório;
2. Um campo com um operador que requer o Mapa de Presença (eu vou comentar
sobre estes mais pra frente);
3. Outro campo mandatório;
4. E, finalmente, outro campo com operador.
... é possível ter um mapa de presença como `1110_0000`, no qual:
1. O primeiro bit é o bit de parada, e o decodificar assume que este é o último
byte do Mapa de Presença.
2. O segundo bit indica que o primeiro com operador está presente. Ele *não*
representa o campo mandatório porque, bom, o campo é mandatório e, assim,
está sempre presente.
3. O segundo bit indica que o segundo campo com operador está presente.
(De novo, eu vou mencionar quais operadores requerem o Mapa de Presença.)
## Operadores
Operadores definem a forma como alguns campos devem ser tratados. Eu vi 5 tipos
diferentes de Operadores:
- Nenhum;
- Constant;
- Default;
- Copy;
- Delta;
- Increment.
### Operador: Nenhum.
Quando não há operador definido para o campo no template, então o operador e o
"Nenhum". Isto significa que não há forma especial de lidar com o valor de
entrada.
Quando um campo tem o operador "Nenhum", não haverá bit para o mesmo no Mapa de
Presença.
3 years ago
### Operador: Constant
Um campo com o operador Constant (Constante) não irá ser enviado nos dados de
entrada e o decodificador deve assumir que o valor é o valor constante.
Anteriormente eu mencionei que uma lista de símbolos pode ter o campo 22,
"Source" e o campo 207 "Exchange", com valores contantes, e assim eles seriam
definidos como
```xml
<string name="Source" id="22">
<constant value="123"/>
</string>
<string name="Exchange" id="207">
<constant value="EXCHANGE"/>
</string>
```
Existe uma pegadinha, no entanto: Se um campo contante pode ser Null
(`presence="optional"`), então o decodificador precisa verificar o Mapa de
Presença: Se estiver ligado, o valor contante deve ser usado; caso contrário, o
valor do campo é Null.
O Mapa de Presença deve ser usado apenas se o campo com valor contante é
opcional.
### Operador: Default
O operador Default é parecido com o operador Constante, mas o decodificador
precisa verificar o bit do campo no Mapa de Presença; se estiver ligado, o
valor do campo deve ser lido dos dados de entrada; caso contrário, deve ser
usado o valor Default.
{% note() %}
Uma forma de ler isso é: O valor está presente nos dados de entrada (indicado
pelo Mapa de Presença)? Leia o valor dos dados de entrada; caso contrário, use
o valor default.
{% end %}
Exemplo
```xml
<uInt32 name="Type" id="3">
<default value="1"/>
</uInt32>
```
### Operador: Copy
O operador Copy (Cópia) indica que o valor deste campo neste registro tem o
mesmo valor do registro anterior; se este é o primeiro registro, então o valor
atual deve ser usado. Se o bit no Mapa de Presença estiver ligado para este
campo, então o decodificador deve ler o campo dos dados de entrada; caso
contrário, o valor anterior deve ser usado.
{% note() %}
Nos dados que eu vi, todo primeiro registro tinha o bit ligado.
{% end %}
Por exemplo: Com um template como:
```xml
<string name="MDReqID" id="262">
<copy/>
</string>
```
... e os dados de entrada tem os seguintes registros e Mapas de Presença:
1. O primeiro registro tem o bit do campo ligado no Mapa de Presença e a string
é lida como "primeiro". O resultado para este campo neste registro será o
valor "primeiro".
2. O segundo registro não tem o bit ligado no Mapa de Presença. Assim, o
decodificador usa o valor anterior e este campo neste registro terá o valor
"primeiro" (de novo).
3. O terceiro registro tem o bit do Mapa de Presença ligado novamente, e é lido
o valor "segundo". Este é o valor para este campo neste registro.
4. O quatro registro não tem o bit ligado no Mapa de Presença e o decodificador
usa o valor "segundo" para o campo.
O operador Copy pode indicar o valor inicial do campo. Por exemplo
```xml
<string name="MDReqID" id="262">
<copy value="string"/>
</string>
```
Isso significa que deve usar "string" como valor anterior, mesmo no primeiro
registro.
Conforme indicado, campos com o operador Copy sempre aparecem no Mapa de
Presença.
### Operador: Delta
Delta é um operador parecido com Copy, mas ao invés de usar o valor do registro
anterior neste campo, o novo valor deve ser calculado usando o valor anterior e
o atual. De novo, se não há valor anterior, então nenhuma operação é feita o
valor nos dados de entrada é o valor atual.
Um exemplo:
```xml
<uInt32 name="NumberOfOrders" id="346">
<delta/>
</uInt32>
```
1. O primeiro registro vem com o valor "300". Este é o valor do campo.
2. O segundo registro vem com o valor "2". Este valor deve ser adicionado no
valor anterior e usado, então o valor no segundo registro é "302".
3. O terceiro registro vem com o valor "3". Novamente, deve ser usado o valor
anterior e o valor atual deve ser adicionar. Assim, o campo no terceiro
registro terá o valor "305".
### Operador: Increment
Increment (Incremento) funciona de forma similar ao operador Delta por estar
sempre adicionando um valor ao valor anterior, mas o valor adicionar é sempre
1.
Se o bit do campo estiver ligado no Mapa de Presença, o valor do campo será o
valor vindo dos dados de entrada; caso contrário, deverá ser usado o valor
anterior acrescido de 1.
Exemplo:
```xml
<uInt32 name="RptSeq" id="83">
<increment/>
</uInt32>
```
1. O primeiro registro tem o bit ligado para o campo no Mapa de Presença e o
decodificador lê o valor "100" nos dados de entrada. Este é o valor do campo
neste registro.
2. O segundo registro não tem o bit ligado, então não há nada a ser lido dos
dados de entrada, mas o campo deve ter o valor "101" para este registro.
Campos com o operador Increment devem aparecer no Mapa de Presença.
## Mapa do Mapa de Presença
Existe um mapa simples que indica se um campo aparece ou não no Mapa de
Presença, [de acordo com a JetTek](https://jettekfix.com/education/fix-fast-tutorial/):
<table>
<tr>
<th>Operador</th>
<th>Aparece para campos Mandatórios?</th>
<th>Aparece para campos Opcionais?</th>
</tr>
<tr>
<td>Nenhum</td>
<td>Não.</td>
<td>Não.</td>
</tr>
<tr>
<td>Constant</td>
<td>Não, o valor constante é sempre usado.</td>
<td>Sim; se ligado, deve usar o valor Constante; caso contrario, o
valor do campo é Null.</td>
</tr>
<tr>
<td>Copy</td>
<td>Sim; se ligado, deve usar o valor dos dados de entrada; caso
contrário, deve ser usado o valor anterior.</td>
<td>Sim, mesma coisa que campos Mandatórios.</td>
</tr>
<tr>
<td>Default</td>
<td>Sim; se ligado, deve usar o valor vindo dos dados de entrada; caso
contrário, use o valor default.</td>
<td>Sim; mesma coisa que campos Mandatórios.</td>
</tr>
<tr>
<td>Delta</td>
<td>Não; o valor vindo dos dados de entrada deve ser adicionado ao
anterior.</td>
<td>Não; mesma coisa que campos Mandatórios.</td>
</tr>
<tr>
<td>Increment</td>
<td>Sim; se ligado, deve usar o valor vindo dos dados de entrada; caso
contrário, adicionar 1 no valor anterior.</td>
<td>Sim; mesma coisa que campos Mandatórios.</td>
</tr>
</table>
# Versionamento de Templates
Eu não mencionei isso antes, mas agora que eu expliquei algumas coisas sobre os
tipos e algumas informações sobre o template, eu posso dizer que o arquivo de
template permite que existam múltiplas versões das mesmas mensagens.
Por exemplo
```xml
<?xml version="1.0" encoding="UTF-8"?>
<templates xmlns="http://www.fixprotocol.org/ns/fast/td/1.1">
<template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="SomeRecordType" id="1">
<string name="MsgType" id="35">
<constant value="Z"/>
</string>
<string name="SomeField" id="1"/>
</template>
<template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="SomeRecordType" id="2">
<string name="MsgType" id="35">
<constant value="Z"/>
</string>
<string name="SomeField" id="1"/>
<string name="SomeOtherField" id="2">
<default value="A Value!"/>
</string>
</template>
</templates>
```
Uma coisa que você pode notar é que existem dois templates definidos, um com ID
"1" e outro com ID "2". Ambos tem o mesmo nome e o mesmo campo com um valor
constante, mas a informação inicial dos dados de entrada indica qual destes
dois deve ser usada.
Os dados de entrada começam com um Mapa de Presença. O primeiro bit deste mapa
é o "Template ID". Com este Template ID, o decodificador pode encontrar a lista
de campos que devem ser processados. Este mapa também tem os campos da
sequência inicial -- no nosso exemplo, se o Template ID for "2", o outro bit no
Mapa de Presença é o indicador do "SomeOtherField".
{% note() %}
Até agora, eu não vi dados de entrada que não tivessem o bit indicador do
template ID ligado, então eu não tenho muita certeza do que fazer caso isso
aconteça.
{% end %}
# Anomalias
Eu chamo "anomalia" qualquer coisa que eu levei muito tempo para entender.
## Decimais com Operadores Diferentes
Isso foi uma coisa meio complicada para entender inicialmente. Por exemplo:
```xml
<decimal name="MDEntryPX" id="270" presence="optional">
<exponent>
<default value="0"/>
</exponent>
<mantissa>
<delta/>
</mantissa>
</decimal>
```
Parece simples, mas tem várias peças se movendo ao mesmo tempo aqui:
1. O indicador `presence="optional"` no decimal significa que o `exponent` pode
ser Null e apenas isso.
2. O operador Default no Expoente significa que o decodificador deve verificar
se o expoente tem um valor presente nos dados de entrada ou se deve ser
usado o valor default de "0".
Existe um problema aqui: Se o Mapa de Presença indica que o valor está
presente e é lido o valor 0, como o campo é opcional, o decodificador deve
considerada o Expoente Null e, portanto, não há Mantissa e todo o valor é
Null.
4. O operador Delta na Mantissa deve ser usado aplicando o valor de entrada com
o valor anterior. Mas, se o Expoente não estiver com o bit ligado no Mapa de
Presença, como há um valor default, este deve ser usado e não lido dos dados
de entrada, e o valor lido deve ser usado na Mantissa.
Tipo isso:
1. O primeiro registro tem o bit no Mapa de Presença ligado e o valor lido é
"-2"; este é o Expoente. O próximo valor valor é "1020", que é o valor da
Mantissa, e o decimal inteiro é "10.20".
2. O segundo registro tem o bit ligado no Mapa de Presença e o valor lido é
"0". Como o Decimal é opcional, o Expoente é opcional e como 0 é Null para
campos opcionais, não há Expoente e o próximo valor não é a Mantissa. O
valor do campo neste registro é Null.
3. O terceiro registro não tem o bit ligado no Mapa de Presença. Como o
Expoente tem um valor default, ele fica como "0" e a Mantissa deve ser lida.
Se vier o valor "10", o decimal se torna "10" (ou "10.00" para mantermos o
mesmo número de casas decimais).
Uma coisa estranha que vi foi relacionada com a forma como a Exchange estava
ordenando os resultados. Era uma sequência de ordens de compra e venda, na
quais
1. O primeiro registro era a ordem de venda, com um Expoente de "0" e Mantissa
de "5410". Isso significa que o valor era "5410".
2. O segundo registro era uma ordem de compra. Ele tinha o Expoente de "-2" e
Mantissa tinha o valor de "526604". Com o operador Delta, isso dá a Mantissa
o valor de "532014", mas como o Expoente é "-2", o valor real era de
"5420.14".
3. A coisa estranha aconteceu no terceiro registro, que era novamente uma ordem
de venda. O valor deveria ser o valor igual ao primeiro, mas a Exchange
mandou o valor "0" para o Expoente e Mantissa de "-526604". Com o Delta,
isso trouxe o valor de volta a "5410".
Eu achei estranho que a Exchange ficou mudando entre dois Expoentes ao invés de
usar apenas um, e na época eu estava com problemas com o cálculo de Deltas no
meu código, então...
## Sequências com Tamanho Null
Outra coisa que eu vi foi a Sequência opcional: Na prática não há diferença
entre uma sequência com 0 elementos e uma com o tamanho Null -- especialmente
se você pensar que o protocolo é, basicamente, um gerador de FIX. Eu não faço
ideia porque não foi padronizado que tamanhos são mandatórios e um valor de 0
significa que não há valores ao invés de ficar fazendo uma dança ao redor de
sequências mandatórias e opcionais.
---
Changelog:
- 2022-01-10: Primeira versão.
- 2022-01-10: Adicionada informações sobre o versionamento de templates.
- 2022-01-13: Adicionados exemplos das tags no template para os tipos de campos
e exemplos dos operadores.
<!--
vim:spelllang=pt:
-->