objectcomputing / mFAST

A FAST (FIX Adapted for STreaming) encoder/decoder
http://objectcomputing.github.io/mFAST
BSD 3-Clause "New" or "Revised" License
220 stars 114 forks source link

Encoder does not properly encode the message (incorrect copy operator behavior) #74

Closed korst1k closed 5 years ago

korst1k commented 7 years ago

Seems, that the copy operator implementation does not work correctly. When i try to encode following message (printed by implementation of the Visitor pattern):

{ MessageType: 'W', BeginString: 'FIXT.1.1', ApplVerID: '9', SenderCompID: 'MOEX', MsgSeqNum: '1', SendingTime: '20170303360537504', LastMsgSeqNumProcessed: '1', RptSeq: '1', LastFragment: '1', RouteFirst: '1', TradSesStatus: '100', TradingSessionID: 'CETS', Symbol: 'USD000UTSTOM', MDSecurityTradingStatus: '17', GroupMDEntries: '
[0]: { MDEntryType: '0', MDEntryID: '1001', MDEntryDate: '20170329', MDEntryTime: '130341', OrigTime: '280076', MDEntryPx: '545*10^-1', MDEntrySize: '1000*10^0', OrderStatus: 'O', TradingSessionSubID: 'N',  }
[1]: { MDEntryType: '1', MDEntryID: '1002', MDEntryDate: '20170329', MDEntryTime: '130341', OrigTime: '280079', MDEntryPx: '56*10^0', MDEntrySize: '2000*10^0', OrderStatus: 'O', TradingSessionSubID: 'N',  }',  }
------------------------------------------------

i see that second MDEntryType value (it must be equal to '1' - ASK) does not present in the output stream:

OLS: 
 length = 79
  1   +0     : 01 00 00 00 C0  // preambula - first 4 bytes before encoded message and pmap
  2   +5     : 1C 90 // template id == 3600
  3   +7     : 81 
  4   +8     : 23 6A 19 0B 36 44 27 A0 
  5   +16    : 82 
  6   +17    : 81 
  7   +18    : 82 
  8   +19    : 82 
  9   +20    : 00 E5 
 10   +22    : 43 45 54 D3 
 11   +26    : 55 53 44 30 30 30 55 54 53 54 4F CD 
 12   +38    : 92 
 13   +39    : 82  // GroupMDEntries == 2
 14   +40    : 7F C0      // 1st record pmap
 15   +42    : B0    <<------ 1st MDEntryType == '0' - BID
 16   +43    : 31 30 30 B1     MDEntryID == "1001"
 17   +47    : 09 4F 0C DA 
 18   +51    : 07 7A A6 
 19   +54    : 11 0C 8D 
 20   +57    : FF 
 21   +58    : 04 A1 
 22   +60    : 81 
 23   +61    : 07 E8 
 24   +63    : CF 
 25   +64    : CE 
 26   +65    : 0E 80      // 2nd record pmap
                 : ???   <<------ Where is the 2nd MDEntryType == '1' - ASK? It must present in the output stream!
 27   +67    : 31 30 30 B2     // MDEntryID == "1002"
 28   +71    : 11 0C 90 
 29   +74    : 81 
 30   +75    : B8 
 31   +76    : 81 
 32   +77    : 0F D0 

Scheme from ftp://ftp.moex.ru/pub/FAST/ASTS/template/FIX50SP2-2017-Mar.xml:

<!--  Market Data - Snapshot/Full Refresh OLS CURR  -->
<template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="W-OLS-CURR" id="3600">
<string name="MessageType" id="35">
<constant value="W"/>
</string>
<string name="BeginString" id="8">
<constant value="FIXT.1.1"/>
</string>
<string name="ApplVerID" id="1128">
<constant value="9"/>
</string>
<string name="SenderCompID" id="49">
<constant value="MOEX"/>
</string>
<uInt32 name="MsgSeqNum" id="34"/>
<uInt64 name="SendingTime" id="52"/>
<uInt32 name="LastMsgSeqNumProcessed" id="369" presence="optional"/>
<int32 name="RptSeq" id="83"/>
<uInt32 name="LastFragment" id="893" presence="optional"/>
<uInt32 name="RouteFirst" id="7944" presence="optional"/>
<int32 name="TradSesStatus" id="340" presence="optional"/>
<string name="TradingSessionID" id="336" presence="optional"/>
<string name="Symbol" id="55"/>
<int32 name="MDSecurityTradingStatus" id="1682" presence="optional"/>
<sequence name="GroupMDEntries">
<length name="NoMDEntries" id="268"/>
<string name="MDEntryType" id="269" presence="optional">
<copy/>
</string>
<string name="MDEntryID" id="278" presence="optional"/>
<uInt32 name="MDEntryDate" id="272" presence="optional">
<copy/>
</uInt32>
<uInt32 name="MDEntryTime" id="273" presence="optional">
<copy/>
</uInt32>
<uInt32 name="OrigTime" id="9412" presence="optional">
<copy/>
</uInt32>
<decimal name="MDEntryPx" id="270" presence="optional">
<copy/>
</decimal>
<decimal name="MDEntrySize" id="271" presence="optional">
<copy/>
</decimal>
<string name="OrderStatus" id="10505" presence="optional">
<copy/>
</string>
<string name="TradingSessionSubID" id="625" presence="optional">
<copy/>
</string>
</sequence>
</template>

As a result, decoded orderbook looks like: BIDS: 1: 56.000000 | 2000000 2: 54.500000 | 1000000 ASKS:

But correct version is: BIDS: 1: 54.500000 | 1000000 ASKS: 1: 56.000000 | 2000000

korst1k commented 7 years ago

Test:

  TEST(FastPublisherTest, FastMsgPrinterTest)
  {
    namespace gen = FIX50SP2_2017_Mar;

    gen::W_OLS_CURR ols_msg;
    auto ols = ols_msg.mref();

    ols.set_MsgSeqNum().as(1);
    ols.set_SendingTime().as(20170308);
    ols.set_LastMsgSeqNumProcessed().as(5);
    ols.set_RptSeq().as(3);
    ols.set_LastFragment().as(0);
    ols.set_RouteFirst().as(1);
    ols.set_Symbol().as("USD000000TOD");

    auto entries = ols.set_GroupMDEntries();
    entries.resize(4);

    entries[0].set_MDEntryType().as("0");
    entries[0].set_MDEntryID().as("1001");
    entries[0].set_MDEntryDate().as(20170309);
    entries[0].set_MDEntryTime().as(145001);
    entries[0].set_MDEntryPx().as(5000025, -3);
    entries[0].set_MDEntrySize().as(10, 0);

    entries[1].set_MDEntryType().as("1");
    entries[1].set_MDEntryID().as("1002");
    entries[1].set_MDEntryDate().as(20170309);
    entries[1].set_MDEntryTime().as(155005);
    entries[1].set_MDEntryPx().as(6000025, -3);
    entries[1].set_MDEntrySize().as(15, 0);

    entries[2].set_MDEntryType().as("0");
    entries[2].set_MDEntryID().as("1003");
    entries[2].set_MDEntryDate().as(20170309);
    entries[2].set_MDEntryTime().as(155055);
    entries[2].set_MDEntryPx().as(5500025, -3);
    entries[2].set_MDEntrySize().as(20, 0);

    entries[3].set_MDEntryType().as("1");
    entries[3].set_MDEntryID().as("1004");
    entries[3].set_MDEntryDate().as(20170309);
    entries[3].set_MDEntryTime().as(165055);
    entries[3].set_MDEntryPx().as(6500025, -3);
    entries[3].set_MDEntrySize().as(25, 0);

    std::vector<char> buf;
    mfast::fast_encoder encoder;
    encoder.include({ gen::description() });
    encoder.encode(ols, buf, true);

    FastMsgPrinter printer;
    printer.visit(ols, 0);
    std::cout << "Encoded: \n" << printer.str() << std::endl;

    mfast::fast_decoder decoder;
    decoder.include({ gen::description() });
    const char* data = buf.data();
    auto msg = decoder.decode(data, &buf[buf.size()]);

    std::cout << "OLS: " << FastEncoder::DumpByMsgFields(buf.data(), buf.size(), false) << std::endl;

    const auto& olsDecoded = static_cast<gen::W_OLS_CURR_cref>(msg);
    printer.clear();
    printer.visit(olsDecoded, 0);
    std::cout << "Decoded: \n" << printer.str() << std::endl;
  }

Output:

Note: Google Test filter = FastPublisherTest.FastMsgPrinterTest
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from FastPublisherTest
[ RUN      ] FastPublisherTest.FastMsgPrinterTest
Encoded: 
{ MessageType: 'W', BeginString: 'FIXT.1.1', ApplVerID: '9', SenderCompID: 'MOEX', MsgSeqNum: '1', SendingTime: '20170308', LastMsgSeqNumProcessed: '5', RptSeq: '3', LastFragment: '0', RouteFirst: '1', Symbol: 'USD000000TOD', GroupMDEntries: '
[0]: { MDEntryType: '0', MDEntryID: '1001', MDEntryDate: '20170309', MDEntryTime: '145001', MDEntryPx: '5000025*10^-3', MDEntrySize: '10*10^0',  }
[1]: { MDEntryType: '1', MDEntryID: '1002', MDEntryDate: '20170309', MDEntryTime: '155005', MDEntryPx: '6000025*10^-3', MDEntrySize: '15*10^0',  }
[2]: { MDEntryType: '0', MDEntryID: '1003', MDEntryDate: '20170309', MDEntryTime: '155055', MDEntryPx: '5500025*10^-3', MDEntrySize: '20*10^0',  }
[3]: { MDEntryType: '1', MDEntryID: '1004', MDEntryDate: '20170309', MDEntryTime: '165055', MDEntryPx: '6500025*10^-3', MDEntrySize: '25*10^0',  }',  }
OLS:  length = 97
  1   +0     : C0 
  2   +1     : 1C 90 
  3   +3     : 81 
  4   +4     : 09 4F 0C C4 
  5   +8     : 86 
  6   +9     : 83 
  7   +10    : 81 
  8   +11    : 82 
  9   +12    : 80 
 10   +13    : 80 
 11   +14    : 55 53 44 30 30 30 30 30 30 54 4F C4 
 12   +26    : 80 
 13   +27    : 84 
 14   +28    : 76 80 
 15   +30    : B0 
 16   +31    : 31 30 30 B1 
 17   +35    : 09 4F 0C C6 
 18   +39    : 08 6C EA 
 19   +42    : FD 
 20   +43    : 02 31 16 D9 
 21   +47    : 81 
 22   +48    : 8A 
 23   +49    : 16 80 
 24   +51    : 31 30 30 B2 
 25   +55    : 09 3A FE 
 26   +58    : FD 
 27   +59    : 02 6E 1B 99 
 28   +63    : 81 
 29   +64    : 8F 
 30   +65    : 16 80 
 31   +67    : 31 30 30 B3 
 32   +71    : 09 3B B0 
 33   +74    : FD 
 34   +75    : 02 4F 58 F9 
 35   +79    : 81 
 36   +80    : 94 
 37   +81    : 16 80 
 38   +83    : 31 30 30 B4 
 39   +87    : 0A 09 C0 
 40   +90    : FD 
 41   +91    : 03 0C 5D B9 
 42   +95    : 81 
 43   +96    : 99 

------------------------------------------------

Decoded: 
{ MessageType: 'W', BeginString: 'FIXT.1.1', ApplVerID: '9', SenderCompID: 'MOEX', MsgSeqNum: '1', SendingTime: '20170308', LastMsgSeqNumProcessed: '5', RptSeq: '3', LastFragment: '0', RouteFirst: '1', Symbol: 'USD000000TOD', GroupMDEntries: '
[0]: { MDEntryType: '0', MDEntryID: '1001', MDEntryDate: '20170309', MDEntryTime: '145001', MDEntryPx: '5000025*10^-3', MDEntrySize: '10*10^0',  }
[1]: { MDEntryType: '0', MDEntryID: '1002', MDEntryDate: '20170309', MDEntryTime: '155005', MDEntryPx: '6000025*10^-3', MDEntrySize: '15*10^0',  }
[2]: { MDEntryType: '0', MDEntryID: '1003', MDEntryDate: '20170309', MDEntryTime: '155055', MDEntryPx: '5500025*10^-3', MDEntrySize: '20*10^0',  }
[3]: { MDEntryType: '0', MDEntryID: '1004', MDEntryDate: '20170309', MDEntryTime: '165055', MDEntryPx: '6500025*10^-3', MDEntrySize: '25*10^0',  }',  }
kirsan31 commented 5 years ago

@korst1k hi, do you have any updates on this?