Browse Source

Translated the FAST post

master
Julio Biason 3 years ago
parent
commit
3e558a2aee
  1. 294
      content/research/decoding-fast.md
  2. 552
      content/research/decoding-fast.pt.md

294
content/research/decoding-fast.md

@ -39,8 +39,8 @@ defined by the protocol (there is a whole list
[here](https://www.inforeachinc.com/fix-dictionary/index.html)) but each [here](https://www.inforeachinc.com/fix-dictionary/index.html)) but each
exchange can create their own IDs. exchange can create their own IDs.
For example, if you have MsgType (ID 35) with value "`y`" and Security ID (ID For example, if you have the field MsgType (ID 35) with value "`y`" and the
48) with value "`123456`", you'd get the message: field Security ID (ID 48) with value "`123456`", the message received would be
``` ```
35=y^A48=123456 35=y^A48=123456
@ -49,12 +49,13 @@ For example, if you have MsgType (ID 35) with value "`y`" and Security ID (ID
# And Back to FAST # And Back to FAST
One of the things FAST is designed for is removing duplicate and/or constant One of the things FAST is designed for is removing duplicate and/or constant
content. For example, MsgType (ID 35) is the "SecurityList" message, which content. For example, MsgType (ID 35) identifies that the message is
contains information about all the symbols (the their security IDs) handled by "SecurityList" (symbol list), which contains information about all the symbols
the exchange. Because the exchange is the same in all the symbols, FAST allows (the their security IDs) available in the exchange. Because the exchange is the
defining the fields related to it (Source, field ID 22, and Exchange, field ID same in all the symbols, FAST allows defining the fields related to it (Source,
207) to constant values, so they don't need to be transmitted and, when decoding field ID 22, and Exchange, field ID 207) to constant values, so they don't need
FAST back to FIX, the decoder simply add the constant value. to be transmitted and, when decoding FAST back to FIX, the decoder simply add
the constant value.
To know which fields are constant and which are not (and some other information), To know which fields are constant and which are not (and some other information),
the protocol defines a template, which have a well defined schema, to report the protocol defines a template, which have a well defined schema, to report
@ -62,21 +63,20 @@ that information.
# The Template # The Template
The template is, as mentioned before, a XML file (which the protocol definition The template is a XML file which describes the fields, their types, names, IDs
doesn't provide any default way to actually receive that field, and thus is left and operators. The protocol itself doesn't provide any default way to actually
for the exchange to find their way) which describes field types, names, IDs and receive that field, and thus is left for the exchange to find their way.
operators.
Note that the template describe the field IDs and their types, which the Note that the template describe the field IDs and their types, and the incoming
incoming data have only the values. If we use the FIX description above, the data have only the values. If we use the FIX description above, the template
template defines the left side of the pair, while the incoming have have only defines the left side of the pair, while the incoming data have have only the
the right side. right side.
## Field Types ## Field Types
The protocol have a few field types: Unsigned Ints of 32 and 64 bits, Signed The protocol have a few field types: Unsigned Ints of 32 and 64 bits, Signed
Ints of 32 and 64 bits, ASCII strings, UTF-8 strings, sequences, decimals and a Ints of 32 and 64 bits, ASCII strings, UTF-8 strings, sequences, decimals and a
type called "presence map". special type called "Presence Map".
One thing to note is that all fields use a "stop bit" format. This is quite One thing to note is that all fields use a "stop bit" format. This is quite
similar to UTF8, although UTF8 uses a "continuation bit" instead of "stop bit", similar to UTF8, although UTF8 uses a "continuation bit" instead of "stop bit",
@ -85,23 +85,25 @@ but the process of reading is the same:
- Read a byte; - Read a byte;
- Does it have the high order by set to 0? - Does it have the high order by set to 0?
- Yes: Keep reading; - Yes: Keep reading;
- No: Stop reading the conclude the field value. - No: Stop reading the field value.
I'll show some examples later in this post.
## Field definitions ## Field definitions
On the template, the fields have their type, name (optional), ID, a presence The template defines the fields with their type, name (optional), ID, a
indicator and an operator (optional). presence indicator and an operator (optional).
For example, if you have an unsigned int of 32 bits, named "MsgType" with ID For example, to describe an unsigned int of 32 bits, named "MsgType" with ID
"35", that would be described in the template as "35", it would be described in the template as
```xml ```xml
<uInt32 name="MsgType" id="35"/> <uInt32 name="MsgType" id="35"/>
``` ```
Because there is no indication of presence, it is assumed that the field is Because there is no indication of presence, it is assumed that the field is
"mandatory" and should always have a value. On the other hand, if you have a "mandatory" and should always have a value. On the other hand, if the field
field defined as definition was something like
```xml ```xml
<int32 name="ImpliedMarketIndicator" id="1144" presence="optional"/> <int32 name="ImpliedMarketIndicator" id="1144" presence="optional"/>
@ -110,17 +112,34 @@ field defined as
... then the field may not not have a value. This is also referred as "nullable" ... then the field may not not have a value. This is also referred as "nullable"
field. field.
## Optional and Mandatory Fields
The difference between optional and mandatory fields is that mandatory fields
*always* have a value, even if 0. Optional fields, on the other hand, can be
null and have no value at all.
{% note() %}
I have the impression that this is done if the exchange wants to mark a field
as "do not appear in the FIX version"; so even if the field have a definition,
the resulting FIX version would not have the field.
So it is more of a "backwards compatibility" than anything else.
{% end %}
## Types
### Types: Ints ### Types: Ints
To read an Int, you pick the 7 low order bits (everything except the high order To read an Int, the decoder would pick the 7 low order bits (everything except
one) and move to the resulting variable. If the stop bit is there, you're done; the high order one) and move them to the resulting variable. If the stop bit is
if it is not, you shift the result by 7 bits and add the 7 bits from the next there, the read is complete; if it is not, the result should be shifted by 7
byte and so on, till you find a byte with the stop bit set. bits and add the 7 bits from the next byte and so on, till you find a byte with
the stop bit set.
The 32 and 64 bits only define the maximum value of the field and should not be The 32 and 64 bits only define the maximum value of the field and should not
used as "number of bits to be read" -- because of the stop bit. If the value necessarily be used as "number of bits to be read" -- because of the stop bit.
exceeds 32 or 64 bits, that is considered an error and the processing should be If the value exceeds 32 or 64 bits, that is considered an error and the
aborted. processing should be aborted.
Signed Int work exactly the same, but as 2's complement. Signed Int work exactly the same, but as 2's complement.
@ -133,32 +152,32 @@ to make it easier to read):
``` ```
... the decoder will read the first byte and see that it doesn't have the high ... the decoder will read the first byte and see that it doesn't have the high
order bit set, so it keep just the "1" for the value and shift everything by 7 order bit set, so it keep just the "1" for the value and shift 7 bits to the
bits. Then the second byte is read; this one have the high order bit set, so left. Then the second byte is read; this one have the high order bit set, so
the remaining bits (in this case "001_0010") are added to the resulting value the remaining bits (in this case "001_0010") are added to the resulting value
and get `1001_0010` -- or 146. and get `1001_0010` -- or 146.
Negative numbers are represented using 2's so you'd get, for example: Negative numbers are represented using 2's so if the decoder receives, for
example:
``` ```
0000_0011 0111_1110 1110_1110 0000_0011 0111_1110 1110_1110
``` ```
... which, when you remove the high order bits and follow the high order to find ... which, when the high order bits are removed, it should generate "`1111_1111
the stop bit, you get "`1111_1111 0110_1110`", which is -146 (in 16 bits, just 0110_1110`", which is -146 (in 16 bits, just to make it shorter).
to make it shorter).
When an integer field is optional, the result must be decremented by 1. The When an integer field is optional, the result must be decremented by 1. The
reason for this is that, when the field is marked as optional -- also called reason is that it should be something to differentiate both 0 and Null. So, an
"nullable" -- we need something to differentiate both 0 and Null. So, an optional integer with value 0 is, actually, Null; to get the value 0 for the
optional integer with value 0 is, actually, Null; if we have a value of 0, we field, the incoming data will have value 1, which is decremented in 1 and goes
incoming data will have the value 1, which we'll decrement by 1 and become 0. back to 0.
### Types: Strings ### Types: Strings
ASCII strings are pretty simple to read: Again, you keep reading the incoming ASCII strings are pretty simple to read: Again, the decoder keeps reading the
data till you find a byte with the high order bit set (again, the stop bit) and incoming data till you find a byte with the high order bit set (again, the stop
just convert to their respective ASCII character. bit) and just convert to their respective ASCII character.
For example For example
@ -166,9 +185,9 @@ For example
0100_1000 0110_0101 0110_1100 0110_1100 1110_1111 0100_1000 0110_0101 0110_1100 0110_1100 1110_1111
``` ```
Would generate the bytes 72, 101, 108, 108 and 111, which using the values as Would generate the bytes 72, 101, 108, 108 and 111, which using the ASCII table
ASCII codes would result in "Hello". Note that the stop bit here represents "end would result in "Hello". Note that the stop bit here represents "end of string"
of string" and the bytes should not be grouped like in Ints. and the bytes should not be grouped like in Ints.
{% note() %} {% note() %}
So far, I didn't find any UTF8 strings, so I'm not quite sure how to process So far, I didn't find any UTF8 strings, so I'm not quite sure how to process
@ -187,22 +206,22 @@ Sequences are basically arrays. The first field of a sequence is the "length"
present. Inside the sequence, you have a list of field definitions, which may present. Inside the sequence, you have a list of field definitions, which may
even include more sequences. even include more sequences.
Optional Sequences follow the same idea of optional Ints: You read the length Optional sequences affect the way the length is read: If optional, the length
and, it is null, there is nothing in the sequence -- and mandatory Sequences can should be treated as an optional Integer and thus the size is decremented by 1.
be zero.
### Types: Decimals ### Types: Decimals
Decimals are formed by two fields: Exponent and Mantissa. The way it works is Decimals are formed by two fields: Exponent and Mantissa. The way it works is
that if you have an Exponent of "-2" and a Mantissa of "1020", you'd do `1020 * that if you have an Exponent of "-2" and a Mantissa of "1020", the resulting
10 ^ -2` ("1020 times 10 to the power of -2"), and the actual value is "10.20". value is `1020 * 10 ^ -2` ("1020 times 10 to the power of -2"), or "10.20".
Both Exponent and Mantissa are read as Signed Ints. Both Exponent and Mantissa are read as Signed Ints.
An Optional Decimal means the Exponent is optional. The documentation says that An Optional Decimal means the Exponent is optional. The documentation says that
the Mantissa is always mandatory, but there is a catch: If the Exponent is null, the Mantissa is always mandatory, but there is a catch: If the Exponent is
then the Mantissa is not present and shouldn't be read; otherwise, you read the null, then the Mantissa is not present and the whole Decimal is Null;
Mantissa and apply the conversion. otherwise, the decoder is expected to read the Mantissa and the formula above
should be applied.
Also, because Exponent and Mantissa are two fields, they can have different Also, because Exponent and Mantissa are two fields, they can have different
operators. I'll show some examples after the Operator, mostly because I've seen operators. I'll show some examples after the Operator, mostly because I've seen
@ -210,18 +229,19 @@ both with different operators and they make a mess to read.
### Type: Presence Map ### Type: Presence Map
Presence Maps are used in conjunction with operators. They are read basically Presence Maps are used in conjunction with operators. They are read like
like you'd read an unsigned int (read bytes till you find the one with the high unsigned Ints are read (read bytes till you find the one with the high order
order bit) but do not have any conversion in themselves. Every time you need to bit, remove the high order bits and put all the bits in sequence) but do not
check if a field is present by checking the presence map, you consume the high have any conversion in themselves. Every time the decoder need to check if a
field is present in the presence map, the bit is consumed and the line
moves on.
Presence Maps are not present in the template and their presence is implied if Presence Maps are not present in the template and their presence is implied if
there is the need for one. For example, in a pure mandatory sequence of fields, there is at least one field that requires it. For example, if all the fields in
there will be no presence map at all. a sequence are mandatory, there will be no Presence Map in the incoming data.
order bit from it, so it is never used again.
The bits in the Presence Map are in the order of the required fields. For The bits in the Presence Map are in the order of the fields in the template.
example, if a template with: For example, if a template with:
1. A mandatory field; 1. A mandatory field;
2. A field with an operator that requires the presence map (I'll mention those 2. A field with an operator that requires the presence map (I'll mention those
@ -229,17 +249,16 @@ example, if a template with:
3. Another mandatory field; 3. Another mandatory field;
4. And, finally, another field with operator. 4. And, finally, another field with operator.
You may receive a Presence Map as `1110_0000`, in which: ... it is possible to have a Presence Map as `1110_0000`, in which:
1. The first bit is the stop bit, so the decoder assumes this is the last byte 1. The first bit is the stop bit, so the decoder assumes this is the last byte
of the presence map. of the presence map.
2. The second bit indicates that the first field with operator is present. It 2. The second bit indicates that the first field with operator is present. It
does *not* represent the mandatory field, 'cause, well, it is mandatory and, does *not* represent the mandatory field 'cause, well, it is mandatory and,
thus, is always present. thus, is always present.
3. The second bit indicates the second field with an operator. 3. The second bit indicates the second field with an operator is present.
Again, I'll mention which ones the decoder should be checked in the presence (Again, I'll mention which operators require the presence map.)
map.
## Operators ## Operators
@ -255,18 +274,18 @@ operators:
### Operator: No Operator ### Operator: No Operator
When there is no operator defined, you have a "no operator" operator. It means When there is no operator defined in the field definition in the template, then
there is no special way of dealing with the incoming value: You just capture it the operator is the "no operator" operator. It means there is no special way of
and use it. dealing with the incoming value.
When a field have No Operator, there will be no bit in the Presence Map. When a field have No Operator, there will be no bit in the Presence Map.
### Operator: Constant ### Operator: Constant
A field with the Constant operator will not appear in the incoming data and you A field with the Constant operator will not appear in the incoming data and the
should assume that its value is the value in the constant. Previously I decoder should assume that its value is the value in the constant. Previously I
mentioned that a list of securities may have the field 22, "Source", and field mentioned that a list of securities may have the field 22, "Source", and field
207, "Exchange", with constant values, they would be defined as 207, "Exchange", with constant values, so they would be defined as
```xml ```xml
<string name="Source" id="22"> <string name="Source" id="22">
@ -277,18 +296,25 @@ mentioned that a list of securities may have the field 22, "Source", and field
</string> </string>
``` ```
There is a catch, though: When a constant can be Null (`presence="optional"`), There is a catch, though: When a Constant field can be Null
then the decoder needs to use the Presence Map bit; if it is set, the constant (`presence="optional"`), then the decoder needs to use the Presence Map bit; if
value should be used; if it is not set, then the field value is Null. it is set, the constant value should be used; if it is not set, then the field
value is Null.
The Presence Map should be use only if there is a field with a constant value The Presence Map should be use only if there is a field with a constant value
that is optional. that is optional.
### Operator: Default ### Operator: Default
The Default operator is similar to the Constant operator, but the decoder needs The Default operator is similar to the Constant operator, but if the bit for
to check the Presence Map; if the bit for the field is set, then you use the the field is set in the Presence Map, then the value for the field should be
default value; if it is not set, then the field is Null. read in the incoming data; otherwise, the Default value should be used.
{% note() %}
In a way, you can read this: Is the value present in the incoming data
(indicated in the Presence Map)? Then read the value in the incoming data;
otherwise, use the default value.
{% end %}
### Operator: Copy ### Operator: Copy
@ -296,8 +322,11 @@ The copy operator indicates that the value for this record have the same value
of the previous record; if it is the first record, then the value should be of the previous record; if it is the first record, then the value should be
used. If the Presence Map bit is set for the field, then the decoder must read used. If the Presence Map bit is set for the field, then the decoder must read
the value in the incoming data; if it is not set, then the previous value should the value in the incoming data; if it is not set, then the previous value should
be used. In the data I saw, every first record have the bit set, so you get the be used.
initial/previous value.
{% note() %}
In the data I saw, every first record have the bit set.
{% end %}
An example: You can have a template like An example: You can have a template like
@ -307,11 +336,11 @@ An example: You can have a template like
</string> </string>
``` ```
... and you have the following records and their Presence Maps: ... and the incoming data had the following records and their Presence Maps:
1. The first record have the bit set for this field in the Presence Map and the 1. The first record have the bit set for this field in the Presence Map and the
strings reads "first". This record will have this field with the value strings reads "first". The result for this field in this record will be
"first". the value "first".
2. The second record doesn't have the bit set in the Presence Map. So the 2. The second record doesn't have the bit set in the Presence Map. So the
decoder reuses the previous value and this record will have the field with decoder reuses the previous value and this record will have the field with
the value "first" (again). the value "first" (again).
@ -320,8 +349,7 @@ An example: You can have a template like
4. The fourth record doesn't have the bit set and the decoder reuses the value 4. The fourth record doesn't have the bit set and the decoder reuses the value
"second" for the field. "second" for the field.
The Copy operator may have the initial value, so you don't need to read it. For The Copy operator may have the initial value. For example
example
```xml ```xml
<string name="MDReqID" id="262"> <string name="MDReqID" id="262">
@ -330,7 +358,7 @@ example
``` ```
This means that you should use "string" as previous value, even in the first This means that you should use "string" as previous value, even in the first
field. record.
As pointed, fields with the Copy operator appear in the Presence Map. As pointed, fields with the Copy operator appear in the Presence Map.
@ -338,7 +366,7 @@ As pointed, fields with the Copy operator appear in the Presence Map.
Delta is an operator similar to Copy, but instead of using the value of the Delta is an operator similar to Copy, but instead of using the value of the
previous record in this field, the new value must be computed using the previous previous record in this field, the new value must be computed using the previous
value and the current one. Again, if you have no previous value, then there is value and the current one. Again, if there is no previous value, then there is
no operation to be done and the incoming value is the current one. no operation to be done and the incoming value is the current one.
An example: An example:
@ -353,18 +381,20 @@ An example:
field. field.
2. The second record comes with the value "2". That should be added in the 2. The second record comes with the value "2". That should be added in the
previous value and used, so the field for the second record is "302". previous value and used, so the field for the second record is "302".
3. The third record comes with the value "3". Again, you reuse the previous 3. The third record comes with the value "3". Again, the previous value should
value and add the current one. So the field for the third record have the be used and the current one should be added. So the field for the third
value "305". record have the value "305".
Fields with the Delta operator do not appear in the Presence Map. Fields with the Delta operator do not appear in the Presence Map.
### Operator: Increment ### Operator: Increment
Increment is another operator that works similar to the Copy operator, but if Increment works like the Delta operator 'cause it always add a value to the
its bit is set in the Presence Map, the decoder reads the field value from the previous one, but the value to be added is always 1.
incoming data; if it is not set, the decoder does not read any data, but reuses
the previous value with an increment of 1. If the bit for the field is set in the Presence Map, the value for the field is
the one in the incoming data; otherwise, the value will be the previous value
added by 1.
Example: Example:
@ -399,8 +429,8 @@ Map, [according to JetTek](https://jettekfix.com/education/fix-fast-tutorial/):
</tr> </tr>
<tr> <tr>
<td>Constant</td> <td>Constant</td>
<td>No, the Constant value should be used.</td> <td>No, the Constant value should always be used.</td>
<td>Yes; if set, use the Constant value; otherwise the field is Null.</td> <td>Yes; if set, use the Constant value; otherwise the field value is Null.</td>
</tr> </tr>
<tr> <tr>
<td>Copy</td> <td>Copy</td>
@ -417,7 +447,8 @@ Map, [according to JetTek](https://jettekfix.com/education/fix-fast-tutorial/):
</tr> </tr>
<tr> <tr>
<td>Delta</td> <td>Delta</td>
<td>No; the value should always be added to the previous one.</td> <td>No; the value in the incoming data should always be added to the
previous one.</td>
<td>No; same as Mandatory fields.</td> <td>No; same as Mandatory fields.</td>
</tr> </tr>
<tr> <tr>
@ -452,47 +483,60 @@ That seems simple. But there are a lot of moving pieces here:
1. The `presence="optional"` in the decimal means that the `exponent` can be 1. The `presence="optional"` in the decimal means that the `exponent` can be
Null and only that. Null and only that.
2. The `default` operator in the Exponent means the decoder must check if the 2. The Default operator in the Exponent means the decoder must check if the
Exponent have a value or should use the default value of "0". Exponent have a value in the incoming data or should use the default value
of "0".
There is another issue here: If the Presence Map indicates that the value is There is an issue here: If the Presence Map indicates that the value is
present and the read value is 0, because the Exponent is optional, it should present and the read value is 0, because the field is optional, the decoder
be considered Null and, thus, there is no Mantissa and everything is Null. should consider the Exponent Null and, thus, there is no Mantissa and
3. The `delta` operator in the Mantissa should be used applying the incoming everything is Null.
value to the previous one. But, if the Exponent is Null, then there is no 3. The Delta operator in the Mantissa should be used applying the incoming
Mantissa, but the previous value is kept. value to the previous one. But, if the Exponent is not in the presence map,
because it has a default value, that's its value and it shouldn't be read in
the incoming data and the read value is actually applied to the Mantissa.
This causes a bunch of weird, "exception of the rule" dealings: Like this:
1. The first record have the field set in the Presence Map and it is read as 1. The first record have the field set in the Presence Map and it is read as
"-2". That's the Exponent, reading the mantissa gives the value "1020", so "-2"; that's the Exponent. The next value is "1020", which is the Mantissa,
the whole decimal is "10.20"; so the whole decimal is "10.20";
2. The second record have the field set in the Presence Map and it is read as 2. The second record have the field set in the Presence Map and it is read as
"0". Because the decimal is optional, the exponent is optional, and because "0". Because the decimal is optional, the exponent is optional, and because
0 is Null, there is no Exponent, and the next value is *not* the Mantissa. 0 is Null for optional fields, there is no Exponent, and the next value is
3. The third record have the field set in the Presence Map and it is again, *not* the Mantissa. The value for the field in this record is Null.
"-2" for the Exponent and we read the Mantissa. The value read for the 3. The third record have the field not set in the Presence Map. Because
Mantissa is "-20", but instead of assuming that the Mantissa was Null in the Exponent has a default value, it becomes "0", and the Mantissa should be read.
previous record, it uses the first record value, so the Mantissa for this If the incoming data have the value "10", the decimal becomes "10" (or
record is "1000" and the value for the decimal is "10.00". "10.00", if we use the same decimal places everywhere).
Another weird thing I saw was related to the way the exchange was ordering the A weird thing I saw was related to the way the exchange was ordering the
results. It had a sequence of sell and buy orders in which results. It had a sequence of sell and buy orders in which
1. The first record was the sell order, with an Exponent of 0 and a Mantissa of 1. The first record was the sell order, with an Exponent of "0" and a Mantissa
"5410". That meant the value is "5410" (pretty straight). of "5410". That meant the value is "5410" (pretty straight).
2. The second record was the buy order. It had an Exponent of "-2" and the 2. The second record was the buy order. It had an Exponent of "-2" and the
Mantissa had an incoming value of 526604. That gives the value of "532014", Mantissa had an incoming value of "526604". With the Delta operador, that
but because the Exponent is "-2", the actual value is "5320.14". gives the value of "532014", but because the Exponent is "-2", the actual
value is "5320.14".
3. The weird thing happened in the third record, which was again a sell order. 3. The weird thing happened in the third record, which was again a sell order.
The value should be exactly the same as the first, but the exchange sent an The value should be exactly the same as the first, but the exchange sent an
Exponent of 0 and a Mantissa of "−526604". With the delta, that would bring Exponent of "0" and a Mantissa of "−526604". With the delta, that would bring
the value back to "5410". the value back to "5410".
I found it weird that they kept jumping between two different Exponents instead I found it weird that they kept jumping between two different Exponents instead
of using a single one, and at the time I had some issues with the delta math in of using a single one, and at the time I had some issues with the delta math in
my code, so... my code, so...
## Null Length Sequences
Another strange thing I saw was the optional sequence: In practice there is no
difference between a sequence with 0 elements and one which the length is Null
-- specially if you think the protocol is, basically, a FIX generator. I have
no idea why it wasn't standardized that lengths are mandatory and a value of 0
means there is no values on it instead of doing a dance around optional and
mandatory sequences.
--- ---
### Changelog: ### Changelog:

552
content/research/decoding-fast.pt.md

@ -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…
Cancel
Save