// // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/boostorg/beast // //------------------------------------------------------------------------------ // // Example: WebSocket SSL client, synchronous // //------------------------------------------------------------------------------ #include "tls-ca-bundle-pem.h" //#include "root_certificates.hpp" #include #include #include #include #include #include #include #include #include #include #include "subscription.h" #include "orderBook.h" namespace beast = boost::beast; // from namespace http = beast::http; // from namespace websocket = beast::websocket; // from namespace net = boost::asio; // from namespace ssl = boost::asio::ssl; // from using tcp = boost::asio::ip::tcp; // from // using namespace std; // Sends a WebSocket message and prints the response int main(int argc, char** argv) { OrderBook orderBook; try { std::string host = "advanced-trade-ws.coinbase.com"; auto const port = "443"; Json::Value root = coinbase::GetSubscribeMsg(); Json::FastWriter fastWriter; string text = fastWriter.write(root); cout << "-------------------------------------" << endl; cout << text << endl; cout << "-------------------------------------" << endl; // The io_context is required for all I/O net::io_context ioc; // The SSL context is required, and holds certificates ssl::context ctx{ssl::context::tlsv12_client}; // This holds the root certificate used for verification load_root_certificates(ctx); // These objects perform our I/O tcp::resolver resolver{ioc}; websocket::stream> ws{ioc, ctx}; // Look up the domain name auto const results = resolver.resolve(host, port); // Make the connection on the IP address we get from a lookup auto ep = net::connect(get_lowest_layer(ws), results); // Set SNI Hostname (many hosts need this to handshake successfully) if(! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) throw beast::system_error( beast::error_code( static_cast(::ERR_get_error()), net::error::get_ssl_category()), "Failed to set SNI Hostname"); // Update the host_ string. This will provide the value of the // Host HTTP header during the WebSocket handshake. // See https://tools.ietf.org/html/rfc7230#section-5.4 host += ':' + std::to_string(ep.port()); // Perform the SSL handshake ws.next_layer().handshake(ssl::stream_base::client); // Set a decorator to change the User-Agent of the handshake ws.set_option(websocket::stream_base::decorator( [](websocket::request_type& req) { req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro"); })); // Perform the websocket handshake ws.handshake(host, "/"); // Send the message ws.write(net::buffer(std::string(text))); for (;;) { // This buffer will hold the incoming message beast::flat_buffer buffer; // Read a message into our buffer ws.read(buffer); // The make_printable() function helps print a ConstBufferSequence //std::cout << "buffer: " << beast::make_printable(buffer.data()) << std::endl; Json::Reader reader; Json::Value root; std::string data((const char*)buffer.data().data(), buffer.data().size()); reader.parse(data, root); // The level2 channel sends a message with fields, type // ("snapshot" or "update"), product_id, and updates. // The field updates is an array of objects of // {price_level, new_quantity, event_time, side} to // represent the entire order book. // The new_quantity property is the updated size at that // price level, not a delta. A new_quantity of "0" // indicates the price level can be removed. if (root["type"].type() != Json::ValueType::nullValue) { Json::FastWriter fastWriter; std::string text = fastWriter.write(root); std::cout << text << std::endl; if (root["type"] == "error") break; } else if (root["channel"] != Json::ValueType::nullValue) { // { // channel: 'l2_data', // client_id: '', // timestamp: '2023-01-17T04:36:31.001272189Z', // sequence_num: 0, // type: null, // events: [ { type: 'snapshot', product_id: 'BTC-USD', updates: [Array] } ] // or // events: [ { type: 'update', product_id: 'BTC-USD', updates: [Array] } ] // } // updates: [Array] element // ------------------------ // { // "event_time" : "2023-01-17T05:10:19.205939Z", // "new_quantity" : "0", // "price_level" : "21160.98", // "side" : "bid" // } // subscriptions message // -------------------- // { // channel: 'subscriptions', // client_id: '', // timestamp: '2023-01-17T04:36:31.02158398Z', // sequence_num: 2, // events: [ { subscriptions: { level2: ['BTC-USD'] } } ] // } if (root["channel"] == "l2_data") { Json::Value event = root["events"]; Json::Value obj = event[0]; Json::Value updates = obj["updates"]; for (auto iter: updates) { Json::Value side = iter["side"]; Json::Value px = iter["price_level"]; Json::Value qty = iter["new_quantity"]; Json::Value time = iter["event_time"]; if (side == "bid") orderBook.insert(orderBook.m_bids, px, qty, time); else if (side == "offer") orderBook.insert(orderBook.m_asks, px, qty, time); else cerr << "unknown side: " << side << endl; orderBook.dump(10); } } } else { Json::FastWriter fastWriter; std::string text = fastWriter.write(root); std::cout << text << std::endl; break; } } // Close the WebSocket connection ws.close(websocket::close_code::normal); // If we get here then the connection is closed gracefully } catch(std::exception const& e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }