libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
batsboe_exchanges_performance.cpp
Go to the documentation of this file.
1 /******************************************************************************
2 ** Copyright © 2016 by J.M.McGuiness, isimud@hussar.me.uk
3 **
4 ** This library is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU Lesser General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2.1 of the License, or (at your option) any later version.
8 **
9 ** This library is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** Lesser General Public License for more details.
13 **
14 ** You should have received a copy of the GNU Lesser General Public
15 ** License along with this library; if not, write to the Free Software
16 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #include "stdafx.h"
20 
21 #define BOOST_TEST_MODULE isimud_tests
22 #include <boost/test/included/unit_test.hpp>
23 
24 #include <boost/mpl/list.hpp>
25 
26 #include "../exchanges/FIX/v5.0sp2/fix_client.hpp"
27 #include "../exchanges/BATSBOE/EU/v1/batsboe.hpp"
28 #include "../exchanges/BATSBOE/EU/v1/batsboe_sim.hpp"
29 #include "../exchanges/BATSBOE/EU/v2/batsboe.hpp"
30 #include "../exchanges/BATSBOE/EU/v2/batsboe_sim.hpp"
31 #ifndef JMMCG_PERFORMANCE_TESTS
32 # include "../exchanges/BATSBOE/US/v1/batsboe.hpp"
33 # include "../exchanges/BATSBOE/US/v1/batsboe_sim.hpp"
34 #endif
35 #include "../exchanges/BATSBOE/US/v2/batsboe.hpp"
36 #include "../exchanges/BATSBOE/US/v2/batsboe_sim.hpp"
37 #include "../exchanges/conversions/fix_to_batsboe_eu_conversions.hpp"
38 #include "../exchanges/conversions/fix_to_batsboe_us_conversions.hpp"
39 #include "../exchanges/conversions/batsboe_eu_to_fix_conversions.hpp"
40 #include "../exchanges/conversions/batsboe_us_to_fix_conversions.hpp"
41 
42 #include "core/ave_deviation_meter.hpp"
43 #include "core/jthread.hpp"
44 #include "core/latency_timestamps.hpp"
45 #include "core/stats_output.hpp"
46 
47 using namespace libjmmcg;
48 using namespace libisimud;
49 
50 using api_thread_traits=ppd::thread_params<ppd::platform_api>;
51 using timed_results_t=ave_deviation_meter<double>;
52 
53 const boost::asio::ip::address localhost(boost::asio::ip::address_v4::loopback());
54 const unsigned short client_port=12367u;
55 const unsigned short unused_primary_port=client_port+1;
57 const exchanges::BATSBOE::common::ClientOrderID_t clientOrderId1{"12345678901234test1"};
58 const exchanges::BATSBOE::common::SeqNum_t sequenceNumber(1);
59 
60 using exchg_t_types=boost::mpl::list<
61  std::pair<exchanges::BATSBOE::EU::v1::link_t<exchanges::FIX::v5_0sp2::MsgTypes>, exchanges::BATSBOE::EU::v1::simulator_t>
62 #ifndef JMMCG_PERFORMANCE_TESTS
63  ,
64 // TODO - not yet implemented std::pair<exchanges::BATSBOE::EU::v2::link_t<exchanges::FIX::v5_0sp2::MsgTypes>, exchanges::BATSBOE::EU::v2::simulator_t>,
65  std::pair<exchanges::BATSBOE::US::v1::link_t<exchanges::FIX::v5_0sp2::MsgTypes>, exchanges::BATSBOE::US::v1::simulator_t>
66 #endif
67 // TODO - not yet implemented std::pair<exchanges::BATSBOE::US::v2::link_t<exchanges::FIX::v5_0sp2::MsgTypes>, exchanges::BATSBOE::US::v2::simulator_t>
68 >;
69 
70 template<class exchg_t>
71 struct only_sim {
72  using link_t=typename exchg_t::first_type;
73  using simulator_t=typename exchg_t::second_type;
74  using conn_pol_t=typename link_t::exchg_link_t::conn_pol_t;
75  using connection_t=exchanges::common::connection<
76  typename simulator_t::msg_processor_t::msg_details_t,
77  conn_pol_t
78  >;
79 
80  const typename conn_pol_t::gateways_t gateways{
82  };
83  const conn_pol_t conn_pol{
84  gateways,
86  0,
87  {"000"},
90  false
91  },
93  0
94  }
95  };
96  no_latency_timestamps ts{0};
97  simulator_t svr{
98  boost::asio::ip::address(),
100  simulator_t::socket_t::socket_priority::low,
101  api_thread_traits::processor_mask_type(exchanges::common::thread_traits::exchange_simulator_thread.core),
102  exchanges::common::thread_traits::exchange_simulator_thread.priority,
103  typename simulator_t::proc_rules_t(),
104  ts,
105  "sim" LIBJMMCG_ENQUOTE(__LINE__)
106  };
107 };
108 
109 template<class exchg_t>
110 struct simulator_and_link : public only_sim<exchg_t> {
111  using base_t=only_sim<exchg_t>;
112  using link_t=typename base_t::link_t;
113 
114 #ifdef JMMCG_PERFORMANCE_TESTS
116 #else
117  no_latency_timestamps ts;
118 #endif
119  link_t link{
120  typename link_t::client_link_t::ctor_args{
121  boost::asio::ip::address(),
122  client_port,
123  typename link_t::client_link_t::proc_rules_t(sequenceNumber)
124  },
125  this->conn_pol,
126  link_t::socket_t::socket_priority::high,
127  link_t::socket_t::socket_priority::low,
128  typename link_t::exchg_to_client_proc_rules_t(sequenceNumber),
129  ts,
130  "link" LIBJMMCG_ENQUOTE(__LINE__)
131  };
132 
133  explicit simulator_and_link(std::size_t num_ts)
134  : ts(num_ts) {}
135 };
136 
137 template<class exchg_t>
139  using base_t=simulator_and_link<exchg_t>;
140  using base_t::base_t;
141 
142 #ifdef JMMCG_PERFORMANCE_TESTS
144 #else
145  no_latency_timestamps client_ts;
146 #endif
147  exchanges::FIX::v5_0sp2::fix_client client{
148  localhost,
149  client_port,
152  client_ts
153  };
154 
155  explicit simulator_and_link_client_too(std::size_t num_ts)
156  : base_t(num_ts), client_ts(num_ts) {}
157 };
158 
159 BOOST_AUTO_TEST_SUITE(performance, *stats_to_csv::make_fixture("batsboe_exchanges_performance.csv"))
160 
161 BOOST_AUTO_TEST_SUITE(simulator)
162 
163 /**
164  \test <a href="./examples/batsboe_exchanges_performance.svg">Graph</a> of performance results for the BATSBOE-based simulator-
165  ================================
166  Results for 5000 repetitions.
167  The timings include the full-round-trip: including time to create the BATSBOE order, send it, time in the simulator, time to receive, all of which includes the "wire-time".
168 */
169 BOOST_AUTO_TEST_CASE_TEMPLATE(reject, exchg_t, exchg_t_types) {
170 #ifdef JMMCG_PERFORMANCE_TESTS
171  const unsigned long num_loops=5000;
172  const unsigned short loops_for_conv=500;
173 #else
174  const unsigned long num_loops=1;
175  const unsigned short loops_for_conv=1;
176 #endif
177  const double perc_conv_estimate=5.0;
178 
179  using fixture_t=only_sim<exchg_t>;
180 
181  const fixture_t f{};
182 
183  typename fixture_t::connection_t link(
184  f.conn_pol,
186  exchanges::common::thread_traits::client_to_exchange_thread.core
187  );
188 
189  auto send_and_receive=[&link]() {
190  using msg_details_t=typename fixture_t::simulator_t::msg_processor_t::msg_details_t;
191  const typename msg_details_t::NewOrder_t msg(
192  sequenceNumber,
193  clientOrderId1,
194  msg_details_t::OrderType::Market,
195  exchanges::BATSBOE::common::TIF::Day,
196  msg_details_t::Side::Buy,
197  exchg_t::second_type::proc_rules_t::invalidInstrumentID,
198  exchg_t::second_type::proc_rules_t::quantity_limit-1,
199  exchg_t::second_type::proc_rules_t::price
200  );
201  BOOST_CHECK_NO_THROW(link.send(msg));
202  typename msg_details_t::OrderRejected reply;
203  BOOST_CHECK_NO_THROW(link.receive(reply));
204  };
205 
206  const std::pair<timed_results_t, bool> timed_results(compute_average_deviation<timed_results_t::value_type>(
207  perc_conv_estimate,
208  loops_for_conv,
209  [&send_and_receive]() {
210  const auto t1=std::chrono::high_resolution_clock::now();
211  for (unsigned long i=0; i<num_loops; ++i) {
212  send_and_receive();
213  }
214  const auto t2=std::chrono::high_resolution_clock::now();
215  return timed_results_t::value_type(static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count())/num_loops);
216  }
217  ));
218  std::cout<<fixture_t::link_t::thread_traits::demangle_name(typeid(typename exchg_t::first_type))<<"\n\tSimulator round-trip time (microseconds)="<<timed_results.first<<std::endl;
219 #ifdef JMMCG_PERFORMANCE_TESTS
220  stats_to_csv::handle->stats<<timed_results.first.to_csv()<<std::flush;
221  BOOST_CHECK(!timed_results.second);
222 #endif
223 }
224 
225 BOOST_AUTO_TEST_SUITE_END()
226 
227 BOOST_AUTO_TEST_SUITE(exchange_gateways)
228 
229 /**
230  \test <a href="./examples/batsboe_exchanges_performance.svg">Graph</a> of performance results for the BATSBOE-based exchange-links, sending an order and receiving the reject one-at-a-time. The <a href="./examples/batsboe_exchanges_in_order_performance_latencies.svg">histogram</a> of the results, pinned but no numactl.
231  ================================
232  Results for 5000 repetitions.
233  The timings include the full-round-trip: including time to create the FIX order, convert it, send it, time in the simulator, time to receive, time to convert back to FIX, all of which includes the "wire-time".
234  If numactl is to be used, the command "numactl --cpunodebind=1 --membind=1 ../../build/release/isimud/tests/test_batsboe_exchanges_performance" should be executed, Where NODE_INDEX should be the same value as that in thread_traits::numa_index.
235 
236  \see thread_traits::numa_index
237 */
238 BOOST_AUTO_TEST_CASE_TEMPLATE(order_rejected_at_a_time, exchg_t, exchg_t_types) {
239 #ifdef JMMCG_PERFORMANCE_TESTS
240  const unsigned long num_loops=5000;
241  const unsigned short loops_for_conv=500;
242 #else
243  const unsigned long num_loops=1;
244  const unsigned short loops_for_conv=1;
245 #endif
246  const double perc_conv_estimate=5.0;
247 
248  using fixture_t=simulator_and_link_client_too<exchg_t>;
249 
250  fixture_t f(2*3*num_loops*loops_for_conv);
251 
252  auto const &timed_results=f.client.in_order_tx_rx_stats(
253  num_loops,
254  loops_for_conv,
255  perc_conv_estimate
256  );
257  std::cout<<fixture_t::link_t::thread_traits::demangle_name(typeid(typename exchg_t::first_type))<<"\n\tExchange round-trip in-order time (microseconds)="<<timed_results.first<<std::endl;
258 #ifdef JMMCG_PERFORMANCE_TESTS
259  stats_to_csv::handle->stats<<timed_results.first.to_csv()<<std::flush;
260  BOOST_CHECK(!timed_results.second);
261  f.ts.write_to_named_csv_file(std::cout, "batsboe_exchanges_in_order_performance_latencies");
262 #endif
263 }
264 
265 /**
266  \test <a href="./examples/batsboe_exchanges_out_of_order_performance.svg">Graph</a> of performance results for the MIT-based exchange-links, sending the orders and receiving the rejects in parallel. The <a href="./examples/batsboe_exchanges_out_of_order_performance_latencies.svg">histogram</a> of the results, pinned but no numactl.
267  ================================
268  Results for 20000 orders sent & rejects received.
269  The timings include the full-round-trip: including time to create the FIX order, convert it, send it, time in the simulator, time to receive, time to convert back to FIX, all of which includes the "wire-time".
270  If numactl is to be used, the command "numactl --cpunodebind=NODE_INDEX --membind=NODE_INDEX ../../build/release/isimud/tests/test_batsboe_exchanges_performance" should be executed, Where NODE_INDEX should be the same value as that in thread_traits::numa_index.
271 
272  \see thread_traits::numa_index
273 */
274 BOOST_AUTO_TEST_CASE_TEMPLATE(order_rejects_parallel, exchg_t, exchg_t_types) {
275 #ifdef JMMCG_PERFORMANCE_TESTS
276  const unsigned long num_loops=20000;
277  const unsigned short loops_for_conv=500;
278 #else
279  const unsigned long num_loops=1;
280  const unsigned short loops_for_conv=1;
281 #endif
282  const double perc_conv_estimate=0.1;
283 
284  using fixture_t=simulator_and_link_client_too<exchg_t>;
285 
286  fixture_t f(2*3*num_loops*loops_for_conv);
287 
288  auto send_and_receive=[&f]() {
289  std::chrono::high_resolution_clock::time_point send_time{};
290  std::chrono::high_resolution_clock::time_point receive_time{};
291 
292  auto send_batch_of_orders=[&f, &send_time]() {
293  BOOST_CHECK(f.client.send_fix_msg.is_valid());
294  // This farting around is just to throttle sending messages so that localhost is not flooded. (If it got flooded it would simply stop sending more messages and the test would break.)
295  double accrued_delay_in_msec{};
296  send_time=std::chrono::high_resolution_clock::now();
297  for (unsigned long i=0; i<num_loops; ++i) {
298  f.client.send();
299  accrued_delay_in_msec+=cpu_timer::pause_for_usec(400);
300  }
301  send_time=std::chrono::time_point_cast<std::chrono::high_resolution_clock::duration>(send_time+std::chrono::duration<double, std::micro>(accrued_delay_in_msec));
302  };
303 
304  auto receive_batch_of_rejects=[&f, &receive_time]() {
305  for (unsigned long i=0; i<num_loops; ++i) {
306  f.client.receive();
307  }
308  receive_time=std::chrono::high_resolution_clock::now();
309  BOOST_CHECK(f.client.receive_fix_msg.is_valid());
310  };
311 
312  {
313  ppd::jthread send(std::move(send_batch_of_orders));
314  ppd::jthread receive(std::move(receive_batch_of_rejects));
315  }
316  return timed_results_t::value_type(static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(receive_time - send_time).count())/num_loops);
317  };
318 
319  const std::pair<timed_results_t, bool> timed_results(compute_average_deviation<timed_results_t::value_type>(
320  perc_conv_estimate,
321  loops_for_conv,
322  std::move(send_and_receive)
323  ));
324  std::cout<<fixture_t::link_t::thread_traits::demangle_name(typeid(typename exchg_t::first_type))<<"\n\tExchange round-trip out-of-order time (microseconds)="<<timed_results.first<<std::endl;
325 #ifdef JMMCG_PERFORMANCE_TESTS
326  stats_to_csv::handle->stats<<timed_results.first.to_csv()<<std::flush;
327  BOOST_CHECK(!timed_results.second);
328  f.ts.write_to_named_csv_file(std::cout, "batsboe_exchanges_out_of_order_performance_latencies");
329 #endif
330 }
331 
332 BOOST_AUTO_TEST_SUITE_END()
333 
334 BOOST_AUTO_TEST_SUITE_END()