CBOE Emulator  1.0
noise.hpp
1 // A complex noise trading strategy.
2 // Copyright 2020 Christian Kauten
3 //
4 // Author: Christian Kauten (kautenja@auburn.edu)
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 //
17 // Reference: High frequency trading strategies, market fragility and price
18 // spikes: an agent based model perspective
19 // Reference: An Agent-Based Model for Market Impact
20 //
21 
22 #ifndef STRATEGIES_NOISE_HPP
23 #define STRATEGIES_NOISE_HPP
24 
25 #include "data_feed/receiver.hpp"
26 #include "order_entry/client.hpp"
27 #include "maths/probability.hpp"
28 #include <nlohmann/json.hpp>
29 #include <atomic>
30 #include <iostream>
31 
33 namespace Strategies {
34 
40 class Noise {
41  private:
45  DataFeedReceiver receiver;
47  OrderEntry::Client client;
49  asio::steady_timer timer;
51  uint32_t sleep_time;
53  float P_act;
55  std::atomic<bool> is_running;
56 
58  OrderEntry::Side side;
60  enum class OrderType : int {Market, Limit, Cancel};
62  std::discrete_distribution<int> order_distribution;
64  enum LimitType : int {Cross, Inside, AtBest, Outside};
66  std::discrete_distribution<int> limit_distribution;
68  double size_market_mean;
70  double size_market_std;
72  double size_limit_mean;
74  double size_limit_std;
77  double x_min_outside;
80  double beta_exp;
81 
83  inline OrderEntry::Quantity size(double mean, double std) {
84  return static_cast<int>(Maths::Probability::lognormal(mean, std));
85  }
86 
88  inline OrderEntry::Price outside() {
89  return static_cast<int>(Maths::Probability::power_law(x_min_outside, beta_exp));
90  }
91 
93  void limit_order() {
94  switch (static_cast<LimitType>(limit_distribution(Maths::Probability::generator))) {
95  case LimitType::Cross: { // cross the spread
96  switch (side) {
97  case OrderEntry::Side::Sell: {
99  receiver.get_book().last_best_buy(),
100  size(size_limit_mean, size_limit_std),
101  side
102  );
103  break;
104  }
105  case OrderEntry::Side::Buy: {
107  receiver.get_book().last_best_sell(),
108  size(size_limit_mean, size_limit_std),
109  side
110  );
111  break;
112  }
113  }
114  break;
115  }
116  case LimitType::Inside: { // inside the spread
117  // get the best buy price
118  auto bid = receiver.get_book().last_best_buy();
119  // check for overflow
120  if (bid >= std::numeric_limits<OrderEntry::Price>::max() - 1)
121  bid = std::numeric_limits<OrderEntry::Price>::max();
122  else // safe to increase the bid
123  bid += 1;
124 
125  // get the best sell price
126  auto ask = receiver.get_book().last_best_sell();
127  // check for underflow
128  if (ask <= std::numeric_limits<OrderEntry::Price>::min() + 1)
129  ask = std::numeric_limits<OrderEntry::Price>::min();
130  else // safe to decrease the bid
131  ask -= 1;
132 
133  // return if there is no spread (the "inside" doesn't exist)
134  if (bid >= ask) return;
135  // place the order with a random size and price inside the spread
138  size(size_limit_mean, size_limit_std),
139  side
140  );
141  break;
142  }
143  case LimitType::AtBest: { // at the best price
144  switch (side) {
145  case OrderEntry::Side::Sell: {
147  receiver.get_book().last_best_sell(),
148  size(size_limit_mean, size_limit_std),
149  side
150  );
151  break;
152 
153  }
154  case OrderEntry::Side::Buy: {
156  receiver.get_book().last_best_buy(),
157  size(size_limit_mean, size_limit_std),
158  side
159  );
160  break;
161  }
162  }
163  break;
164  }
165  case LimitType::Outside: { // outside the spread (deep in the book)
166  // create some noise for the price
167  auto noise = outside();
168  switch (side) {
169  case OrderEntry::Side::Sell: {
170  // get the best sell
171  auto price = receiver.get_book().last_best_sell();
172  // check if the noise causes overflow
173  if (price >= std::numeric_limits<OrderEntry::Price>::max() - noise)
174  price = std::numeric_limits<OrderEntry::Price>::max();
175  else // add the noise
176  price += noise;
178  price,
179  size(size_limit_mean, size_limit_std),
180  side
181  );
182  break;
183  }
184  case OrderEntry::Side::Buy: {
185  // get the best buy
186  auto price = receiver.get_book().last_best_buy();
187  // check if the noise causes underflow
188  if (price <= std::numeric_limits<OrderEntry::Price>::min() + noise)
189  price = std::numeric_limits<OrderEntry::Price>::min();
190  else // reduce by the noise
191  price -= noise;
193  price,
194  size(size_limit_mean, size_limit_std),
195  side
196  );
197  break;
198  }
199  }
200  break;
201  }
202  }
203  }
204 
206  inline void start_strategy() {
207  timer.expires_after(std::chrono::milliseconds(sleep_time));
208  timer.async_wait([this](const std::error_code& error) {
209  if (error) { // TODO: handle error code
210  std::cout << "Noise::start_strategy - " << error << std::endl;
211  return;
212  }
213  if (Maths::Probability::boolean(P_act)) do_strategy();
214  start_strategy();
215  });
216  }
217 
219  void do_strategy() {
220  side = Maths::Probability::boolean() ? OrderEntry::Side::Buy : OrderEntry::Side::Sell;
221  // select an action to perform
222  switch (static_cast<OrderType>(order_distribution(Maths::Probability::generator))) {
223  case OrderType::Market: { // make a market order
225  OrderEntry::Messages::ORDER_PRICE_MARKET,
226  size(size_market_mean, size_market_std),
227  side
228  );
229  break;
230  }
231  case OrderType::Limit: { // make a limit order
232  limit_order();
233  break;
234  }
235  case OrderType::Cancel: { // cancel the oldest order
236  if (client.has_active_order()) // an order exists
238  break;
239  }
240  }
241  }
242 
243  public:
251  asio::io_context& feed_context,
252  asio::io_context& context,
253  nlohmann::json options
254  ) :
255  receiver(
256  feed_context,
257  asio::ip::make_address(options["data_feed"]["listen"].get<std::string>()),
258  asio::ip::make_address(options["data_feed"]["group"].get<std::string>()),
259  options["data_feed"]["port"].get<uint16_t>(),
260  *this
261  ),
262  client(
263  context,
264  options["order_entry"]["host"].get<std::string>(),
265  std::to_string(options["order_entry"]["port"].get<uint16_t>())
266  ),
267  timer(context),
268  sleep_time(options["strategy"]["sleep_time"].get<uint32_t>()),
269  P_act(options["strategy"]["P_act"].get<double>()),
270  is_running(false),
271  order_distribution{
272  options["strategy"]["order_distribution"]["P_market"].get<double>(),
273  options["strategy"]["order_distribution"]["P_limit"].get<double>(),
274  options["strategy"]["order_distribution"]["P_cancel"].get<double>()
275  },
276  limit_distribution{
277  options["strategy"]["limit_distribution"]["P_cross"].get<double>(),
278  options["strategy"]["limit_distribution"]["P_inside"].get<double>(),
279  options["strategy"]["limit_distribution"]["P_best"].get<double>(),
280  options["strategy"]["limit_distribution"]["P_outside"].get<double>()
281  },
282  size_market_mean(options["strategy"]["size_market_mean"].get<double>() + 2),
283  size_market_std(options["strategy"]["size_market_std"].get<double>()),
284  size_limit_mean(options["strategy"]["size_limit_mean"].get<double>() + 2),
285  size_limit_std(options["strategy"]["size_limit_std"].get<double>()),
286  x_min_outside(options["strategy"]["x_min_outside"].get<double>()),
287  beta_exp(options["strategy"]["beta_exp"].get<double>()) {
288  // login to the order entry client
289  auto username = OrderEntry::make_username(options["order_entry"]["username"].get<std::string>());
290  auto password = OrderEntry::make_password(options["order_entry"]["password"].get<std::string>());
291  client.send<OrderEntry::Messages::LoginRequest>(username, password);
292  }
293 
299  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::StartOfSession& message) {
300  if (is_running) {
301  std::cerr << "received start of session when already running" << std::endl;
302  return;
303  }
304  // start the strategy
305  start_strategy();
306  // set the running flag to true
307  is_running = true;
308  }
309 
315  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::EndOfSession& message) {
316  if (not is_running) {
317  std::cerr << "received end of session when not running" << std::endl;
318  return;
319  }
320  // stop the strategy
321  timer.cancel();
322  is_running = false;
323  }
324 
330  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::Clear& message) { }
331 
337  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::AddOrder& message) { }
338 
344  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::DeleteOrder& message) { }
345 
351  inline void did_receive(DataFeedReceiver* receiver, const DataFeed::Messages::Trade& message) { }
352 };
353 
354 } // namespace Strategies
355 
356 #endif // STRATEGIES_NOISE_HPP
Maths::Probability::lognormal
T lognormal(T mean, T stddev)
Sample a number from a log-normal distribution.
Definition: probability.hpp:85
DataFeed::Receiver< Noise >
OrderEntry::Messages::LoginRequest
A request that indicates a client is attempting to create a new session.
Definition: messages.hpp:248
OrderEntry::make_password
Password make_password(std::string password)
Make a password from the given input string.
Definition: messages.hpp:62
DataFeed::Messages::Trade
A message that indicates a market order matches with a limit order.
Definition: messages.hpp:387
OrderEntry::Client::has_active_order
bool has_active_order() const
Return true if the client has an active order.
Definition: client.hpp:267
OrderEntry::Client::send
void send(Args &&...args)
Write a message asynchronously (non-blocking).
Definition: client.hpp:299
DataFeed::LOB::LimitOrderBook::last_best_sell
Price last_best_sell() const
Return the last best sell price.
Definition: limit_order_book.hpp:262
Strategies::Noise::Noise
Noise(asio::io_context &feed_context, asio::io_context &context, nlohmann::json options)
Initialize the strategy.
Definition: noise.hpp:250
DataFeed::Messages::DeleteOrder
A message that indicates a limit order was added to the book.
Definition: messages.hpp:332
OrderEntry::Messages::OrderRequest
A request to place a new limit / market order in the book.
Definition: messages.hpp:502
DataFeed::Receiver::get_book
const LOB::LimitOrderBook & get_book()
Return the limit order book for this receiver.
Definition: receiver.hpp:214
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::AddOrder &message)
Handle an add order message.
Definition: noise.hpp:337
DataFeed::Messages::EndOfSession
A message that indicates the end of a trading session.
Definition: messages.hpp:506
DataFeed::Messages::Clear
A message that indicates to clear all orders in the order book.
Definition: messages.hpp:213
Maths::Probability::boolean
bool boolean()
Return the result of a coin toss.
Definition: probability.hpp:105
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::StartOfSession &message)
Handle a start of session message.
Definition: noise.hpp:299
DataFeed::Messages::StartOfSession
A message that indicates the start of a trading session.
Definition: messages.hpp:460
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::Clear &message)
Handle a clear book message.
Definition: noise.hpp:330
Maths::Probability::uniform_int
T uniform_int(T min, T max)
Sample a number from an integer-valued uniform distribution.
Definition: probability.hpp:49
Maths::Probability::generator
std::mt19937 generator(getpid())
the global generator for the probability module.
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::Trade &message)
Handle a trade message.
Definition: noise.hpp:351
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::DeleteOrder &message)
Handle a delete order message.
Definition: noise.hpp:344
Strategies::Noise::did_receive
void did_receive(DataFeedReceiver *receiver, const DataFeed::Messages::EndOfSession &message)
Handle an end of session message.
Definition: noise.hpp:315
DataFeed::LOB::LimitOrderBook::last_best_buy
Price last_best_buy() const
Return the last best buy price.
Definition: limit_order_book.hpp:268
OrderEntry::Client
A client for interacting with the direct market access server.
Definition: client.hpp:42
OrderEntry::Messages::PurgeRequest
A request to cancel all active orders in the book.
Definition: messages.hpp:917
Maths::Probability::power_law
T power_law(T constant, T exponent)
Sample a number sample from a power law distribution.
Definition: probability.hpp:97
OrderEntry::make_username
Username make_username(std::string username)
Make a username from the given input string.
Definition: messages.hpp:46
OrderEntry::Quantity
uint32_t Quantity
A type for order quantities.
Definition: messages.hpp:133
Strategies::Noise
The noise trader strategy logic.
Definition: noise.hpp:40
OrderEntry::Price
uint64_t Price
A type for order prices.
Definition: messages.hpp:136
OrderEntry::Side
Side
The side of an order.
Definition: messages.hpp:71
Strategies
Direct market access trading strategies.
Definition: iceberg_liquidity_consumer.hpp:33
DataFeed::Messages::AddOrder
A message that indicates a limit order was added to the book.
Definition: messages.hpp:259