Skip to content

FIX (Foreign Information Exchange) protocol implementation for Erlang

License

Notifications You must be signed in to change notification settings

basiliscos/erl-erlyfix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

erlyfix

Travis Hex.pm Version codecov license

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', [
    {'SenderCompID', <<"me">>},                         % field
    {'TargetCompID', <<"you">>},
    {'MsgSeqNum', 1},
    {'SendingTime', <<"20171109-16:19:07.541">>},
    {'Instrument', [{'Symbol', <<"EURCHF">>}] },        % component
    {'MDReqID', <<"31955:1510225047.01637:EURCHF">>},
    {'MDFullGrp', [{'NoMDEntries', [                    % group
        [{'MDEntryType', <<"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, 'Logon', TagsMarkup, <<>>} = erlyfix_parser:parse(BinaryMessage, Protocol),

The fields BeginString, BodyLength, MsgType and CheckSum are managed by the library. They should not be provided for messages serialization.

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, 'BeginString', F_BeginString, <<"FIX.4.4">>},
        {field, 'BodyLength', F_BodyLength, 102},
        {field, 'MsgType', F_MsgType, <<"A">>},
        {field, 'XmlDataLen', F_XmlDataLen, <<"1">>},
        {field, 'XmlData', F_XmlData, <<1>>},
        {field, 'SenderCompID', F_SenderCompID, <<"me">>},
        {field, 'TargetCompID',F_TargetCompID, <<"you">>},
        {field, 'MsgSeqNum', F_MsgSeqNum, <<"1">>},
        {field, 'SendingTime', F_SendingTime, <<"20090107-18:15:16">>},
    {finish,header,undefined},
    {start,body,undefined},
        {field, 'EncryptMethod', F_EncryptMethod, <<"0">>},
        {field, 'HeartBtInt', F_HeartBtInt, <<"60">>},
        {start,group,{'NoMsgTypes',2}},
            {field, 'RefMsgType', F_RefMsgType, <<"abc">>},
            {field, 'MsgDirection', F_MsgDirection, << "S">>},
            {field, 'RefMsgType', F_RefMsgType, <<"def">>},
            {field, 'MsgDirection', F_MsgDirection, <<"R">>},
        {finish,group,{'NoMsgTypes',2}},
    {finish,body,undefined},
    {start,trailer,undefined},
        {field, 'CheckSum', 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, 'MarketDataSnapshotFullRefresh', [
    {'SenderCompID', <<"me">>},
    {'TargetCompID', <<"you">>},
    {'MsgSeqNum', 1},
    {'SendingTime', <<"20171109-16:19:07.541">>},
    {'Instrument', [{'Symbol', <<"EURCHF">>}] },
    {'MDReqID', <<"31955:1510225047.01637:EURCHF">>},
    {'MDFullGrp', [{'NoMDEntries', [
        [{'MDEntryType', <<"BID">>}, {'MDEntryPx', <<"1.07509">>},
            {'MDEntrySize', <<"200000">>}, {'QuoteCondition', <<"OPEN">>},
            {'MDEntryOriginator', <<"PromoXM">>, {'QuoteEntryID', <<"82837831">>}}],
        [{'MDEntryType', <<"OFFER">>}, {'MDEntryPx', <<"1.07539">>},
            {'MDEntrySize', <<"100000">>}, {'QuoteCondition', <<"OPEN">>},
            {'MDEntryOriginator', <<"PromoXM1">>, {'QuoteEntryID', <<"82837832">>}}]
    ]}]}
]),
Msg = iolist_to_binary(IoList),
{ok, 'MarketDataSnapshotFullRefresh', 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, 'Symbol', _F, V} -> {ok, [ {symbol, V} | Stack ]};
        {field, 'MDEntryType', F, V} ->
            case erlyfix_fields:as_label(V, F) of
                <<"BID">> -> {ok, [{bid, #{} } | Stack]};
                <<"OFFER">> -> {ok, [{ask, #{}} | Stack]}
            end;
        {field, 'MDEntryPx', _F, V} ->
            [{Type, Map0} | T] = Stack,
            Price = binary_to_float(V),
            {ok, [ {Type, Map0#{price => Price} } | T ] };
        {field, 'MDEntrySize', _F, V} ->
            [{Type, Map0} | T] = Stack,
            Volume = binary_to_integer(V),
            {ok, [ {Type, Map0#{volume => Volume} } | T ] };
        {field, 'MDEntryOriginator', _F, V} ->
            [{Type, Map0} | T] = Stack,
            {ok, [ {Type, Map0#{source => V} } | T ] };
        {start,group,{'NoMDEntries',Count}} ->
            case Count of
                2 -> {ok, [{group, 'NoMsgTypes' } | Stack]};
                _ -> {error, Stack}
            end;
        {finish,group, {'NoMDEntries',2}} ->
            [E1, E2, {group, 'NoMsgTypes' } | 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.

About

FIX (Foreign Information Exchange) protocol implementation for Erlang

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published