erlyfix

Traviscodecovlicense

FIX (Foreign Information eXchange) protocol implementation in erlang.

Versions

Versions supported: R18 and up

Description

This FIX protocol implementation based on XML FIX-protocol definitions provided by quickfixengine.org. It is possible to have own/proprietary extenstion XML, which extends the quickfix's definition.

The library provides only serialization, deserialization and basic validation of FIX-messages and does not provides network layer. You have to build your own FIX-client/server.

Synopsis

% loading protocol
{ok, Protocol} = erlyfix_protocol:load("test/FIX44.xml"),
{ok, Protocol} = erlyfix_protocol:load("test/FIX44.xml", "test/extension-sample.xml"),

% message serialization
{ok, IoList} = erlyfix_protocol:serialize(Protocol, 'MarketDataSnapshotFullRefresh', [
    {&#39;SenderCompID&#39;, <<"me">>},                         % field
    {&#39;TargetCompID&#39;, <<"you">>},
    {&#39;MsgSeqNum&#39;, 1},
    {&#39;SendingTime&#39;, <<"20171109-16:19:07.541">>},
    {&#39;Instrument&#39;, [{&#39;Symbol&#39;, <<"EURCHF">>}] },        % component
    {&#39;MDReqID&#39;, <<"31955:1510225047.01637:EURCHF">>},
    {&#39;MDFullGrp&#39;, [{&#39;NoMDEntries&#39;, [                    % group
        [{&#39;MDEntryType&#39;, <<"BID">>}]
    ]}]}
]),

% message deserialization
BinaryMessage = <<"8=FIX.4.4", 1, "9=102", 1, "35=A", 1,
    "212=1", 1, "213=", 1, 1,"49=me", 1, "56=you", 1,
    "34=1", 1, "52=20090107-18:15:16", 1, "98=0", 1, "108=60", 1, "384=2", 1,
    "372=abc", 1, "385=S", 1, "372=def", 1, "385=R", 1, "10=232", 1
>>,
{ok, &#39;Logon&#39;, TagsMarkup, <<>>} = erlyfix_parser:parse(BinaryMessage, Protocol),

TagsMarkup is flat list of tags uplifted to fields/groups/components. It is expected to be processed by lists:foldl/3. Tagsmarkup is something like that:

[
    {start, header, undefined},
        {field, &#39;BeginString&#39;, F_BeginString, <<"FIX.4.4">>},
        {field, &#39;BodyLength&#39;, F_BodyLength, 102},
        {field, &#39;MsgType&#39;, F_MsgType, <<"A">>},
        {field, &#39;XmlDataLen&#39;, F_XmlDataLen, <<"1">>},
        {field, &#39;XmlData&#39;, F_XmlData, <<1>>},
        {field, &#39;SenderCompID&#39;, F_SenderCompID, <<"me">>},
        {field, &#39;TargetCompID&#39;,F_TargetCompID, <<"you">>},
        {field, &#39;MsgSeqNum&#39;, F_MsgSeqNum, <<"1">>},
        {field, &#39;SendingTime&#39;, F_SendingTime, <<"20090107-18:15:16">>},
    {finish,header,undefined},
    {start,body,undefined},
        {field, &#39;EncryptMethod&#39;, F_EncryptMethod, <<"0">>},
        {field, &#39;HeartBtInt&#39;, F_HeartBtInt, <<"60">>},
        {start,group,{&#39;NoMsgTypes&#39;,2}},
            {field, &#39;RefMsgType&#39;, F_RefMsgType, <<"abc">>},
            {field, &#39;MsgDirection&#39;, F_MsgDirection, << "S">>},
            {field, &#39;RefMsgType&#39;, F_RefMsgType, <<"def">>},
            {field, &#39;MsgDirection&#39;, F_MsgDirection, <<"R">>},
        {finish,group,{&#39;NoMsgTypes&#39;,2}},
    {finish,body,undefined},
    {start,trailer,undefined},
        {field, &#39;CheckSum&#39;, F_CheckSum, 232},
    {finish,trailer,undefined}
],

where F_* is opaque field structure. Please note, that identations are for humans-only, the list itself is flat

Here is an example of folding tags markup:


-record(quote, {
    price,
    volume,
    source
}).
-record(tick, {
    symbol,
    bid,
    ask
}).
...

{ok, IoList} = erlyfix_protocol:serialize(Protocol, &#39;MarketDataSnapshotFullRefresh&#39;, [
    {&#39;SenderCompID&#39;, <<"me">>},
    {&#39;TargetCompID&#39;, <<"you">>},
    {&#39;MsgSeqNum&#39;, 1},
    {&#39;SendingTime&#39;, <<"20171109-16:19:07.541">>},
    {&#39;Instrument&#39;, [{&#39;Symbol&#39;, <<"EURCHF">>}] },
    {&#39;MDReqID&#39;, <<"31955:1510225047.01637:EURCHF">>},
    {&#39;MDFullGrp&#39;, [{&#39;NoMDEntries&#39;, [
        [{&#39;MDEntryType&#39;, <<"BID">>}, {&#39;MDEntryPx&#39;, <<"1.07509">>},
            {&#39;MDEntrySize&#39;, <<"200000">>}, {&#39;QuoteCondition&#39;, <<"OPEN">>},
            {&#39;MDEntryOriginator&#39;, <<"PromoXM">>, {&#39;QuoteEntryID&#39;, <<"82837831">>}}],
        [{&#39;MDEntryType&#39;, <<"OFFER">>}, {&#39;MDEntryPx&#39;, <<"1.07539">>},
            {&#39;MDEntrySize&#39;, <<"100000">>}, {&#39;QuoteCondition&#39;, <<"OPEN">>},
            {&#39;MDEntryOriginator&#39;, <<"PromoXM1">>, {&#39;QuoteEntryID&#39;, <<"82837832">>}}]
    ]}]}
]),
Msg = iolist_to_binary(IoList),
{ok, &#39;MarketDataSnapshotFullRefresh&#39;, Markup, <<>>} = erlyfix_parser:parse(Msg, Protocol),

M2Q = fun(M) ->
    #quote{
        price = maps:get(price, M),
        volume = maps:get(volume, M),
        source = maps:get(source, M)
    }
end,

F = fun(E, {Result, Stack} = Acc ) ->
    case E of
        {field, &#39;Symbol&#39;, _F, V} -> {ok, [ {symbol, V} | Stack ]};
        {field, &#39;MDEntryType&#39;, F, V} ->
            case erlyfix_fields:as_label(V, F) of
                <<"BID">> -> {ok, [{bid, #{} } | Stack]};
                <<"OFFER">> -> {ok, [{ask, #{}} | Stack]}
            end;
        {field, &#39;MDEntryPx&#39;, _F, V} ->
            [{Type, Map0} | T] = Stack,
            Price = binary_to_float(V),
            {ok, [ {Type, Map0#{price => Price} } | T ] };
        {field, &#39;MDEntrySize&#39;, _F, V} ->
            [{Type, Map0} | T] = Stack,
            Volume = binary_to_integer(V),
            {ok, [ {Type, Map0#{volume => Volume} } | T ] };
        {field, &#39;MDEntryOriginator&#39;, _F, V} ->
            [{Type, Map0} | T] = Stack,
            {ok, [ {Type, Map0#{source => V} } | T ] };
        {start,group,{&#39;NoMDEntries&#39;,Count}} ->
            case Count of
                2 -> {ok, [{group, &#39;NoMsgTypes&#39; } | Stack]};
                _ -> {error, Stack}
            end;
        {finish,group, {&#39;NoMDEntries&#39;,2}} ->
            [E1, E2, {group, &#39;NoMsgTypes&#39; } | T] = Stack
            {T1, M1} = E1,
            Q1 = M2Q(M1),
            {T2, M2} = E2,
            Q2 = M2Q(M2),
            case {T1, T2} of
                {bid, ask} -> {ok, [Q1, Q2 | T]};
                {ask, bid} -> {ok, [Q2, Q1 | T]}
            end;
        {finish,trailer,_} ->
            case Result of
                ok ->
                    [Bid, Ask, {symbol, Symbol}] = Stack,
                    Tick = #tick{ bid = Bid, ask = Ask, symbol = Symbol },
                    {ok, Tick};
                _ -> Acc
            end;
        _ -> Acc
    end
end,
{ok, Tick} = lists:foldl(F, {ok, []}, Markup),

It is possible to map field binary value to human-readable binary, i.e. when F points to MDEntryType field, and V contains <<"1">>, it is possible to map it to binary<<OFFER>>

Label = erlyfix_fields:as_label(V, F).

(It is not possible to use atoms here, as the string description in XML-specification exceed possible atom length in Erlang).

In general it is assumed that you should use this library with tigth cooperation with XML-specifictions.

Build

$ rebar3 compile

License

Apache 2

See also

https://github.com/maxlapshin/fix - FIX-client implementation as OTP-application, includes network layer.