CBOE Emulator  1.0
client.hpp
1 // A client for sending data to a financial instrument exchange micro-service.
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 
18 #ifndef ORDER_ENTRY_CLIENT_HPP
19 #define ORDER_ENTRY_CLIENT_HPP
20 
21 #include <deque>
22 #include <functional>
23 #include <iostream>
24 #include <string>
25 #include <set>
26 #include <asio.hpp>
27 #include "exceptions.hpp"
28 #include "messages.hpp"
29 #include "clock.hpp"
30 
32 namespace OrderEntry {
33 
35 typedef std::set<OrderID> OrderIDSet;
37 typedef int32_t ClientShares;
39 typedef int64_t ClientCapital;
40 
42 class Client {
43  private:
45  asio::ip::tcp::socket socket;
47  Messages::PacketQueue input_buffer;
49  Messages::PacketQueue output_buffer;
51  uint32_t output_buffer_size = 0;
53  SequenceNumber sequence = 0;
55  bool is_authorized = false;
57  OrderIDSet active_orders = {};
59  ClientShares shares = 0;
61  ClientCapital capital = 0;
62 
64  inline void write_buffer() {
65  asio::async_write(
66  socket,
67  asio::buffer(output_buffer.front(), sizeof(Messages::Packet)),
68  [this](const std::error_code& error, std::size_t) {
69  if (error) // an error occurred while sending a message
70  throw Exception("Client::write_buffer - " + error.message());
71  // remove the sent message from the queue
72  output_buffer.pop_front();
73  // send another message if there is more work
74  if (--output_buffer_size) write_buffer();
75  }
76  );
77  }
78 
80  inline void read_buffer() {
81  input_buffer.emplace_back();
82  asio::async_read(
83  socket,
84  asio::buffer(input_buffer.back(), sizeof(Messages::Packet)),
85  [this](const std::error_code& error, std::size_t) {
86  if (error) { // an error occurred during the read operation
87  if (error == asio::error::eof) { // connection closed
88  is_authorized = false;
89  throw Exception("Client::read_buffer - Connection to remote host closed!");
90  }
91  throw Exception("Client::read_buffer - " + error.message());
92  }
93  // copy the message from the front of the queue and pop it off
94  auto message = input_buffer.front();
95  input_buffer.pop_front();
96  // read another message from the server
97  read_buffer();
98  // unwrap and handle the data using the template ID
99  switch (reinterpret_cast<Messages::Header&>(message).uid) {
100  case Messages::MessageID::LoginResponse: { handle(reinterpret_cast<Messages::LoginResponse&>(message)); break; }
101  case Messages::MessageID::LogoutResponse: { handle(reinterpret_cast<Messages::LogoutResponse&>(message)); break; }
102  case Messages::MessageID::OrderResponse: { handle(reinterpret_cast<Messages::OrderResponse&>(message)); break; }
103  case Messages::MessageID::CancelResponse: { handle(reinterpret_cast<Messages::CancelResponse&>(message)); break; }
104  case Messages::MessageID::ReplaceResponse: { handle(reinterpret_cast<Messages::ReplaceResponse&>(message)); break; }
105  case Messages::MessageID::PurgeResponse: { handle(reinterpret_cast<Messages::PurgeResponse&>(message)); break; }
106  case Messages::MessageID::TradeResponse: { handle(reinterpret_cast<Messages::TradeResponse&>(message)); break; }
107  default: { handle(reinterpret_cast<Messages::Header&>(message)); break; }
108  }
109  }
110  );
111  }
112 
117  void handle(const Messages::LoginResponse& response) {
118  switch (response.status) { // handle the message based on its status
119  case Messages::LoginResponseStatus::Accepted: { is_authorized = true; break; }
120  case Messages::LoginResponseStatus::NotAuthorized: { is_authorized = false; break; }
121  case Messages::LoginResponseStatus::AlreadyAuthorized: { is_authorized = true; break; }
122  case Messages::LoginResponseStatus::SessionInUse: { is_authorized = false; break; }
123  }
124  }
125 
130  void handle(const Messages::LogoutResponse& response) {
131  is_authorized = false;
132  // clear the set of active orders
133  active_orders = {};
134  switch (response.reason) { // handle the message based on its status
135  case Messages::LogoutReason::UserRequested: { break; }
136  case Messages::LogoutReason::EndOfDay: { break; }
137  case Messages::LogoutReason::Administrative: { break; }
138  case Messages::LogoutReason::ProtocolViolation: { break; }
139  }
140  }
141 
146  void handle(const Messages::OrderResponse& response) {
147  if (response.status == Messages::OrderStatus::Rejected) { // order rejected
148  std::cerr << "Client::handle(OrderResponse) - order rejected" << std::endl;
149  // TODO: handle order rejected
150  return;
151  }
152  if (response.order_id == 0) // market order
153  return;
154  // add the limit order ID to the active orders set
155  active_orders.insert(response.order_id);
156  }
157 
162  void handle(const Messages::CancelResponse& response) {
163  if (response.status == Messages::CancelStatus::Rejected) { // cancel rejected
164  std::cerr << "Client::handle(CancelResponse) - cancel rejected" << std::endl;
165  // TODO: handle cancel rejected
166  return;
167  }
168  // remove the limit order ID from the active orders set
169  active_orders.erase(response.order_id);
170  }
171 
176  void handle(const Messages::ReplaceResponse& response) {
177  if (response.status == Messages::ReplaceStatus::Rejected) { // replace rejected
178  std::cerr << "Client::handle(ReplaceResponse) - replace rejected" << std::endl;
179  // TODO: handle replace rejected
180  return;
181  }
182  // remove the canceled order from the set of active orders
183  if (response.canceled != 0)
184  active_orders.erase(response.canceled);
185  // add the limit order ID to the active orders set
186  if (response.new_order_id != 0)
187  active_orders.insert(response.new_order_id);
188  }
189 
194  void handle(const Messages::PurgeResponse& response) {
195  if (response.status == Messages::PurgeStatus::Rejected) { // purge rejected
196  std::cerr << "Client::handle(PurgeResponse) - purge rejected" << std::endl;
197  // TODO: handle purge rejected
198  return;
199  }
200  active_orders.clear();
201  }
202 
207  void handle(const Messages::TradeResponse& response) {
208  switch (response.side) { // handle the trade of shares and capital
209  case Side::Sell: {
210  shares -= response.quantity;
211  capital += response.quantity * response.price;
212  break;
213  }
214  case Side::Buy: {
215  shares += response.quantity;
216  capital -= response.quantity * response.price;
217  break;
218  }
219  }
220  // update the set of active orders
221  if (response.order_id != 0) { // limit order
222  if (response.leaves_quantity == 0) // completely filled
223  active_orders.erase(response.order_id);
224  }
225  }
226 
231  void handle(const Messages::Header& header) {
232  is_authorized = false;
233  throw Exception("Client::handle(Header) - received message with invalid header " + header.to_string());
234  }
235 
236  public:
243  Client(asio::io_context& context, std::string host, std::string port) :
244  socket(context) {
245  // connect to the server
246  asio::connect(socket, asio::ip::tcp::resolver(context).resolve(host, port));
247  // listen for messages
248  read_buffer();
249  }
250 
255  inline bool is_logged_in() const { return is_authorized; }
256 
261  inline const OrderIDSet& get_active_orders() const { return active_orders; }
262 
267  inline bool has_active_order() const { return not active_orders.empty(); }
268 
273  inline void set_shares(ClientShares shares_) { shares = shares_; }
274 
279  inline ClientShares get_shares() const { return shares; }
280 
285  inline void set_capital(ClientCapital capital_) { capital = capital_; }
286 
291  inline ClientCapital get_capital() const { return capital; }
292 
298  template <class T, typename ...Args>
299  inline void send(Args && ...args) {
300  // put an empty packet on the back of the queue
301  output_buffer.emplace_back();
302  // initialize the message data on the buffer using forwarded arguments
303  new (&output_buffer.back()) T{sequence++, std::forward<Args>(args)...};
304  // increment the number of messages available now the buffer is ready
305  // start the write event loop if this is the first message
306  if (++output_buffer_size == 1) write_buffer();
307  }
308 
314  template <class T, typename ...Args>
315  inline void send_sync(Args && ...args) {
316  // put an empty packet on the back of the queue
317  output_buffer.emplace_back();
318  // initialize the message data on the buffer using forwarded arguments
319  new (&output_buffer.back()) T{sequence++, std::forward<Args>(args)...};
320  // increment the number of messages available now the buffer is ready
321  // start the write event loop if this is the first message
322  if (++output_buffer_size == 1) write_buffer();
323  }
324 };
325 
326 } // namespace OrderEntry
327 
328 #endif // ORDER_ENTRY_CLIENT_HPP
OrderEntry
Logic for sending/receiving application messages in a financial market.
Definition: authorizer.hpp:26
OrderEntry::Messages::Packet
std::array< char, 40 > Packet
Definition: messages.hpp:146
OrderEntry::Messages::OrderResponse
A response describing the status of an order request.
Definition: messages.hpp:585
OrderEntry::Client::has_active_order
bool has_active_order() const
Return true if the client has an active order.
Definition: client.hpp:267
OrderEntry::Messages::LoginResponse::status
const LoginResponseStatus status
the status of the login request
Definition: messages.hpp:338
OrderEntry::Client::send
void send(Args &&...args)
Write a message asynchronously (non-blocking).
Definition: client.hpp:299
OrderEntry::Client::set_shares
void set_shares(ClientShares shares_)
Set the client shares to a new value.
Definition: client.hpp:273
OrderEntry::ClientCapital
int64_t ClientCapital
a type for storing the amount of capital a client has
Definition: client.hpp:39
OrderEntry::SequenceNumber
uint32_t SequenceNumber
A type for sequence numbers.
Definition: messages.hpp:36
OrderEntry::ClientShares
int32_t ClientShares
a type for storing the amount of shares a client has
Definition: client.hpp:37
OrderEntry::Messages::CancelResponse
A response describing the cancellation of an active order in the book.
Definition: messages.hpp:708
Exception
An exception class.
Definition: exceptions.hpp:25
OrderEntry::Messages::LogoutResponse
A response that tells the status of a session destruction to the client.
Definition: messages.hpp:449
OrderEntry::Client::get_capital
ClientCapital get_capital() const
Return the amount of capital the client currently has.
Definition: client.hpp:291
OrderEntry::Client::get_shares
ClientShares get_shares() const
Return the number of shares the client currently has.
Definition: client.hpp:279
OrderEntry::Messages::PacketQueue
std::deque< Packet > PacketQueue
A type for queuing packet buffers.
Definition: messages.hpp:149
OrderEntry::Client
A client for interacting with the direct market access server.
Definition: client.hpp:42
OrderEntry::Client::set_capital
void set_capital(ClientCapital capital_)
Set the client capital to a new value.
Definition: client.hpp:285
OrderEntry::Messages::PurgeResponse
A response describing the status of canceling all active orders in the book.
Definition: messages.hpp:976
OrderEntry::Messages::Header
A header containing type information and metadata for a message.
Definition: messages.hpp:190
OrderEntry::OrderIDSet
std::set< OrderID > OrderIDSet
a type for keeping order IDs in an unordered set
Definition: client.hpp:35
OrderEntry::Client::is_logged_in
bool is_logged_in() const
Return true if the client is authorized, false otherwise.
Definition: client.hpp:255
OrderEntry::Messages::LoginResponse
A response that tells the status of a session creation to the client.
Definition: messages.hpp:334
OrderEntry::Client::get_active_orders
const OrderIDSet & get_active_orders() const
Return the set of active orders for this client connection.
Definition: client.hpp:261
OrderEntry::Messages::ReplaceResponse
A response describing the replacement of an active order with a new order.
Definition: messages.hpp:852
OrderEntry::Messages::TradeResponse
A response describing a trade that occurred for a limit / market order.
Definition: messages.hpp:1026
OrderEntry::Client::Client
Client(asio::io_context &context, std::string host, std::string port)
Create a new order entry client.
Definition: client.hpp:243
OrderEntry::Client::send_sync
void send_sync(Args &&...args)
Write a message synchronously (blocking).
Definition: client.hpp:315