JayBeams  0.1
Another project to have fun coding.
itch5eventdepth.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  *
4  * This program tries to answer the question:
5  * - How many changes happen at the top of book?
6  * - How many changes happen 2 levels down the book?
7  * - What about 10 levels?
8  *
9  * It reports the percentiles of "for each event, record the depth of
10  * the change".
11  */
16 #include <jb/fileio.hpp>
17 #include <jb/log.hpp>
18 
19 #include <chrono>
20 #include <iostream>
21 #include <stdexcept>
22 #include <unordered_map>
23 
24 /**
25  * Define types and functions used in this program.
26  */
27 namespace {
28 
29 /// Configuration parameters for itch5eventdepth
30 class config : public jb::config_object {
31 public:
32  config();
34 
35  void validate() const override;
36 
42  jb::config_attribute<config, bool> enable_symbol_stats;
43 };
44 
45 /// Calculate the depth of an event, taking care with events that
46 /// moved the BBO.
47 template <typename book_type>
48 void record_event_depth(
51  jb::itch5::book_update const& update) {
52  // ... we need to treat each side differently ...
53  int depth = 0;
54  if (update.buy_sell_indicator == u'B') {
55  // ... if the update price is higher than the current best bid
56  // that means the update moved the best_bid down, we are going to
57  // treat that as a change at depth 0. Notice that this works even
58  // when there is no best bid, because the book returns "0" as a
59  // best bid in that case ...
60  if (update.px < book.best_bid().first) {
61  depth = jb::itch5::price_levels(update.px, book.best_bid().first);
62  }
63  } else {
64  // ... do the analogous thing for the sell side ...
65  if (update.px > book.best_offer().first) {
66  depth = jb::itch5::price_levels(book.best_offer().first, update.px);
67  }
68  }
69  stats.sample(depth);
70 }
71 
72 } // anonymous namespace
73 
74 int main(int argc, char* argv[]) try {
75  config cfg;
76  cfg.load_overrides(
77  argc, argv, std::string("itch5eventdepth.yaml"), "JB_ROOT");
78  jb::log::init(cfg.log());
79 
80  boost::iostreams::filtering_istream in;
81  jb::open_input_file(in, cfg.input_file());
82 
83  boost::iostreams::filtering_ostream out;
84  jb::open_output_file(out, cfg.output_file());
85 
86  std::map<jb::itch5::stock_t, jb::book_depth_statistics> per_symbol;
87  jb::book_depth_statistics aggregate_stats(cfg.stats());
88 
90  [&aggregate_stats](
91  jb::itch5::message_header const& header,
93  updated_book,
94  jb::itch5::book_update const& update) {
95  record_event_depth(aggregate_stats, header, updated_book, update);
96  };
97 
98  if (cfg.enable_symbol_stats()) {
99  jb::book_depth_statistics::config symcfg(cfg.symbol_stats());
101  chain = [&per_symbol, symcfg, cb](
102  jb::itch5::message_header const& header,
104  jb::itch5::book_update const& update) {
105  cb(header, book, update);
106  auto location = per_symbol.find(update.stock);
107  if (location == per_symbol.end()) {
108  auto p = per_symbol.emplace(
109  update.stock, jb::book_depth_statistics(symcfg));
110  location = p.first;
111  }
112  record_event_depth(location->second, header, book, update);
113  };
114  cb = std::move(chain);
115  }
116 
119  std::move(cb), cfg_bk);
120  jb::itch5::process_iostream(in, handler);
121 
123  for (auto const& i : per_symbol) {
124  i.second.print_csv(i.first.c_str(), out);
125  }
126  aggregate_stats.print_csv("__aggregate__", out);
127  return 0;
128 
129 } catch (jb::usage const& u) {
130  std::cerr << u.what() << std::endl;
131  return u.exit_status();
132 } catch (std::exception const& ex) {
133  std::cerr << "Standard exception raised: " << ex.what() << std::endl;
134  return 1;
135 } catch (...) {
136  std::cerr << "Unknown exception raised" << std::endl;
137  return 1;
138 }
139 
140 namespace {
141 
142 /// Limit the amount of memory used on each per-symbol statistics
143 #ifndef JB_ITCH5EVENTDEPTH_DEFAULT_per_symbol_max_book_depth
144 #define JB_ITCH5EVENTDEPTH_DEFAULT_per_symbol_max_book_depth 5000
145 #endif // JB_ITCH5EVENTDEPTH_DEFAULT_per_symbol_max_book_depth
146 
147 /// Create a different default configuration for the per-symbol stats
148 jb::book_depth_statistics::config default_per_symbol_stats() {
151 }
152 
153 config::config()
154  : input_file(
155  desc("input-file").help("An input file with ITCH-5.0 messages."),
156  this)
157  , output_file(
158  desc("output-file")
159  .help("The name of the file where to store the statistics."
160  " By default output to stdout."
161  " Files ending in .gz are automatically compressed."),
162  this, "stdout")
163  , log(desc("log", "logging"), this)
164  , stats(desc("stats", "event-depth-statistics"), this)
165  , symbol_stats(
166  desc("symbol-stats", "event-depth-statistics-per-symbol"), this,
167  default_per_symbol_stats())
168  , enable_symbol_stats(
169  desc("enable-symbol-stats")
170  .help("If set, enable per-symbol statistics."
171  " Collecting per-symbol statistics is expensive in both"
172  " memory and execution time, enable only if needed."),
173  this, true) {
174 }
175 
176 void config::validate() const {
177  if (input_file() == "") {
178  throw jb::usage(
179  "Missing input-file setting."
180  " The program needs an input file to read ITCH-5.0 data from.",
181  1);
182  }
183  if (output_file() == "") {
184  throw jb::usage(
185  "Missing output-file setting."
186  " Use 'stdout' if you want to print to the standard output.",
187  1);
188  }
189  log().validate();
190  stats().validate();
191  symbol_stats().validate();
192 }
193 
194 } // anonymous namespace
Compute the book and call a user-defined callback on each change.
Define the header common to all ITCH 5.0 messages.
Base class for all configuration objects.
void sample(book_depth_t book_depth)
Record a sample, that is book depth value after the event.
virtual void validate() const
Validate the settings.
static void print_csv_header(std::ostream &os)
Print a CSV header.
int exit_status() const
Definition: usage.hpp:21
void open_output_file(boost::iostreams::filtering_ostream &out, std::string const &filename)
Open a file for writing.
Definition: fileio.cpp:12
void process_iostream(std::istream &in, message_handler &handler)
Process an iostream of ITCH-5.0 messages.
Keep statistics about a feed and its book depth.
void init(config const &cfg)
Initialize the logging functions using the configuration provided.
Definition: log.cpp:190
#define config_object_constructors(NAME)
Helper class to easily define configuration attributes.
std::function< void(message_header const &header, order_book< book_type > const &updated_book, book_update const &update)> callback_type
Define the callback type.
jb::config_attribute< config, book_depth_t > max_book_depth
No more than this value is recorded.
Configure a book_depth_statistics object.
half_quote best_bid() const
Definition: order_book.hpp:73
buy_sell_indicator_t buy_sell_indicator
What side of the book is being updated.
stock_t stock
The security updated by this order.
A simple class to communicate the result of parsing the options.
Definition: usage.hpp:11
A flat struct to represent updates to an order book.
void open_input_file(boost::iostreams::filtering_istream &in, std::string const &filename)
Open a file for reading.
Definition: fileio.cpp:27
static attribute_descriptor desc(std::string const &name)
Convenience function to create attribute descriptors with less typing.
Configure an map_based_order_book config object.
std::size_t price_levels(price_field_t lo, price_field_t hi)
Compute the number of price levels between two prices.
Maintain the ITCH-5.0 order book for a single security.
Definition: order_book.hpp:57
#define JB_ITCH5EVENTDEPTH_DEFAULT_per_symbol_max_book_depth
Limit the amount of memory used on each per-symbol statistics.
int main(int argc, char *argv[])
half_quote best_offer() const
Definition: order_book.hpp:83
price4_t px
What price level is being updated.