Julio Biason
3 years ago
2 changed files with 721 additions and 125 deletions
@ -0,0 +1,552 @@ |
|||||||
|
+++ |
||||||
|
title = "Decodificando o Protocolo FAST" |
||||||
|
date = 2021-01-10 |
||||||
|
|
||||||
|
[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. |
||||||
|
|
||||||
|
{% 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 |
||||||
|
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 |
||||||
|
operatores. 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() %} |
||||||
|
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. |
||||||
|
|
||||||
|
### 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. |
||||||
|
|
||||||
|
### 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 |
||||||
|
sequência é opcional, o tamanho deve ser tratato como um inteiro opcional e, |
||||||
|
portanto, decrementando em 1. |
||||||
|
|
||||||
|
### 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. |
||||||
|
|
||||||
|
### 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. |
||||||
|
|
||||||
|
### Operador: Contant |
||||||
|
|
||||||
|
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 %} |
||||||
|
|
||||||
|
### 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> |
||||||
|
|
||||||
|
# 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. |
||||||
|
|
||||||
|
<!-- |
||||||
|
vim:spelllang=pt: |
||||||
|
--> |
Loading…
Reference in new issue