├── .gitignore ├── .gitmodules ├── .vs ├── CMake Overview ├── HFTBOT │ ├── FileContentIndex │ │ ├── f1ce7841-bb01-4d70-9eff-d3517ac8f6e3.vsidx │ │ └── read.lock │ └── v17 │ │ ├── .suo │ │ └── Browse.VC.db ├── ProjectSettings.json ├── VSWorkspaceState.json └── slnx.sqlite ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── Photo.png ├── adapters ├── Bitfinex │ ├── bitfinex-http.cpp │ └── bitfinex-http.hpp ├── binance │ ├── binance-http.cpp │ ├── binance-http.hpp │ └── binance-ws.hpp ├── coinbase │ ├── coinbase-http.cpp │ ├── coinbase-http.hpp │ └── coinbase-ws.hpp ├── ftx │ ├── ftx-http.cpp │ ├── ftx-http.hpp │ └── ftx-ws.hpp ├── kraken │ ├── kraken-http.cpp │ ├── kraken-http.hpp │ └── kraken-ws.hpp └── utils │ ├── utils.cpp │ └── utils.hpp ├── build.sh ├── configure.sh ├── features ├── TapeSpeedIndicator.cpp └── TapeSpeedIndicator.hpp ├── main ├── Ringbuffer.hpp ├── datafeeds.hpp ├── main.cpp ├── notes.md └── types.hpp ├── readme.md └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | CMakeLists.txt.user 35 | CMakeCache.txt 36 | CMakeFiles 37 | CMakeScripts 38 | Testing 39 | Makefile 40 | cmake_install.cmake 41 | install_manifest.txt 42 | compile_commands.json 43 | CTestTestfile.cmake 44 | _deps 45 | 46 | build/ 47 | 48 | [Bb][Uu][Ii][Ll][Dd]/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/boost-asio/asio"] 2 | path = external/boost-asio/asio 3 | url = https://github.com/boostorg/asio.git 4 | [submodule "external/asio"] 5 | path = external/asio 6 | url = https://github.com/boostorg/asio.git 7 | [submodule "external/url"] 8 | path = external/url 9 | url = https://github.com/CPPAlliance/url.git 10 | -------------------------------------------------------------------------------- /.vs/CMake Overview: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/CMake Overview -------------------------------------------------------------------------------- /.vs/HFTBOT/FileContentIndex/f1ce7841-bb01-4d70-9eff-d3517ac8f6e3.vsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/HFTBOT/FileContentIndex/f1ce7841-bb01-4d70-9eff-d3517ac8f6e3.vsidx -------------------------------------------------------------------------------- /.vs/HFTBOT/FileContentIndex/read.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/HFTBOT/FileContentIndex/read.lock -------------------------------------------------------------------------------- /.vs/HFTBOT/v17/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/HFTBOT/v17/.suo -------------------------------------------------------------------------------- /.vs/HFTBOT/v17/Browse.VC.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/HFTBOT/v17/Browse.VC.db -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": "x64-Debug" 3 | } -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "", 4 | "\\src" 5 | ], 6 | "SelectedNode": "\\src\\main.cpp", 7 | "PreviewInSolutionExplorer": false 8 | } -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [] 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cstring": "cpp", 4 | "cctype": "cpp", 5 | "clocale": "cpp", 6 | "cmath": "cpp", 7 | "csignal": "cpp", 8 | "cstdarg": "cpp", 9 | "cstddef": "cpp", 10 | "cstdio": "cpp", 11 | "cstdlib": "cpp", 12 | "ctime": "cpp", 13 | "cwchar": "cpp", 14 | "cwctype": "cpp", 15 | "array": "cpp", 16 | "atomic": "cpp", 17 | "*.tcc": "cpp", 18 | "bitset": "cpp", 19 | "chrono": "cpp", 20 | "codecvt": "cpp", 21 | "complex": "cpp", 22 | "condition_variable": "cpp", 23 | "cstdint": "cpp", 24 | "deque": "cpp", 25 | "list": "cpp", 26 | "unordered_map": "cpp", 27 | "vector": "cpp", 28 | "exception": "cpp", 29 | "algorithm": "cpp", 30 | "functional": "cpp", 31 | "iterator": "cpp", 32 | "map": "cpp", 33 | "memory": "cpp", 34 | "memory_resource": "cpp", 35 | "numeric": "cpp", 36 | "optional": "cpp", 37 | "random": "cpp", 38 | "ratio": "cpp", 39 | "set": "cpp", 40 | "string": "cpp", 41 | "string_view": "cpp", 42 | "system_error": "cpp", 43 | "tuple": "cpp", 44 | "type_traits": "cpp", 45 | "utility": "cpp", 46 | "fstream": "cpp", 47 | "future": "cpp", 48 | "initializer_list": "cpp", 49 | "iomanip": "cpp", 50 | "iosfwd": "cpp", 51 | "iostream": "cpp", 52 | "istream": "cpp", 53 | "limits": "cpp", 54 | "mutex": "cpp", 55 | "new": "cpp", 56 | "ostream": "cpp", 57 | "sstream": "cpp", 58 | "stdexcept": "cpp", 59 | "streambuf": "cpp", 60 | "thread": "cpp", 61 | "cinttypes": "cpp", 62 | "typeindex": "cpp", 63 | "typeinfo": "cpp", 64 | "variant": "cpp", 65 | "bit": "cpp", 66 | "*.ipp": "cpp", 67 | "any": "cpp", 68 | "source_location": "cpp", 69 | "cfenv": "cpp", 70 | "forward_list": "cpp", 71 | "filesystem": "cpp", 72 | "valarray": "cpp", 73 | "charconv": "cpp", 74 | "compare": "cpp", 75 | "concepts": "cpp", 76 | "format": "cpp", 77 | "ios": "cpp", 78 | "locale": "cpp", 79 | "stop_token": "cpp", 80 | "xfacet": "cpp", 81 | "xhash": "cpp", 82 | "xiosbase": "cpp", 83 | "xlocale": "cpp", 84 | "xlocbuf": "cpp", 85 | "xlocinfo": "cpp", 86 | "xlocmes": "cpp", 87 | "xlocmon": "cpp", 88 | "xlocnum": "cpp", 89 | "xloctime": "cpp", 90 | "xmemory": "cpp", 91 | "xstddef": "cpp", 92 | "xstring": "cpp", 93 | "xtr1common": "cpp", 94 | "xtree": "cpp", 95 | "xutility": "cpp" 96 | } 97 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "cppbuild", 5 | "label": "C/C++: g++ build active file", 6 | "command": "/usr/bin/g++", 7 | "args": [ 8 | "-fdiagnostics-color=always", 9 | "-g", 10 | "${file}", 11 | "-o", 12 | "${fileDirname}/${fileBasenameNoExtension}" 13 | ], 14 | "options": { 15 | "cwd": "${fileDirname}" 16 | }, 17 | "problemMatcher": [ 18 | "$gcc" 19 | ], 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "detail": "Task generated by Debugger." 25 | } 26 | ], 27 | "version": "2.0.0" 28 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | set (CMAKE_CXX_STANDARD 17) 3 | set(CMAKE_CXX_COMPILER /usr/bin/clang++) 4 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 5 | set (CMAKE_CXX_FLAGS "-O3 -pthread -DNDEBUG -lssl -lcrypto") 6 | set(CMAKE_BUILD_TYPE "Release") 7 | project(hftbot) 8 | 9 | include(FetchContent) 10 | 11 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz) 12 | FetchContent_MakeAvailable(json) 13 | 14 | find_package(Boost 1.79.0 REQUIRED COMPONENTS system thread filesystem container) 15 | find_package(Threads REQUIRED) 16 | 17 | set(URLIB_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external/url/include") 18 | set(URLIB_HEADERS ${URLIB_DIRECTORY}/boost/url.hpp ${URLIB_DIRECTORY}/boost/url/src.hpp) 19 | set(SOURCES adapters/bitfinex/bitfinex-http.cpp features/TapeSpeedIndicator.cpp main/main.cpp adapters/binance/binance-http.cpp adapters/utils/utils.cpp adapters/ftx/ftx-http.cpp adapters/coinbase/coinbase-http.cpp adapters/kraken/kraken-http.cpp) 20 | 21 | add_executable(${PROJECT_NAME} ${SOURCES}) 22 | target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) 23 | target_include_directories(${PROJECT_NAME} PUBLIC ${URLIB_DIRECTORY}) 24 | target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/adapters/bitfinex ${PROJECT_SOURCE_DIR}/features ${PROJECT_SOURCE_DIR}/adapters/binance ${PROJECT_SOURCE_DIR}/main ${PROJECT_SOURCE_DIR}/adapters/ftx ${PROJECT_SOURCE_DIR}/adapters/coinbase ${PROJECT_SOURCE_DIR}/adapters/kraken ${PROJECT_SOURCE_DIR}/adapters/utils) 25 | 26 | target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost Threads::Threads) 27 | target_link_libraries(${PROJECT_NAME} PUBLIC ssl crypto) 28 | target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json) 29 | 30 | -------------------------------------------------------------------------------- /Photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naseefabu/HFTBOT/963c3ae4bf33e56b31e4e0df4ae92658c78fa264/Photo.png -------------------------------------------------------------------------------- /adapters/Bitfinex/bitfinex-http.cpp: -------------------------------------------------------------------------------- 1 | #include "bitfinex-http.hpp" 2 | 3 | bitfinexAPI::bitfinexAPI(executor ex, ssl::context& ctxe, net::io_context &ioce) 4 | : resolver_(ex),stream_(ex, ctxe),ioc(ioce),ctx(ctxe) 5 | { 6 | PostDecodeString = decode64(this->secret_key); 7 | } 8 | 9 | 10 | http::response bitfinexAPI::http_call(boost::url url, http::verb action) 11 | { 12 | 13 | std::string const host(url.host()); 14 | std::string const service = "https"; 15 | url.remove_origin(); 16 | 17 | SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()); 18 | 19 | req_.method(action); 20 | req_.target(url.c_str()); 21 | req_.set(http::field::host, host); 22 | req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); 23 | req_.set("bfx-apikey", api_key); 24 | req_.set("Content-Type", "application/json"); 25 | req_.prepare_payload(); 26 | 27 | auto const results = resolver_.resolve(host, service); 28 | beast::get_lowest_layer(stream_).connect(results); 29 | 30 | stream_.handshake(ssl::stream_base::client); 31 | 32 | http::write(stream_, req_); 33 | http::read(stream_, buffer_, res_); 34 | beast::error_code ec; 35 | stream_.shutdown(ec); 36 | return res_; 37 | } 38 | 39 | // valid levels : 1,25,100 40 | json bitfinexAPI::get_snapshot(const std::string &symbol,const std::string &levels) 41 | { 42 | std::string path_params = "book/"+symbol+"/P3"; 43 | 44 | boost::url method{path_params}; 45 | method.params().emplace_back("len",levels); 46 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(public_base_api,method),http::verb::get).body()); 47 | 48 | } 49 | 50 | json bitfinexAPI::place_market_buy(std::string symbol,std::string amount) 51 | { 52 | boost::url method{ "auth/w/order/submit"}; 53 | 54 | nlohmann::ordered_json payload = {{"type", "MARKET"}, 55 | {"symbol", symbol}, 56 | {"amount",amount}}; 57 | 58 | long NONCE = generate_nonce(); 59 | std::string message = "/api/v2/auth/w/order/submit" + std::to_string(NONCE) + payload.dump(); 60 | std::string sign = getHmacSha384(message,secret_key); 61 | req_.target("/v2/auth/w/order/submit"); 62 | req_.set("bfx-nonce",std::to_string(NONCE)); 63 | req_.set("bfx-signature",sign); 64 | req_.body() = payload.dump(); 65 | return json::parse(http_call(make_url(private_base_api,method),http::verb::post).body()); 66 | 67 | } 68 | 69 | json bitfinexAPI::place_market_sell(std::string symbol,std::string amount) 70 | { 71 | 72 | boost::url method{ "auth/w/order/submit"}; 73 | amount.insert(0,"-"); 74 | nlohmann::ordered_json payload = {{"type", "MARKET"}, 75 | {"symbol", symbol}, 76 | {"amount",amount}}; 77 | 78 | long NONCE = generate_nonce(); 79 | std::string message = "/api/v2/auth/w/order/submit" + std::to_string(NONCE) + payload.dump(); 80 | std::string sign = getHmacSha384(message,secret_key); 81 | req_.target("/v2/auth/w/order/submit"); 82 | req_.set("bfx-nonce",std::to_string(NONCE)); 83 | req_.set("bfx-signature",sign); 84 | req_.body() = payload.dump(); 85 | return json::parse(http_call(make_url(private_base_api,method),http::verb::post).body()); 86 | 87 | } 88 | 89 | json bitfinexAPI::cancel_order(int orderid) 90 | { 91 | 92 | boost::url method{ "auth/w/order/cancel"}; 93 | nlohmann::ordered_json payload = {{"id", orderid}}; 94 | 95 | long NONCE = generate_nonce(); 96 | std::string message = "/api/v2/auth/w/order/cancel" + std::to_string(NONCE) + payload.dump(); 97 | std::string sign = getHmacSha384(message,secret_key); 98 | req_.set("bfx-nonce",std::to_string(NONCE)); 99 | req_.set("bfx-signature",sign); 100 | req_.body() = payload.dump(); 101 | return json::parse(http_call(make_url(private_base_api,method),http::verb::post).body()); 102 | 103 | } 104 | 105 | json bitfinexAPI::cancel_all_orders() 106 | { 107 | 108 | boost::url method{ "auth/w/order/cancel/multi"}; 109 | 110 | long NONCE = generate_nonce(); 111 | std::string message = "/api/v2/auth/w/order/cancel/multi" + std::to_string(NONCE); 112 | std::string sign = getHmacSha384(message,secret_key); 113 | req_.set("bfx-nonce",std::to_string(NONCE)); 114 | req_.set("bfx-signature",sign); 115 | return json::parse(http_call(make_url(private_base_api,method),http::verb::post).body()); 116 | 117 | } -------------------------------------------------------------------------------- /adapters/Bitfinex/bitfinex-http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.hpp" 13 | #include 14 | 15 | namespace beast = boost::beast; 16 | namespace http = beast::http; 17 | namespace net = boost::asio; 18 | namespace ssl = boost::asio::ssl; 19 | using tcp = boost::asio::ip::tcp; 20 | 21 | using json = nlohmann::json; 22 | using executor = net::any_io_executor; 23 | 24 | /* 25 | Timeforce : GTC,IOC,FOK 26 | fix expensive smart pointers 27 | */ 28 | 29 | class bitfinexAPI : public std::enable_shared_from_this 30 | { 31 | 32 | private: 33 | 34 | void configure(const std::string &api,const std::string &secret); 35 | boost::url public_base_api{"https://api-pub.bitfinex.com/v2/"}; 36 | boost::url private_base_api{"https://api.bitfinex.com/v2/"}; 37 | tcp::resolver resolver_; 38 | beast::ssl_stream stream_; 39 | beast::flat_buffer buffer_; 40 | http::request req_; 41 | http::response res_; 42 | std::string api_key = "YRpmzFgOAeaYGCpHqsRAkRZTgtofziPRJsQ59IMey0z"; 43 | std::string secret_key = "0191uDe4OarxnxstnLVGInZGEqOY05culJMZGNWHEtk"; 44 | net::io_context& ioc; 45 | ssl::context& ctx; 46 | std::string PostDecodeString; 47 | 48 | public: 49 | 50 | bitfinexAPI(executor ex, ssl::context& ctx,net::io_context& ioc); 51 | 52 | http::response http_call(boost::url url, http::verb action); 53 | 54 | json get_snapshot(const std::string &symbol,const std::string &levels); 55 | 56 | json place_market_buy(std::string symbol,std::string amount); 57 | 58 | json place_market_sell(std::string symbol,std::string amount); 59 | 60 | json cancel_order(int orderid); 61 | 62 | json cancel_all_orders(); // including trading and derivatives 63 | 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /adapters/binance/binance-http.cpp: -------------------------------------------------------------------------------- 1 | #include "binance-http.hpp" 2 | 3 | 4 | 5 | binanceAPI::binanceAPI(executor ex, ssl::context& ctxe, net::io_context &ioce) 6 | : resolver_(ex),stream_(ex, ctxe),ioc(ioce),ctx(ctxe){} 7 | 8 | 9 | http::response binanceAPI::http_call(boost::url url, http::verb action) 10 | { 11 | 12 | std::string const host(url.host()); 13 | std::string const service = "https"; 14 | url.remove_origin(); 15 | 16 | SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()); 17 | 18 | req_.method(action); 19 | req_.target(url.c_str()); 20 | req_.set(http::field::host, host); 21 | req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); 22 | req_.set("X-MBX-APIKEY", api_key); 23 | 24 | req_.prepare_payload(); 25 | 26 | auto const results = resolver_.resolve(host, service); 27 | beast::get_lowest_layer(stream_).connect(results); 28 | 29 | stream_.handshake(ssl::stream_base::client); 30 | 31 | http::write(stream_, req_); 32 | http::read(stream_, buffer_, res_); 33 | std::cout << "raw request : " << req_ << std::endl; 34 | std::cout << "response : " << res_.body() << std::endl; 35 | beast::error_code ec; 36 | stream_.shutdown(ec); 37 | 38 | return res_; 39 | } 40 | 41 | 42 | void binanceAPI::configure(const std::string &api,const std::string &secret) 43 | { 44 | this->api_key = api; 45 | this->secret_key = secret; 46 | } 47 | 48 | json binanceAPI::server_time() 49 | { 50 | boost::url method{"time"}; 51 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 52 | } 53 | 54 | json binanceAPI::latest_price(const std::string &symbol) 55 | { 56 | boost::url method{"ticker/price"}; 57 | method.params().emplace_back("symbol",symbol); 58 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 59 | } 60 | 61 | 62 | json binanceAPI::exchange_info(const std::string &symbol) 63 | { 64 | boost::url method{"exchangeInfo"}; 65 | method.params().emplace_back("symbol",symbol); 66 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 67 | } 68 | 69 | json binanceAPI::ping_binance( ) 70 | { 71 | boost::url method{"ping"}; 72 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 73 | 74 | } 75 | 76 | json binanceAPI::orderbook(const std::string &symbol,const std::string &levels) 77 | { 78 | 79 | boost::url method{"depth"}; 80 | method.params().emplace_back("symbol",symbol); 81 | method.params().emplace_back("limit",levels); 82 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 83 | 84 | } 85 | 86 | json binanceAPI::recent_trades(const std::string &symbol,const std::string &levels) 87 | { 88 | boost::url method{"trades"}; 89 | method.params().emplace_back("symbol",symbol); 90 | method.params().emplace_back("limit",levels); 91 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 92 | } 93 | 94 | json binanceAPI::klines(const std::string &symbol,const std::string &interval) 95 | { 96 | 97 | boost::url method{"klines"}; 98 | method.params().emplace_back("symbol",symbol); 99 | method.params().emplace_back("interval",interval); 100 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 101 | 102 | } 103 | 104 | json binanceAPI::avg_price(const std::string &symbol ) 105 | { 106 | 107 | boost::url method{"avgPrice"}; 108 | method.params().emplace_back("symbol",symbol); 109 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 110 | 111 | } 112 | 113 | json binanceAPI::bidask(const std::string &symbol ) 114 | { 115 | boost::url method{"ticker/bookTicker"}; 116 | method.params().emplace_back("symbol",symbol); 117 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 118 | } 119 | 120 | json binanceAPI::open_orders( ) 121 | { 122 | boost::url method{"openOrders"}; 123 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 124 | std::string query_params = "timestamp=" +server_timestamp; 125 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); // order matters 126 | method.params().emplace_back("timestamp",server_timestamp); 127 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 128 | } 129 | 130 | 131 | json binanceAPI::place_order(const std::string &symbol,double price,std::string side,std::string timeforce,const std::string &quantity ) 132 | { 133 | boost::url method{"order"}; 134 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 135 | std::string query_params; 136 | 137 | query_params ="symbol="+symbol+"&side="+side +"&type=LIMIT"+ "&timeInForce="+timeforce+ "&quantity="+quantity+"&price="+std::to_string(price)+"&recvWindow=60000"+"×tamp=" + server_timestamp; 138 | 139 | method.params().emplace_back("symbol",symbol); 140 | method.params().emplace_back("side",side); 141 | method.params().emplace_back("type","LIMIT"); 142 | method.params().emplace_back("timeInForce",timeforce); 143 | method.params().emplace_back("quantity",quantity); 144 | method.params().emplace_back("price",std::to_string(price)); 145 | method.params().emplace_back("recvWindow", "60000"); 146 | method.params().emplace_back("timestamp",server_timestamp); 147 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 148 | 149 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::post).body());; 150 | 151 | } 152 | 153 | json binanceAPI::place_order(const std::string &symbol,std::string side,const std::string &quantity ) 154 | { 155 | boost::url method{"order"}; 156 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 157 | std::string query_params; 158 | 159 | query_params ="symbol="+symbol+"&side="+side +"&type=MARKET"+ "&quantity="+quantity+"&recvWindow=60000"+"×tamp=" + server_timestamp; 160 | 161 | method.params().emplace_back("symbol",symbol); 162 | method.params().emplace_back("side",side); 163 | method.params().emplace_back("type","MARKET"); 164 | method.params().emplace_back("quantity",quantity); 165 | method.params().emplace_back("recvWindow", "60000"); 166 | method.params().emplace_back("timestamp",server_timestamp); 167 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 168 | 169 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::post).body());; 170 | 171 | } 172 | 173 | json binanceAPI::cancel_order(const std::string &symbol,int orderid ) 174 | { 175 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 176 | std::string query_params = "symbol="+symbol+"&orderId="+std::to_string(orderid)+"×tamp="+server_timestamp; 177 | boost::url method{"order"}; 178 | method.params().emplace_back("symbol",symbol); 179 | method.params().emplace_back("orderId",std::to_string(orderid)); 180 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 181 | method.params().emplace_back("timestamp",server_timestamp); 182 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::delete_).body()); 183 | 184 | } 185 | 186 | json binanceAPI::cancel_all_orders(const std::string &symbol) 187 | { 188 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 189 | std::string query_params = "symbol="+symbol+"×tamp="+server_timestamp; 190 | boost::url method{"openOrders"}; 191 | method.params().emplace_back("symbol",symbol); 192 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 193 | method.params().emplace_back("timestamp",server_timestamp); 194 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::delete_).body()); 195 | } 196 | 197 | json binanceAPI::check_order_status(const std::string &symbol,int orderid ) 198 | { 199 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 200 | std::string query_params = "symbol="+symbol+"&orderId="+std::to_string(orderid)+"×tamp="+server_timestamp; 201 | boost::url method{"order"}; 202 | method.params().emplace_back("symbol",symbol); 203 | method.params().emplace_back("orderId",std::to_string(orderid)); 204 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 205 | method.params().emplace_back("timestamp",server_timestamp); 206 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 207 | 208 | } 209 | 210 | json binanceAPI::get_account_info() 211 | { 212 | std::string server_timestamp = std::to_string(get_ms_timestamp(current_time()).count()); 213 | std::string query_params = "timestamp="+server_timestamp; 214 | boost::url method{"account"}; 215 | method.params().emplace_back("signature",getHmacSha256(this->secret_key.c_str(),query_params.c_str())); 216 | method.params().emplace_back("timestamp",server_timestamp); 217 | return json::parse(std::make_shared(ioc.get_executor(),ctx,ioc)->http_call(make_url(base_api,method),http::verb::get).body()); 218 | 219 | } 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /adapters/binance/binance-http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.hpp" 13 | #include 14 | 15 | namespace beast = boost::beast; 16 | namespace http = beast::http; 17 | namespace net = boost::asio; 18 | namespace ssl = boost::asio::ssl; 19 | using tcp = boost::asio::ip::tcp; 20 | 21 | using json = nlohmann::json; 22 | using executor = net::any_io_executor; 23 | 24 | /* 25 | Timeforce : GTC,IOC,FOK 26 | fix expensive smart pointers 27 | */ 28 | 29 | class binanceAPI : public std::enable_shared_from_this 30 | { 31 | 32 | private: 33 | 34 | void configure(const std::string &api,const std::string &secret); 35 | boost::url base_api{"https://api1.binance.com/api/v3/"}; 36 | tcp::resolver resolver_; 37 | beast::ssl_stream stream_; 38 | beast::flat_buffer buffer_; 39 | http::request req_; 40 | http::response res_; 41 | std::string api_key = "v6uhUtse5Ae1Gyz72eMSbUMGw7VUDdd5AnqobMOW1Llzi4snnfP4YCyY9V74PFJ4"; 42 | std::string secret_key = "FW8j4YobD26PVP6QLu0sv4Dv7OzrtfgQKzn8FoIMwGzMW9Y0VmX1DatbLIfXoCHV"; 43 | net::io_context& ioc; 44 | ssl::context& ctx; 45 | 46 | public: 47 | 48 | binanceAPI(executor ex, ssl::context& ctx,net::io_context& ioc); 49 | 50 | http::response http_call(boost::url url, http::verb action); 51 | 52 | json server_time(); 53 | 54 | json latest_price(const std::string &symbol); 55 | 56 | json exchange_info(const std::string &symbol); 57 | 58 | json get_account_info(); 59 | 60 | json check_order_status(const std::string &symbol,int orderid); 61 | 62 | json cancel_all_orders(const std::string &symbol); 63 | 64 | json cancel_order(const std::string &symbol,int orderid); 65 | 66 | json place_order(const std::string &symbol,double price,std::string side,std::string timeforce,const std::string &quantity); 67 | 68 | json place_order(const std::string &symbol,std::string side,const std::string &quantity ); 69 | 70 | json open_orders(); 71 | 72 | json bidask(const std::string &symbol); 73 | 74 | json avg_price(const std::string &symbol); 75 | 76 | json klines(const std::string &symbol,const std::string &interval); 77 | 78 | json recent_trades(const std::string &symbol,const std::string &levels); 79 | 80 | json orderbook(const std::string &symbol,const std::string &levels); 81 | 82 | json ping_binance(); 83 | 84 | }; 85 | -------------------------------------------------------------------------------- /adapters/binance/binance-ws.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | //#include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "Ringbuffer.hpp" 16 | #include 17 | #include "types.hpp" 18 | 19 | namespace beast = boost::beast; 20 | namespace http = beast::http; 21 | namespace websocket = beast::websocket; 22 | namespace net = boost::asio; 23 | namespace ssl = net::ssl; 24 | using beast::error_code; 25 | 26 | using tcp = net::ip::tcp; 27 | using json = nlohmann::json; 28 | 29 | using TCPStream = beast::tcp_stream; 30 | using SSLStream = beast::ssl_stream; 31 | using Stream = websocket::stream; 32 | 33 | using namespace std::chrono_literals; 34 | void fail_ws(beast::error_code ec, char const* what); 35 | #define BINANCE_HANDLER(f) beast::bind_front_handler(&binanceWS::f, this->shared_from_this()) 36 | 37 | 38 | class binanceWS : public std::enable_shared_from_this { 39 | 40 | private: 41 | 42 | tcp::resolver resolver_; 43 | Stream ws_; 44 | beast::flat_buffer buffer_; 45 | std::string host_; 46 | std::string message_text_; 47 | 48 | std::string wsTarget_ = "/ws/"; 49 | char const* host = "stream.binance.com"; 50 | char const* port = "9443"; 51 | std::function message_handler; 52 | 53 | public: 54 | binanceWS(net::any_io_executor ex, ssl::context& ctx) 55 | : resolver_(ex) 56 | , ws_(ex, ctx) {} 57 | 58 | void run(char const* host, char const* port, json message, const std::string& streamName) { 59 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host)) { 60 | throw boost::system::system_error( 61 | error_code(::ERR_get_error(), net::error::get_ssl_category())); 62 | } 63 | 64 | host_ = host; 65 | message_text_ = message.dump(); 66 | wsTarget_ += streamName; 67 | 68 | resolver_.async_resolve(host_, port, BINANCE_HANDLER(on_resolve)); 69 | } 70 | 71 | void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { 72 | if (ec) 73 | return fail_ws(ec, "resolve"); 74 | 75 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host_.c_str())) { 76 | throw beast::system_error{ 77 | error_code(::ERR_get_error(), net::error::get_ssl_category())}; 78 | } 79 | 80 | get_lowest_layer(ws_).expires_after(30s); 81 | 82 | beast::get_lowest_layer(ws_).async_connect(results, BINANCE_HANDLER(on_connect)); 83 | } 84 | 85 | void on_connect(beast::error_code ec, 86 | [[maybe_unused]] tcp::resolver::results_type::endpoint_type ep) { 87 | if (ec) 88 | return fail_ws(ec, "connect"); 89 | 90 | // Perform the SSL handshake 91 | ws_.next_layer().async_handshake(ssl::stream_base::client, BINANCE_HANDLER(on_ssl_handshake)); 92 | } 93 | 94 | void on_ssl_handshake(beast::error_code ec) { 95 | if (ec) 96 | return fail_ws(ec, "ssl_handshake"); 97 | 98 | beast::get_lowest_layer(ws_).expires_never(); 99 | 100 | ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); 101 | 102 | ws_.set_option(websocket::stream_base::decorator([](websocket::request_type& req) { 103 | req.set(http::field::user_agent, 104 | std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async"); 105 | })); 106 | 107 | std::cout << "using host_: " << host_ << std::endl; 108 | ws_.async_handshake(host_, wsTarget_, BINANCE_HANDLER(on_handshake)); 109 | } 110 | 111 | void on_handshake(beast::error_code ec) { 112 | if (ec) { 113 | return fail_ws(ec, "handshake"); 114 | } 115 | 116 | std::cout << "Sending : " << message_text_ << std::endl; 117 | 118 | ws_.async_write(net::buffer(message_text_), BINANCE_HANDLER(on_write)); 119 | } 120 | 121 | void on_write(beast::error_code ec, size_t bytes_transferred) { 122 | boost::ignore_unused(bytes_transferred); 123 | 124 | if (ec) 125 | return fail_ws(ec, "write"); 126 | std::cout << "on_write : " << std::endl; 127 | 128 | ws_.async_read(buffer_, BINANCE_HANDLER(on_message)); 129 | } 130 | 131 | void on_message(beast::error_code ec, size_t bytes_transferred) { 132 | 133 | boost::ignore_unused(bytes_transferred); 134 | if (ec) 135 | return fail_ws(ec, "read"); 136 | 137 | //json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 138 | // std::cout << "on_message : " << payload << std::endl; 139 | 140 | buffer_.clear(); 141 | ws_.async_read(buffer_, [this](beast::error_code ec, size_t n) { 142 | if (ec) 143 | return fail_ws(ec, "read"); 144 | 145 | message_handler(); 146 | buffer_.clear(); 147 | ws_.async_read(buffer_, BINANCE_HANDLER(on_message)); 148 | }); 149 | 150 | } 151 | 152 | void on_close(beast::error_code ec) { 153 | if (ec) 154 | return fail_ws(ec, "close"); 155 | 156 | std::cout << beast::make_printable(buffer_.data()) << std::endl; 157 | } 158 | 159 | 160 | void subscribe_aggtrades(const std::string& action,const std::string& symbol) 161 | { 162 | std::string stream = symbol+"@"+"aggTrade"; 163 | json jv = { 164 | { "method", action }, 165 | { "params", {stream} }, 166 | { "id", 1 } 167 | }; 168 | run(host, port,jv, stream); 169 | } 170 | 171 | 172 | void subscribe_trades(const std::string& action,const std::string& symbol) 173 | { 174 | std::string stream = symbol+"@"+"trade"; 175 | json jv = { 176 | { "method", action }, 177 | { "params", {stream} }, 178 | { "id", 1 } 179 | }; 180 | run(host, port,jv, stream); 181 | } 182 | 183 | 184 | /* stream candle stick every second */ 185 | 186 | void subscribe_candlestick(const std::string& action,const std::string& symbol,const std::string& interval) 187 | { 188 | std::string stream = symbol+"@"+"kline_"+interval; 189 | json jv = { 190 | { "method", action }, 191 | { "params", {stream} }, 192 | { "id", 1 } 193 | }; 194 | run(host, port,jv, stream); 195 | } 196 | 197 | 198 | void subscribe_levelone(const std::string& action,const std::string& symbol) 199 | { 200 | std::string stream = symbol+"@"+"bookTicker"; 201 | message_handler = [this]() { 202 | 203 | json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 204 | bool is; 205 | 206 | std::cout << "payload : " << payload << std::endl; 207 | //for(auto x : payload["bids"]) 208 | //is = diff_messages_queue.push(OrderBookMessage(true,std::stod(x[0].get()),std::stod(x[1].get()),payload['u'])); // no update id 209 | 210 | 211 | //for(auto x : payload["asks"]) 212 | //is = diff_messages_queue.push(OrderBookMessage(false,std::stod(x[0].get()),std::stod(x[1].get()),payload["u"])); // no update id 213 | 214 | }; 215 | json jv = { 216 | { "method", action }, 217 | { "params", {stream} }, 218 | { "id", 1 } 219 | }; 220 | run(host, port,jv, stream); 221 | } 222 | 223 | 224 | void subscribe_orderbook_diffs(const std::string action,const std::string symbol,short int depth_levels) 225 | { 226 | std::string stream = symbol+"@"+"depth";//+"@"+std::to_string(depth_levels); 227 | 228 | 229 | message_handler = [this]() { 230 | 231 | json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 232 | bool is; 233 | 234 | std::cout << "First update ID : " << payload["U"] << " "; 235 | std::cout << "Last update ID : " << payload["u"] << std::endl; 236 | 237 | //for(auto x : payload["bids"]) 238 | //is = diff_messages_queue.push(OrderBookMessage(true,std::stod(x[0].get()),std::stod(x[1].get()),payload['u'])); // no update id 239 | 240 | 241 | //for(auto x : payload["asks"]) 242 | //is = diff_messages_queue.push(OrderBookMessage(false,std::stod(x[0].get()),std::stod(x[1].get()),payload["u"])); // no update id 243 | 244 | }; 245 | 246 | json jv = { 247 | { "method", action }, 248 | { "params", {stream} }, 249 | { "id", 1 } 250 | }; 251 | run(host, port,jv, stream); 252 | } 253 | 254 | 255 | void subscribe_orderbook(const std::string& action,const std::string& symbol) 256 | { 257 | std::string stream = symbol+"@"+"depth"; 258 | json jv = { 259 | { "method", action }, 260 | { "params", {stream} }, 261 | { "id", 1 } 262 | }; 263 | run(host, port,jv, stream); 264 | } 265 | 266 | 267 | 268 | }; 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /adapters/coinbase/coinbase-http.cpp: -------------------------------------------------------------------------------- 1 | #include "coinbase-http.hpp" 2 | 3 | 4 | coinbaseAPI::coinbaseAPI(executor ex, ssl::context& ctxe, net::io_context &ioce) 5 | : resolver_(ex),stream_(ex, ctxe),ioc(ioce),ctx(ctxe){ 6 | PostDecodeString = decode64(this->secret_key); 7 | } 8 | 9 | 10 | http::response coinbaseAPI::http_call(boost::url url, http::verb action) 11 | { 12 | 13 | std::string const host(url.host()); 14 | 15 | std::string const service = "https"; 16 | url.remove_origin(); 17 | 18 | SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()); 19 | 20 | req_.method(action); 21 | req_.target(url.c_str()); 22 | req_.set(http::field::host, host); 23 | req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); 24 | req_.set("CB-ACCESS-KEY",api_key); 25 | req_.set("CB-ACCESS-PASSPHRASE",passphrase); 26 | req_.set("Content-Type","application/json"); 27 | req_.prepare_payload(); 28 | 29 | auto const results = resolver_.resolve(host, service); 30 | beast::get_lowest_layer(stream_).connect(results); 31 | 32 | stream_.handshake(ssl::stream_base::client); 33 | 34 | http::write(stream_, req_); 35 | http::read(stream_, buffer_, res_); 36 | beast::error_code ec; 37 | stream_.shutdown(ec); 38 | 39 | return res_; 40 | } 41 | 42 | 43 | void coinbaseAPI::configure(const std::string &api, const std::string &secret,const std::string &pass) 44 | { 45 | this->api_key = api; 46 | this->secret_key = secret; 47 | this->passphrase = pass; 48 | } 49 | 50 | json coinbaseAPI::open_orders() 51 | { 52 | boost::url method{"orders"}; 53 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 54 | std::string data = time + "GET" + "/orders"; 55 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 56 | req_.set("CB-ACCESS-SIGN",sign); 57 | req_.set("CB-ACCESS-TIMESTAMP", time); 58 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 59 | } 60 | 61 | json coinbaseAPI::cancel_all() 62 | { 63 | boost::url method{"orders"}; 64 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 65 | std::string data = time + "DELETE" + "/orders"; 66 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 67 | req_.set("CB-ACCESS-SIGN",sign); 68 | req_.set("CB-ACCESS-TIMESTAMP", time); 69 | return json::parse(http_call(make_url(base_api,method),http::verb::delete_).body()); 70 | } 71 | 72 | // "Trading Pair is not allowed" => most likely because of your region that is permission denied 73 | ordered_json coinbaseAPI::place_market_buy(std::string market,std::string size) 74 | { 75 | boost::url method{"orders"}; 76 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 77 | 78 | ordered_json payload = {{"type", "market"}, 79 | {"side", "buy"}, 80 | {"product_id",market}, 81 | {"funds", size}}; 82 | 83 | std::string data = time + "POST" + "/orders" + payload.dump(); 84 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 85 | req_.set("CB-ACCESS-SIGN",sign); 86 | req_.set("CB-ACCESS-TIMESTAMP", time); 87 | req_.body() = payload.dump(); 88 | return json::parse(http_call(make_url(base_api,method),http::verb::post).body()); 89 | } 90 | 91 | ordered_json coinbaseAPI::place_market_sell(std::string market,std::string size) 92 | { 93 | boost::url method{"orders"}; 94 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 95 | 96 | ordered_json payload = {{"type", "market"}, 97 | {"side", "sell"}, 98 | {"product_id",market}, 99 | {"size", size}}; 100 | 101 | std::string data = time + "POST" + "/orders" + payload.dump(); 102 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 103 | req_.set("CB-ACCESS-SIGN",sign); 104 | req_.set("CB-ACCESS-TIMESTAMP", time); 105 | req_.body() = payload.dump(); 106 | return json::parse(http_call(make_url(base_api,method),http::verb::post).body()); 107 | } 108 | 109 | ordered_json coinbaseAPI::place_limit_order(std::string market,int price,std::string size, std::string side) 110 | { 111 | boost::url method{"orders"}; 112 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 113 | 114 | ordered_json payload = {{"type", "limit"}, 115 | {"side", side}, 116 | {"product_id",market}, 117 | {"price",std::to_string(price)}, 118 | {"size", size}, 119 | {"post_only",true}}; 120 | 121 | std::string data = time + "POST" + "/orders" + payload.dump(); 122 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 123 | req_.set("CB-ACCESS-SIGN",sign); 124 | req_.set("CB-ACCESS-TIMESTAMP", time); 125 | req_.body() = payload.dump(); 126 | return json::parse(http_call(make_url(base_api,method),http::verb::post).body()); 127 | } 128 | 129 | json coinbaseAPI::cancel_order(int orderid) 130 | { 131 | boost::url method{"orders/"+std::to_string(orderid)}; 132 | std::string time = std::to_string(get_sec_timestamp(current_time()).count()); 133 | std::string data = time + "DELETE" + "/orders/"+std::to_string(orderid); 134 | sign = coinbase_HmacSha256(PostDecodeString.c_str(),data.c_str()); 135 | req_.set("CB-ACCESS-SIGN",sign); 136 | req_.set("CB-ACCESS-TIMESTAMP", time); 137 | return json::parse(http_call(make_url(base_api,method),http::verb::delete_).body()); 138 | } 139 | 140 | json coinbaseAPI::get_snapshot(std::string market, int level) 141 | { 142 | boost::url method{"products/"+market+"/"+"book"}; 143 | method.params().emplace_back("level",std::to_string(level)); 144 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 145 | } -------------------------------------------------------------------------------- /adapters/coinbase/coinbase-http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.hpp" 13 | #include 14 | 15 | namespace beast = boost::beast; 16 | namespace http = beast::http; 17 | namespace net = boost::asio; 18 | namespace ssl = boost::asio::ssl; 19 | using tcp = boost::asio::ip::tcp; 20 | 21 | using json = nlohmann::json; 22 | using ordered_json = nlohmann::ordered_json; 23 | using executor = net::any_io_executor; 24 | 25 | class coinbaseAPI : public std::enable_shared_from_this 26 | { 27 | 28 | private: 29 | 30 | void configure(const std::string &api, const std::string& secret, const std::string &pass); 31 | boost::url base_api{"https://api.exchange.coinbase.com/"}; 32 | std::string secret_key = "Fhy8v43DGnUC6wPKF89ImG8xUMjiMGduSKlGaV39Ttbgh6gbk4lNkH3g/GBh3tYkm5gDKCUilyS8WFh0Bn1wVQ=="; 33 | std::string PostDecodeString; 34 | tcp::resolver resolver_; 35 | beast::ssl_stream stream_; 36 | beast::flat_buffer buffer_; 37 | http::request req_; 38 | http::response res_; 39 | std::string api_key = "db11f28ba4fa14b548ee568c4d236745"; 40 | std::string passphrase = "f717xa2bkqi"; 41 | net::io_context& ioc; 42 | ssl::context& ctx; 43 | std::string sign; 44 | 45 | public: 46 | 47 | explicit coinbaseAPI(executor ex, ssl::context& ctx,net::io_context& ioc); 48 | 49 | http::response http_call(boost::url url, http::verb action); 50 | 51 | json open_orders(); 52 | 53 | json get_snapshot(std::string market, int level); 54 | 55 | json cancel_order(int orderid); 56 | 57 | json cancel_all(); 58 | 59 | ordered_json place_market_buy(std::string market,std::string size); 60 | 61 | ordered_json place_market_sell(std::string market,std::string size); 62 | 63 | ordered_json place_limit_order(std::string market,int price,std::string size, std::string side); 64 | 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /adapters/coinbase/coinbase-ws.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Ringbuffer.hpp" 15 | #include 16 | #include "types.hpp" 17 | #include 18 | 19 | namespace beast = boost::beast; 20 | namespace http = beast::http; 21 | namespace websocket = beast::websocket; 22 | namespace net = boost::asio; 23 | namespace ssl = net::ssl; 24 | using beast::error_code; 25 | 26 | using tcp = net::ip::tcp; 27 | using json = nlohmann::json; 28 | 29 | using TCPStream = beast::tcp_stream; 30 | using SSLStream = beast::ssl_stream; 31 | using Stream = websocket::stream; 32 | 33 | using namespace std::chrono_literals; 34 | void fail_ws(beast::error_code ec, char const* what); 35 | #define COINBASE_HANDLER(c) beast::bind_front_handler(&coinbaseWS::c, this->shared_from_this()) 36 | 37 | 38 | 39 | 40 | class coinbaseWS : public std::enable_shared_from_this 41 | { 42 | tcp::resolver resolver_; 43 | Stream ws_; 44 | beast::flat_buffer buffer_; 45 | std::string message_text_; 46 | char const* host = "ws-feed.exchange.coinbase.com"; 47 | std::string wsTarget_ = "/ws/"; 48 | std::string host_; 49 | SPSCQueue &diff_messages_queue; 50 | std::function message_handler; 51 | std::unordered_map>> bids; 52 | std::unordered_map>> asks; 53 | 54 | public: 55 | 56 | coinbaseWS(net::any_io_executor ex, ssl::context& ctx, SPSCQueue& q) 57 | : resolver_(ex) 58 | , ws_(ex, ctx) 59 | , diff_messages_queue(q) {} 60 | 61 | void run(json message) { 62 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host)) { 63 | throw boost::system::system_error( 64 | error_code(::ERR_get_error(), net::error::get_ssl_category())); 65 | } 66 | host_ = host; 67 | message_text_ = message.dump(); 68 | 69 | resolver_.async_resolve(host_, "443", COINBASE_HANDLER(on_resolve)); 70 | } 71 | 72 | void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { 73 | if (ec) 74 | return fail_ws(ec, "resolve"); 75 | 76 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host_.c_str())) { 77 | throw beast::system_error{ 78 | error_code(::ERR_get_error(), net::error::get_ssl_category())}; 79 | } 80 | 81 | get_lowest_layer(ws_).expires_after(30s); 82 | 83 | beast::get_lowest_layer(ws_).async_connect(results, COINBASE_HANDLER(on_connect)); 84 | } 85 | 86 | void on_connect(beast::error_code ec, 87 | [[maybe_unused]] tcp::resolver::results_type::endpoint_type ep) { 88 | if (ec) 89 | return fail_ws(ec, "connect"); 90 | 91 | // Perform the SSL handshake 92 | ws_.next_layer().async_handshake(ssl::stream_base::client, COINBASE_HANDLER(on_ssl_handshake)); 93 | } 94 | 95 | void on_ssl_handshake(beast::error_code ec) { 96 | if (ec) 97 | return fail_ws(ec, "ssl_handshake"); 98 | 99 | beast::get_lowest_layer(ws_).expires_never(); 100 | 101 | ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); 102 | 103 | ws_.set_option(websocket::stream_base::decorator([](websocket::request_type& req) { 104 | req.set(http::field::user_agent, 105 | std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async"); 106 | })); 107 | 108 | std::cout << "using host_: " << host_ << std::endl; 109 | ws_.async_handshake(host_, wsTarget_, COINBASE_HANDLER(on_handshake)); 110 | } 111 | 112 | void on_handshake(beast::error_code ec) { 113 | if (ec) { 114 | return fail_ws(ec, "handshake"); 115 | } 116 | 117 | std::cout << "Sending : " << message_text_ << std::endl; 118 | 119 | ws_.async_write(net::buffer(message_text_), COINBASE_HANDLER(on_write)); 120 | } 121 | 122 | void on_write(beast::error_code ec, size_t bytes_transferred) { 123 | boost::ignore_unused(bytes_transferred); 124 | 125 | if (ec) 126 | return fail_ws(ec, "write"); 127 | 128 | ws_.async_read(buffer_, COINBASE_HANDLER(on_message)); 129 | } 130 | 131 | void on_message(beast::error_code ec, size_t bytes_transferred) { 132 | boost::ignore_unused(bytes_transferred); 133 | if (ec) 134 | return fail_ws(ec, "read"); 135 | 136 | buffer_.clear(); 137 | ws_.async_read(buffer_, [this](beast::error_code ec, size_t n) { 138 | if (ec) 139 | return fail_ws(ec, "read"); 140 | message_handler(); 141 | buffer_.clear(); 142 | ws_.async_read(buffer_, COINBASE_HANDLER(on_message)); 143 | }); 144 | } 145 | 146 | void on_close(beast::error_code ec) { 147 | if (ec) 148 | return fail_ws(ec, "close"); 149 | 150 | std::cout << beast::make_printable(buffer_.data()) << std::endl; 151 | } 152 | 153 | 154 | void subscribe(const std::string& method, const std::string& market, const std::string& channel) 155 | { 156 | 157 | json payload = {{"type", method}, 158 | {"product_ids", {market}}, 159 | {"channels", {channel}}}; 160 | 161 | run(payload); 162 | } 163 | 164 | 165 | double get_agg_size_for_bids(const double& price){ 166 | double aggsize = 0; 167 | for(auto x : bids[price]){ 168 | aggsize += x.second["remaining_size"]; 169 | } 170 | return aggsize; 171 | } 172 | 173 | double get_agg_size_for_asks(const double& price){ 174 | double aggsize = 0; 175 | for(auto x : asks[price]){ 176 | aggsize += x.second["remaining_size"]; 177 | } 178 | return aggsize; 179 | } 180 | 181 | // level 3 orderbook messages 182 | void subscribe_orderbook_diffs(const std::string& method, const std::string& market) 183 | { 184 | json payload = {{"type", method}, 185 | {"product_ids", {market}}, 186 | {"channels", {"full"}}}; 187 | 188 | message_handler = [this](){ 189 | 190 | json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 191 | // std::cout << "payload : " << payload << std::endl; 192 | 193 | std::string side = payload["side"]; 194 | std::string msg_type = payload["type"]; 195 | std::string price_raw = payload["price"]; 196 | std::unordered_map order_dict; 197 | double price = std::stod(price_raw); 198 | double newfunds=0; 199 | double remaining_size=0; 200 | std::string order_id; 201 | double agg_size; 202 | bool is; 203 | 204 | if(payload.contains(payload["order_id"])) 205 | order_id = payload["order_id"]; 206 | else 207 | order_id = payload["maker_order_id"]; 208 | 209 | if(price_raw == "null") 210 | std::cout << "null" << std::endl; 211 | 212 | if(msg_type == "open"){ 213 | remaining_size = std::stod(payload["remaining_size"].get()); 214 | order_dict.insert({"remaining_size",remaining_size}); 215 | if(side == "buy"){ 216 | if(bids.find(price) != bids.end()) 217 | bids[price][order_id] = order_dict; // replacing yes 218 | else 219 | bids[price][order_id] = order_dict; // add new if not existing 220 | agg_size = get_agg_size_for_bids(price); // aggregating size by price 221 | is = diff_messages_queue.push(OrderBookMessage(true,price,agg_size,payload["sequence"])); 222 | } 223 | 224 | else{ 225 | if(asks.find(price) != asks.end()) 226 | asks[price][order_id] = order_dict; 227 | else 228 | asks[price][order_id] = order_dict; 229 | agg_size = get_agg_size_for_asks(price); 230 | is = diff_messages_queue.push(OrderBookMessage(false,price,agg_size,payload["sequence"])); 231 | } 232 | 233 | } 234 | 235 | else if(msg_type == "change"){ 236 | 237 | if(payload.contains(payload["new_size"])) 238 | remaining_size = std::stod(payload["remaining_size"].get()); 239 | else if(payload.contains(payload["new_funds"])){ 240 | newfunds = std::stod(payload["new_funds"].get()); 241 | remaining_size = newfunds / price; 242 | } 243 | else 244 | std::cout << "invalid diff message" << std::endl; 245 | 246 | if(side == "buy"){ 247 | if((bids.find(price) != bids.end()) && (bids[price].find(order_id) != bids[price].end())){ 248 | bids[price][order_id]["remaining_size"] = remaining_size; 249 | agg_size = get_agg_size_for_bids(price); 250 | is = diff_messages_queue.push(OrderBookMessage(true,price,agg_size,payload["sequence"])); 251 | } 252 | else 253 | std::cout << "empty update " << std::endl; 254 | } 255 | 256 | else{ 257 | if((asks.find(price) != asks.end()) && (asks[price].find(order_id) != asks[price].end())){ 258 | asks[price][order_id]["remaining_size"] = remaining_size; 259 | agg_size = get_agg_size_for_asks(price); 260 | is = diff_messages_queue.push(OrderBookMessage(false,price,agg_size,payload["sequence"])); 261 | } 262 | else 263 | std::cout << "empty update " << std::endl; 264 | 265 | } 266 | } 267 | 268 | else if(msg_type == "match"){ 269 | 270 | if(side == "buy"){ 271 | if((bids.find(price) != bids.end()) && (bids[price].find(order_id) != bids[price].end())){ 272 | double size = bids[price][order_id]["remaining_size"]; 273 | bids[price][order_id]["remaining_size"] = size - std::stod(payload["size"].get()); 274 | agg_size = get_agg_size_for_bids(price); 275 | is = diff_messages_queue.push(OrderBookMessage(true,price,agg_size,payload["sequence"])); 276 | } 277 | else 278 | std::cout << "empty update " << std::endl; 279 | } 280 | else{ 281 | if((asks.find(price) != asks.end()) && (asks[price].find(order_id) != asks[price].end())){ 282 | double size = asks[price][order_id]["remaining_size"]; 283 | asks[price][order_id]["remaining_size"] = size - std::stod(payload["size"].get()); 284 | agg_size = get_agg_size_for_asks(price); 285 | is = diff_messages_queue.push(OrderBookMessage(false,price,agg_size,payload["sequence"])); 286 | } 287 | else 288 | std::cout << "empty update" << std::endl; 289 | } 290 | 291 | } 292 | 293 | else if(msg_type == "done"){ 294 | if(side == "buy"){ 295 | if((bids.find(price) != bids.end()) && (bids[price].find(order_id) != bids[price].end())){ 296 | bids[price].erase(order_id); 297 | if(bids[price].size() < 1){ 298 | bids.erase(price); 299 | is = diff_messages_queue.push(OrderBookMessage(true,price,0.0,payload["sequence"])); 300 | } 301 | else{ 302 | agg_size = get_agg_size_for_bids(price); 303 | is = diff_messages_queue.push(OrderBookMessage(true,price,agg_size,payload["sequence"])); 304 | } 305 | } 306 | } 307 | else{ 308 | 309 | if((asks.find(price) != asks.end()) && (asks[price].find(order_id) != asks[price].end())){ 310 | asks[price].erase(order_id); 311 | if(asks[price].size() < 1){ 312 | asks.erase(price); 313 | is = diff_messages_queue.push(OrderBookMessage(false,price,0.0,payload["sequence"])); 314 | } 315 | else{ 316 | agg_size = get_agg_size_for_asks(price); 317 | is = diff_messages_queue.push(OrderBookMessage(false,price,agg_size,payload["sequence"])); 318 | } 319 | } 320 | } 321 | 322 | } 323 | else{ 324 | std::cout << "Invalid Message Type" << std::endl; 325 | } 326 | 327 | }; 328 | 329 | run(payload); 330 | } 331 | }; 332 | 333 | 334 | -------------------------------------------------------------------------------- /adapters/ftx/ftx-http.cpp: -------------------------------------------------------------------------------- 1 | #include "ftx-http.hpp" 2 | 3 | 4 | ftxAPI::ftxAPI(executor ex, ssl::context& ctxe, net::io_context &ioce) 5 | : resolver_(ex),stream_(ex, ctxe),ioc(ioce),ctx(ctxe){} 6 | 7 | 8 | http::response ftxAPI::http_call(boost::url url, http::verb action) 9 | { 10 | 11 | std::string const host(url.host()); 12 | std::string const service = url.has_port() 13 | ? url.port() 14 | : (url.scheme_id() == boost::urls::scheme::https) 15 | ? "https" 16 | : "http"; 17 | url.remove_origin(); 18 | 19 | 20 | if(! SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str())) 21 | { 22 | beast::error_code ec{static_cast(::ERR_get_error()), net::error::get_ssl_category()}; 23 | std::cerr << ec.message() << "\n"; 24 | } 25 | 26 | req_.method(action); 27 | req_.target(url.c_str()); 28 | req_.set(http::field::host, host); 29 | req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); 30 | req_.set("FTX-KEY",api_key); 31 | req_.set("FTX-TS",std::to_string(get_ms_timestamp(current_time()).count())); 32 | req_.set("FTX-SIGN",sign); 33 | 34 | req_.prepare_payload(); 35 | 36 | auto const results = resolver_.resolve(host, service); 37 | beast::get_lowest_layer(stream_).connect(results); 38 | 39 | stream_.handshake(ssl::stream_base::client); 40 | 41 | http::write(stream_, req_); 42 | http::read(stream_, buffer_, res_); 43 | beast::error_code ec; 44 | stream_.shutdown(ec); 45 | 46 | return res_; 47 | } 48 | 49 | std::string ftxAPI::authenticate(const char* key, const char* data) 50 | { 51 | unsigned char *result; 52 | static char res_hexstring[64]; 53 | int result_len = 32; 54 | std::string signature; 55 | 56 | result = HMAC(EVP_sha256(), key, strlen((char *)key), const_cast(reinterpret_cast(data)), strlen((char *)data), NULL, NULL); 57 | for (int i = 0; i < result_len; i++) { 58 | sprintf(&(res_hexstring[i * 2]), "%02x", result[i]); 59 | } 60 | 61 | for (int i = 0; i < 64; i++) { 62 | signature += res_hexstring[i]; 63 | } 64 | 65 | return signature; 66 | } 67 | 68 | void ftxAPI::configure(const std::string &api,const std::string &secret) 69 | { 70 | this->api_key = api; 71 | this->secret_key = secret; 72 | } 73 | 74 | json ftxAPI::list_markets() 75 | { 76 | boost::url method{"markets"}; 77 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 78 | } 79 | 80 | json ftxAPI::list_market(std::string market) 81 | { 82 | boost::url method{"markets/"+market}; 83 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 84 | } 85 | 86 | json ftxAPI::get_orderbook(std::string market, int depth) 87 | { 88 | boost::url method{"markets/"+market+"/orderbook"}; 89 | method.params().emplace_back("depth",std::to_string(depth)); 90 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 91 | } 92 | 93 | json ftxAPI::get_trades(std::string market) 94 | { 95 | boost::url method{"markets/"+market+"/trades"}; 96 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 97 | } 98 | 99 | json ftxAPI::list_future(std::string future) 100 | { 101 | boost::url method{"futures/"+future}; 102 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 103 | } 104 | 105 | json ftxAPI::future_stats(std::string future) 106 | { 107 | boost::url method{"futures/"+future+"/stats"}; 108 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 109 | } 110 | 111 | json ftxAPI::account_info(std::string future) 112 | { 113 | boost::url method{"account"}; 114 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "GET" + "/api/account"; 115 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 116 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 117 | } 118 | 119 | json ftxAPI::closed_positions() 120 | { 121 | boost::url method{"positions"}; 122 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "GET" + "/api/positions"; 123 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 124 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 125 | } 126 | 127 | json ftxAPI::open_orders() 128 | { 129 | boost::url method{"orders"}; 130 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "GET" + "/api/orders"; 131 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 132 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 133 | } 134 | 135 | json ftxAPI::open_orders(std::string market) 136 | { 137 | boost::url method{"orders"}; 138 | method.params().emplace_back("market",market); 139 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "GET" + "/api/orders?market="+ market; 140 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 141 | return json::parse(http_call(make_url(base_api,method),http::verb::get).body()); 142 | } 143 | 144 | json ftxAPI::place_order(std::string market, std::string side, double size,bool ioc,bool post_only,bool reduce_only) 145 | { 146 | boost::url method{"orders"}; 147 | 148 | json payload = {{"market", market}, 149 | {"side", side}, 150 | {"price", NULL}, 151 | {"type", "market"}, 152 | {"size", size}, 153 | {"reduceOnly", reduce_only}, 154 | {"ioc", ioc}, 155 | {"postOnly", post_only}}; 156 | 157 | req_.body() = payload.dump(); 158 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "POST" + "/api/orders" + payload.dump(); 159 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 160 | return json::parse(http_call(make_url(base_api,method),http::verb::post).body()); 161 | } 162 | 163 | json ftxAPI::place_order(std::string market, std::string side,double price, double size,bool ioc,bool post_only,bool reduce_only) 164 | { 165 | boost::url method{"orders"}; 166 | 167 | json payload = {{"market", market}, 168 | {"side", side}, 169 | {"price", price}, 170 | {"type", "limit"}, 171 | {"size", size}, 172 | {"reduceOnly", reduce_only}, 173 | {"ioc", ioc}, 174 | {"postOnly", post_only}}; 175 | 176 | req_.body() = payload.dump(); 177 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "POST" + "/api/orders" + payload.dump(); 178 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 179 | return json::parse(http_call(make_url(base_api,method),http::verb::post).body()); 180 | } 181 | 182 | json ftxAPI::cancel_order(int orderid) 183 | { 184 | boost::url method{"orders/"+std::to_string(orderid)}; 185 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "DELETE" + "/api/orders/"+std::to_string(orderid); 186 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 187 | return json::parse(http_call(make_url(base_api,method),http::verb::delete_).body()); 188 | } 189 | 190 | json ftxAPI::cancel_all_orders(std::string market) 191 | { 192 | boost::url method{"orders"}; 193 | json payload = {{"market",market}}; 194 | req_.body() = payload.dump(); 195 | std::string data = std::to_string(get_ms_timestamp(current_time()).count()) + "DELETE" + "/api/orders"+payload.dump(); 196 | sign = getHmacSha256(secret_key.c_str(),data.c_str()); 197 | return json::parse(http_call(make_url(base_api,method),http::verb::delete_).body()); 198 | } 199 | 200 | -------------------------------------------------------------------------------- /adapters/ftx/ftx-http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.hpp" 13 | #include 14 | 15 | namespace beast = boost::beast; 16 | namespace http = beast::http; 17 | namespace net = boost::asio; 18 | namespace ssl = boost::asio::ssl; 19 | using tcp = boost::asio::ip::tcp; 20 | 21 | using json = nlohmann::json; 22 | using executor = net::any_io_executor; 23 | 24 | class ftxAPI : public std::enable_shared_from_this 25 | { 26 | 27 | private: 28 | 29 | void configure(const std::string &api,const std::string &secret); 30 | boost::url base_api{"https://ftx.com/api/"}; 31 | tcp::resolver resolver_; 32 | beast::ssl_stream stream_; 33 | beast::flat_buffer buffer_; 34 | http::request req_; 35 | http::response res_; 36 | std::string api_key = "XCBZEitcljAjeCH6ZxkEdn7ngWrMMr8ytTp4GAiX"; 37 | std::string secret_key = "GKPAz77T2X54MQH3ICe379ZFxS_Si9qbxzJtzQ5I"; 38 | net::io_context& ioc; 39 | ssl::context& ctx; 40 | std::string sign; 41 | 42 | public: 43 | 44 | explicit ftxAPI(executor ex, ssl::context& ctx,net::io_context& ioc); 45 | 46 | http::response http_call(boost::url url, http::verb action); 47 | 48 | std::string authenticate(const char* key, const char* data); 49 | 50 | json list_markets(); 51 | 52 | json list_market(std::string market); 53 | 54 | json get_orderbook(std::string market,int depth); 55 | 56 | json get_trades(std::string market); 57 | 58 | json list_future(std::string future); 59 | 60 | json future_stats(std::string future); 61 | 62 | json account_info(std::string future); 63 | 64 | json closed_positions(); 65 | 66 | json open_orders(); 67 | 68 | json open_orders(std::string market); 69 | 70 | json place_order(std::string market, std::string side, double size,bool ioc,bool post_only,bool reduce_only); 71 | 72 | json cancel_order(int orderid); 73 | 74 | json cancel_all_orders(std::string market); 75 | 76 | json place_order(std::string market, std::string side,double price, double size,bool ioc,bool post_only,bool reduce_only); 77 | }; 78 | -------------------------------------------------------------------------------- /adapters/ftx/ftx-ws.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include "Ringbuffer.hpp" 16 | #include "types.hpp" 17 | namespace beast = boost::beast; 18 | namespace http = beast::http; 19 | namespace websocket = beast::websocket; 20 | namespace net = boost::asio; 21 | namespace ssl = net::ssl; 22 | using beast::error_code; 23 | 24 | using tcp = net::ip::tcp; 25 | using json = nlohmann::json; 26 | 27 | using TCPStream = beast::tcp_stream; 28 | using SSLStream = beast::ssl_stream; 29 | using Stream = websocket::stream; 30 | 31 | using namespace std::chrono_literals; 32 | void fail_ws(beast::error_code ec, char const* what); 33 | #define FTX_HANDLER(d) beast::bind_front_handler(&ftxWS::d, this->shared_from_this()) 34 | 35 | 36 | 37 | class ftxWS : public std::enable_shared_from_this 38 | { 39 | tcp::resolver resolver_; 40 | Stream ws_; 41 | beast::flat_buffer buffer_; 42 | std::string message_text_; 43 | char const* host = "ftx.com"; 44 | std::string wsTarget_ = "/ws/"; 45 | std::string host_; 46 | SPSCQueue &diff_messages_queue; 47 | std::function on_message_handler; 48 | 49 | public: 50 | 51 | ftxWS(net::any_io_executor ex, ssl::context& ctx, SPSCQueue& q) 52 | : resolver_(ex) 53 | , ws_(ex, ctx) 54 | , diff_messages_queue(q) {} 55 | 56 | void run(json message) { 57 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host)) { 58 | throw boost::system::system_error( 59 | error_code(::ERR_get_error(), net::error::get_ssl_category())); 60 | } 61 | host_ = host; 62 | message_text_ = message.dump(); 63 | 64 | resolver_.async_resolve(host_, "443", FTX_HANDLER(on_resolve)); 65 | } 66 | 67 | void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { 68 | if (ec) 69 | return fail_ws(ec, "resolve"); 70 | 71 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host_.c_str())) { 72 | throw beast::system_error{ 73 | error_code(::ERR_get_error(), net::error::get_ssl_category())}; 74 | } 75 | 76 | get_lowest_layer(ws_).expires_after(30s); 77 | 78 | beast::get_lowest_layer(ws_).async_connect(results, FTX_HANDLER(on_connect)); 79 | } 80 | 81 | void on_connect(beast::error_code ec, 82 | [[maybe_unused]] tcp::resolver::results_type::endpoint_type ep) { 83 | if (ec) 84 | return fail_ws(ec, "connect"); 85 | 86 | // Perform the SSL handshake 87 | ws_.next_layer().async_handshake(ssl::stream_base::client, FTX_HANDLER(on_ssl_handshake)); 88 | } 89 | 90 | void on_ssl_handshake(beast::error_code ec) { 91 | if (ec) 92 | return fail_ws(ec, "ssl_handshake"); 93 | 94 | beast::get_lowest_layer(ws_).expires_never(); 95 | 96 | ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); 97 | 98 | ws_.set_option(websocket::stream_base::decorator([](websocket::request_type& req) { 99 | req.set(http::field::user_agent, 100 | std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async"); 101 | })); 102 | 103 | std::cout << "using host_: " << host_ << std::endl; 104 | ws_.async_handshake(host_, wsTarget_, FTX_HANDLER(on_handshake)); 105 | } 106 | 107 | void on_handshake(beast::error_code ec) { 108 | if (ec) { 109 | return fail_ws(ec, "handshake"); 110 | } 111 | 112 | std::cout << "Sending : " << message_text_ << std::endl; 113 | 114 | ws_.async_write(net::buffer(message_text_), FTX_HANDLER(on_write)); 115 | } 116 | 117 | void on_write(beast::error_code ec, size_t bytes_transferred) { 118 | boost::ignore_unused(bytes_transferred); 119 | 120 | if (ec) 121 | return fail_ws(ec, "write"); 122 | 123 | ws_.async_read(buffer_, FTX_HANDLER(on_message)); 124 | } 125 | 126 | void on_message(beast::error_code ec, size_t bytes_transferred) { 127 | 128 | boost::ignore_unused(bytes_transferred); 129 | if (ec) 130 | return fail_ws(ec, "read"); 131 | 132 | 133 | buffer_.clear(); 134 | ws_.async_read(buffer_, [this](beast::error_code ec, size_t n) { 135 | if (ec) 136 | return fail_ws(ec, "read"); 137 | on_message_handler(); 138 | buffer_.clear(); 139 | ws_.async_read(buffer_, FTX_HANDLER(on_message)); 140 | }); 141 | 142 | } 143 | 144 | void on_close(beast::error_code ec) { 145 | if (ec) 146 | return fail_ws(ec, "close"); 147 | 148 | std::cout << beast::make_printable(buffer_.data()) << std::endl; 149 | } 150 | 151 | // provides the latest best bid and offer market data 152 | 153 | void subscribe_ticker(const std::string& action,const std::string& symbol) 154 | { 155 | json jv = { 156 | { "op", action }, 157 | { "channel", "ticker" }, 158 | { "market", symbol } 159 | }; 160 | run(jv); 161 | } 162 | 163 | 164 | void subscribe_orderbook_diffs(const std::string& action,const std::string& symbol) 165 | { 166 | json jv = { 167 | { "op", action }, 168 | { "channel", "orderbook" }, 169 | { "market", symbol } 170 | }; 171 | 172 | on_message_handler = [this](){ 173 | 174 | json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 175 | 176 | // bool is; 177 | // if(payload["data"]["action"] == "update" && payload.contains(payload["data"]["sells"])){ // error 178 | 179 | // for(auto x : payload["data"]["asks"]){ 180 | // OrderBookMessage level; 181 | // level.is_bid = false; 182 | // level.price = std::stod(x[0].get()); 183 | // level.quantity = std::stod(x[1].get()); 184 | // is = diff_messages_queue.push(level); 185 | // } 186 | // } 187 | 188 | // if(payload["data"]["action"] == "update" && payload.contains(payload["data"]["bids"])){ 189 | // for(auto x : payload["data"]["bids"]){ 190 | // OrderBookMessage level; 191 | // level.is_bid = true; 192 | // level.price = std::stod(x[0].get()); 193 | // level.quantity = std::stod(x[1].get()); 194 | // is = diff_messages_queue.push(level); 195 | // } 196 | // } 197 | 198 | }; 199 | 200 | 201 | run(jv); 202 | } 203 | 204 | 205 | void subscribe_trades(const std::string& action,const std::string& symbol) 206 | { 207 | json jv = { 208 | { "op", action }, 209 | { "channel", "trades" }, 210 | { "market", symbol } 211 | }; 212 | run(jv); 213 | } 214 | 215 | 216 | }; 217 | -------------------------------------------------------------------------------- /adapters/kraken/kraken-http.cpp: -------------------------------------------------------------------------------- 1 | #include "kraken-http.hpp" 2 | 3 | #include "coinbase-http.hpp" 4 | 5 | KrakenAPI::KrakenAPI(executor ex, ssl::context &ctxe, net::io_context &ioce) 6 | : resolver_(ex), stream_(ex, ctxe), ioc(ioce), ctx(ctxe) 7 | { 8 | PostDecodeString = decode64(this->secret_key); 9 | } 10 | 11 | http::response KrakenAPI::http_call(boost::url url, http::verb action) 12 | { 13 | 14 | std::string const host(url.host()); 15 | 16 | std::string const service = "https"; 17 | url.remove_origin(); 18 | url.remove_origin(); 19 | 20 | SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()); 21 | 22 | req_.method(action); 23 | req_.target(url.c_str()); 24 | req_.set(http::field::host, host); 25 | req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); 26 | req_.set("API-Key", api_key); 27 | req_.set("Content-Type", "application/x-www-form-urlencoded"); 28 | req_.prepare_payload(); 29 | std::cout << "raw request : " << req_ << std::endl; 30 | 31 | auto const results = resolver_.resolve(host, service); 32 | beast::get_lowest_layer(stream_).connect(results); 33 | 34 | stream_.handshake(ssl::stream_base::client); 35 | 36 | http::write(stream_, req_); 37 | http::read(stream_, buffer_, res_); 38 | std::cout << "response : " << res_.body() << std::endl; 39 | std::cout << "raw response : " << res_ << std::endl; 40 | 41 | beast::error_code ec; 42 | stream_.shutdown(ec); 43 | 44 | return res_; 45 | } 46 | 47 | void KrakenAPI::configure(const std::string &api, const std::string &secret) 48 | { 49 | this->api_key = api; 50 | this->secret_key = secret; 51 | } 52 | 53 | // interval in minutes 54 | json KrakenAPI::get_ohlc(std::string pair, std::string interval) 55 | { 56 | boost::url method{"public/OHLC"}; 57 | method.params().emplace_back("pair", pair); 58 | method.params().emplace_back("interval", interval); 59 | return json::parse(http_call(make_url(base_api, method), http::verb::get).body()); 60 | } 61 | 62 | json KrakenAPI::get_orderbook(std::string pair, std::string levels) 63 | { 64 | boost::url method{"public/Depth"}; 65 | method.params().emplace_back("pair", pair); 66 | method.params().emplace_back("count", levels); 67 | return json::parse(http_call(make_url(base_api, method), http::verb::get).body()); 68 | } 69 | 70 | json KrakenAPI::get_trades(std::string pair, std::string sinceTime) 71 | { 72 | boost::url method{"public/Depth"}; 73 | method.params().emplace_back("pair", pair); 74 | method.params().emplace_back("since", sinceTime); 75 | return json::parse(http_call(make_url(base_api, method), http::verb::get).body()); 76 | } 77 | 78 | json KrakenAPI::submit_market_order(std::string action, std::string pair, double size) 79 | { 80 | boost::url method{"private/AddOrder"}; 81 | long nonce = generate_nonce(); 82 | std::string postdata1 = "nonce=" + std::to_string(nonce) + "&ordertype=market&type=" + action + 83 | "&volume=" + std::to_string(size) + "&pair=" + pair; 84 | std::string signature = krak_signature("/0/private/AddOrder", std::to_string(nonce), postdata1, secret_key); 85 | req_.set("API-Sign", signature); 86 | req_.body() = postdata1; 87 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 88 | } 89 | 90 | json KrakenAPI::submit_limit_order(std::string action, std::string pair, double size, double price) 91 | { 92 | boost::url method{"private/AddOrder"}; 93 | long nonce = generate_nonce(); 94 | std::string postdata1 = "nonce=" + std::to_string(nonce) + "&ordertype=limit&type=" + action + 95 | "&volume=" + std::to_string(size) + "&pair=" + pair + "&price=" + std::to_string(price); 96 | std::string signature = krak_signature("/0/private/AddOrder", std::to_string(nonce), postdata1, secret_key); 97 | req_.set("API-Sign", signature); 98 | req_.body() = postdata1; 99 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 100 | } 101 | 102 | json KrakenAPI::cancel_order(int id) 103 | { 104 | boost::url method{"private/CancelOrder"}; 105 | long nonce = generate_nonce(); 106 | std::string postdata1 = "nonce=" + std::to_string(nonce) + "&txid=" + std::to_string(id); 107 | std::string signature = krak_signature("/0/private/CancelOrder", std::to_string(nonce), postdata1, secret_key); 108 | req_.set("API-Sign", signature); 109 | req_.body() = postdata1; 110 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 111 | } 112 | 113 | json KrakenAPI::cancel_all_orders() 114 | { 115 | boost::url method{"private/CancelOrder"}; 116 | long nonce = generate_nonce(); 117 | std::string postdata1 = "nonce=" + std::to_string(nonce); 118 | std::string signature = krak_signature("/0/private/CancelAll", std::to_string(nonce), postdata1, secret_key); 119 | req_.set("API-Sign", signature); 120 | req_.body() = postdata1; 121 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 122 | } 123 | 124 | json KrakenAPI::get_account_balance() 125 | { 126 | boost::url method{"private/Balance"}; 127 | long nonce = generate_nonce(); 128 | std::string postdata1 = "nonce=" + std::to_string(nonce); 129 | std::string signature = krak_signature("/0/private/Balance", std::to_string(nonce), postdata1, secret_key); 130 | req_.set("API-Sign", signature); 131 | req_.body() = postdata1; 132 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 133 | } 134 | 135 | json KrakenAPI::get_open_orders() 136 | { 137 | boost::url method{"private/OpenOrders"}; 138 | long nonce = generate_nonce(); 139 | std::string postdata1 = "nonce=" + std::to_string(nonce); 140 | std::string signature = krak_signature("/0/private/OpenOrders", std::to_string(nonce), postdata1, secret_key); 141 | req_.set("API-Sign", signature); 142 | req_.body() = postdata1; 143 | return json::parse(http_call(make_url(base_api, method), http::verb::post).body()); 144 | } -------------------------------------------------------------------------------- /adapters/kraken/kraken-http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.hpp" 13 | #include 14 | 15 | namespace beast = boost::beast; 16 | namespace http = beast::http; 17 | namespace net = boost::asio; 18 | namespace ssl = boost::asio::ssl; 19 | using tcp = boost::asio::ip::tcp; 20 | 21 | using json = nlohmann::json; 22 | using ordered_json = nlohmann::ordered_json; 23 | using executor = net::any_io_executor; 24 | 25 | class KrakenAPI : public std::enable_shared_from_this 26 | { 27 | 28 | private: 29 | 30 | void configure(const std::string &api, const std::string& secret); 31 | boost::url base_api{"https://api.kraken.com/0/"}; 32 | std::string secret_key = "81INfYCAqThM8STjYuAGWoSCM+XAVyYPi/VxmQW+KfHWu1UxVqG/MosX9X4zNRnkFh1z4B2zsJvdOmV08fTMHg=="; 33 | std::string PostDecodeString; 34 | tcp::resolver resolver_; 35 | beast::ssl_stream stream_; 36 | beast::flat_buffer buffer_; 37 | http::request req_; 38 | http::response res_; 39 | std::string api_key = "Skz2FPOvhvvYrOHi6qMEqmmzq4tj3XrrDE5Zwxs/5/tfhdWCM3VAuOXp"; 40 | net::io_context& ioc; 41 | ssl::context& ctx; 42 | std::string sign; 43 | 44 | public: 45 | 46 | explicit KrakenAPI(executor ex, ssl::context& ctx,net::io_context& ioc); 47 | 48 | http::response http_call(boost::url url, http::verb action); 49 | 50 | json get_ohlc(std::string pair, std::string interval); 51 | 52 | json get_orderbook(std::string pair, std::string levels); 53 | 54 | // default = last 1000 trades 55 | json get_trades(std::string pair, std::string sinceTime); 56 | 57 | json submit_limit_order(std::string action,std::string pair,double size,double price); 58 | 59 | json submit_market_order(std::string action,std::string pair,double size); 60 | 61 | json cancel_order(int id); 62 | 63 | json cancel_all_orders(); 64 | 65 | json get_account_balance(); 66 | 67 | json get_open_orders(); 68 | 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /adapters/kraken/kraken-ws.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "Ringbuffer.hpp" 18 | #include "types.hpp" 19 | 20 | namespace beast = boost::beast; 21 | namespace http = beast::http; 22 | namespace websocket = beast::websocket; 23 | namespace net = boost::asio; 24 | namespace ssl = net::ssl; 25 | using beast::error_code; 26 | 27 | using tcp = net::ip::tcp; 28 | using json = nlohmann::json; 29 | 30 | using TCPStream = beast::tcp_stream; 31 | using SSLStream = beast::ssl_stream; 32 | using Stream = websocket::stream; 33 | 34 | using namespace std::chrono_literals; 35 | void fail_ws(beast::error_code ec, char const* what); 36 | #define KRAKEN_HANDLER(z) beast::bind_front_handler(&krakenWS::z, this->shared_from_this()) 37 | 38 | 39 | class krakenWS : public std::enable_shared_from_this 40 | { 41 | tcp::resolver resolver_; 42 | Stream ws_; 43 | beast::flat_buffer buffer_; 44 | std::string message_text_; 45 | char const* host = "ws.kraken.com"; 46 | std::string wsTarget_ = "/ws/"; 47 | std::string host_; 48 | SPSCQueue &diff_messages_queue; 49 | std::function message_handler; 50 | 51 | public: 52 | 53 | krakenWS(net::any_io_executor ex, ssl::context& ctx, SPSCQueue& q) 54 | : resolver_(ex) 55 | , ws_(ex, ctx) 56 | , diff_messages_queue(q) {} 57 | 58 | void run(json message) { 59 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host)) { 60 | throw boost::system::system_error( 61 | error_code(::ERR_get_error(), net::error::get_ssl_category())); 62 | } 63 | host_ = host; 64 | message_text_ = message.dump(); 65 | 66 | resolver_.async_resolve(host_, "443", KRAKEN_HANDLER(on_resolve)); 67 | } 68 | 69 | void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { 70 | if (ec) 71 | return fail_ws(ec, "resolve"); 72 | 73 | if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host_.c_str())) { 74 | throw beast::system_error{ 75 | error_code(::ERR_get_error(), net::error::get_ssl_category())}; 76 | } 77 | 78 | get_lowest_layer(ws_).expires_after(30s); 79 | 80 | beast::get_lowest_layer(ws_).async_connect(results, KRAKEN_HANDLER(on_connect)); 81 | } 82 | 83 | void on_connect(beast::error_code ec,[[maybe_unused]] tcp::resolver::results_type::endpoint_type ep) { 84 | if (ec) 85 | return fail_ws(ec, "connect"); 86 | 87 | // Perform the SSL handshake 88 | ws_.next_layer().async_handshake(ssl::stream_base::client, KRAKEN_HANDLER(on_ssl_handshake)); 89 | } 90 | 91 | void on_ssl_handshake(beast::error_code ec) { 92 | if (ec) 93 | return fail_ws(ec, "ssl_handshake"); 94 | 95 | beast::get_lowest_layer(ws_).expires_never(); 96 | 97 | ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); 98 | 99 | ws_.set_option(websocket::stream_base::decorator([](websocket::request_type& req) { 100 | req.set(http::field::user_agent, 101 | std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async"); 102 | })); 103 | 104 | std::cout << "using host_: " << host_ << std::endl; 105 | ws_.async_handshake(host_, wsTarget_, KRAKEN_HANDLER(on_handshake)); 106 | } 107 | 108 | void on_handshake(beast::error_code ec) { 109 | if (ec) { 110 | return fail_ws(ec, "handshake"); 111 | } 112 | 113 | std::cout << "Sending : " << message_text_ << std::endl; 114 | 115 | ws_.async_write(net::buffer(message_text_), KRAKEN_HANDLER(on_write)); 116 | } 117 | 118 | void on_write(beast::error_code ec, size_t bytes_transferred) { 119 | boost::ignore_unused(bytes_transferred); 120 | 121 | if (ec) 122 | return fail_ws(ec, "write"); 123 | 124 | ws_.async_read(buffer_, KRAKEN_HANDLER(on_message)); 125 | } 126 | 127 | void on_message(beast::error_code ec, size_t bytes_transferred) { 128 | 129 | boost::ignore_unused(bytes_transferred); 130 | if (ec) 131 | return fail_ws(ec, "read"); 132 | 133 | 134 | buffer_.clear(); 135 | ws_.async_read(buffer_, [this](beast::error_code ec, size_t n) { 136 | if (ec) 137 | return fail_ws(ec, "read"); 138 | message_handler(); 139 | buffer_.clear(); 140 | ws_.async_read(buffer_, KRAKEN_HANDLER(on_message)); 141 | }); 142 | 143 | } 144 | 145 | void on_close(beast::error_code ec) { 146 | if (ec) 147 | return fail_ws(ec, "close"); 148 | 149 | std::cout << beast::make_printable(buffer_.data()) << std::endl; 150 | } 151 | 152 | 153 | void subscribe_trades(const std::string& action,const std::string& pair) 154 | { 155 | json payload = {{"event", action}, 156 | {"pair", {pair}}}; 157 | 158 | payload["subscription"]["name"] = "trade"; 159 | run(payload); 160 | } 161 | 162 | 163 | void subscribe_ticker(const std::string& action,const std::string& pair) 164 | { 165 | 166 | json payload = {{"event", action}, 167 | {"pair", {pair}}}; 168 | 169 | payload["subscription"]["name"] = "ticker"; 170 | run(payload); 171 | } 172 | 173 | // valid levels options : 10,25,100,500,1000 174 | void subscribe_orderbook_diffs(const std::string& action,const std::string& pair, int levels) 175 | { 176 | 177 | json payload = {{"event", action}, 178 | {"pair", {pair}}}; 179 | // add handling snapshot message first 180 | message_handler = [this](){ 181 | 182 | json payload = json::parse(beast::buffers_to_string(buffer_.cdata())); 183 | bool is; 184 | 185 | if(payload.is_array()){ 186 | // No need update-id, reliability of the orderbook is checked using checksum 187 | for(auto x: payload[1]["a"]) 188 | is = diff_messages_queue.push(OrderBookMessage(false,std::stod(x[0].get()),std::stod(x[1].get()),0)); 189 | 190 | 191 | for(auto x: payload[1]["b"]) 192 | is = diff_messages_queue.push(OrderBookMessage(true,std::stod(x[0].get()),std::stod(x[1].get()),0)); 193 | 194 | } 195 | 196 | }; 197 | 198 | payload["subscription"]["name"] = "book"; 199 | payload["subscription"]["depth"] = 10; 200 | run(payload); 201 | } 202 | 203 | }; 204 | -------------------------------------------------------------------------------- /adapters/utils/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.hpp" 2 | 3 | 4 | 5 | TimePoint current_time() 6 | { 7 | return Clock::now(); 8 | } 9 | 10 | std::chrono::milliseconds get_ms_timestamp(TimePoint time) 11 | { 12 | return std::chrono::duration_cast(time.time_since_epoch()); 13 | } 14 | 15 | std::chrono::seconds get_sec_timestamp(TimePoint time) 16 | { 17 | return std::chrono::duration_cast(time.time_since_epoch()); 18 | } 19 | 20 | void fail_http(beast::error_code ec, char const* what) 21 | { 22 | std::cerr << what << ": " << ec.message() << "\n"; 23 | } 24 | 25 | boost::url make_url(boost::url base_api, boost::url method){ 26 | assert(!method.is_path_absolute()); 27 | assert(base_api.data()[base_api.size() - 1] == '/'); 28 | 29 | boost::urls::error_code ec; 30 | boost::url url; 31 | resolve(base_api, method, url, ec); 32 | if (ec) 33 | throw boost::system::system_error(ec); 34 | return url; 35 | } 36 | 37 | void fail_ws(beast::error_code ec, char const* what) 38 | { 39 | std::cerr << what << ": " << ec.message() << "\n"; 40 | } 41 | 42 | std::string encode64(std::string &val) { 43 | using namespace boost::archive::iterators; 44 | using It = base64_from_binary>; 45 | auto tmp = std::string(It(std::begin(val)), It(std::end(val))); 46 | return tmp.append((3 - val.size() % 3) % 3, '='); 47 | } 48 | 49 | std::string decode64(const std::string &val) { 50 | using namespace boost::archive::iterators; 51 | using It = transform_width, 8, 6>; 52 | return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) { 53 | return c == '\0'; 54 | }); 55 | } 56 | 57 | 58 | std::string removeDecimalAndLeadingZeros(std::string str) { 59 | // Remove the decimal point 60 | str.erase(std::remove(str.begin(), str.end(), '.'), str.end()); 61 | 62 | // Remove leading zeros 63 | while (str.length() > 1 && str[0] == '0') { 64 | str.erase(0, 1); 65 | } 66 | 67 | return str; 68 | } 69 | 70 | // there is compiler instrinsics for checksum32 calculation as part of SSE4 instruction set, will refer later for more optimizations 71 | uint32_t checksum32(const std::string &str){ 72 | boost::crc_32_type result; 73 | result.process_bytes(str.data(), str.length()); 74 | 75 | // Cast the checksum to an unsigned 32-bit integer 76 | uint32_t checksum = result.checksum(); 77 | 78 | return checksum; 79 | } 80 | 81 | std::string coinbase_HmacSha256(const char* key, const char* data) 82 | { 83 | unsigned char *result; 84 | static char res_hexstring[64]; 85 | int result_len = 32; 86 | std::string signature; 87 | 88 | result = HMAC(EVP_sha256(), key, strlen((char *)key), const_cast(reinterpret_cast(data)), strlen((char *)data), NULL, NULL); 89 | const char *preEncodeSignature_c = strdup(reinterpret_cast(result)); 90 | std::string preEncodeSignature(preEncodeSignature_c); 91 | 92 | std::string postEncodeSignature = encode64(preEncodeSignature); 93 | 94 | return postEncodeSignature; 95 | } 96 | 97 | std::string getHmacSha256(const char* key, const char* data) 98 | { 99 | unsigned char *result; 100 | static char res_hexstring[64]; 101 | int result_len = 32; 102 | std::string signature; 103 | 104 | result = HMAC(EVP_sha256(), key, strlen((char *)key), const_cast(reinterpret_cast(data)), strlen((char *)data), NULL, NULL); 105 | for (int i = 0; i < result_len; i++) { 106 | sprintf(&(res_hexstring[i * 2]), "%02x", result[i]); 107 | } 108 | 109 | for (int i = 0; i < 64; i++) { 110 | signature += res_hexstring[i]; 111 | } 112 | 113 | return signature; 114 | } 115 | 116 | std::string getHmacSha384(std::string &key, std::string &content) 117 | { 118 | unsigned char *result; 119 | int result_len = 48; 120 | std::string digest; 121 | std::string hex_digest; 122 | 123 | result = HMAC(EVP_sha384(), key.data(), key.size(), 124 | reinterpret_cast(content.data()), 125 | content.size(), NULL, NULL); 126 | 127 | // Generate a string of hexadecimal digits from the MAC 128 | std::stringstream ss; 129 | for (int i = 0; i < result_len; i++) { 130 | ss << std::hex << std::setw(2) << std::setfill('0') << (int)result[i]; 131 | } 132 | hex_digest = ss.str(); 133 | 134 | return hex_digest; 135 | } 136 | 137 | std::string encode_url(std::string input) { 138 | std::stringstream encoded; 139 | for (const char& c : input) { 140 | if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { 141 | encoded << c; 142 | } else { 143 | encoded << '%' << std::setw(2) << std::setfill('0') << std::uppercase << std::hex << static_cast(c); 144 | } 145 | } 146 | return encoded.str(); 147 | } 148 | 149 | long generate_nonce() { 150 | return std::chrono::system_clock::now().time_since_epoch() / 1ms; 151 | } 152 | 153 | 154 | 155 | // temp test 156 | 157 | std::string krak_signature(std::string path, std::string nonce, const std::string postdata, std::string secret) 158 | { 159 | // add path to data to encrypt 160 | std::vector data(path.begin(), path.end()); 161 | 162 | // concatenate nonce and postdata and compute SHA256 163 | std::vector nonce_postdata = sha256(nonce + postdata); 164 | 165 | // concatenate path and nonce_postdata (path + sha256(nonce + postdata)) 166 | data.insert(data.end(), nonce_postdata.begin(), nonce_postdata.end()); 167 | 168 | // and compute HMAC 169 | return b64_encode( hmac_sha512(data, b64_decode(secret)) ); 170 | } 171 | 172 | std::vector sha256(const std::string& data) 173 | { 174 | std::vector digest(SHA256_DIGEST_LENGTH); 175 | 176 | SHA256_CTX ctx; 177 | SHA256_Init(&ctx); 178 | SHA256_Update(&ctx, data.c_str(), data.length()); 179 | SHA256_Final(digest.data(), &ctx); 180 | 181 | return digest; 182 | } 183 | 184 | std::vector b64_decode(const std::string& data) 185 | { 186 | BIO* b64 = BIO_new(BIO_f_base64()); 187 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 188 | 189 | BIO* bmem = BIO_new_mem_buf((void*)data.c_str(),data.length()); 190 | bmem = BIO_push(b64, bmem); 191 | 192 | std::vector output(data.length()); 193 | int decoded_size = BIO_read(bmem, output.data(), output.size()); 194 | BIO_free_all(bmem); 195 | 196 | if (decoded_size < 0) 197 | throw std::runtime_error("failed while decoding base64."); 198 | 199 | return output; 200 | } 201 | 202 | std::string b64_encode(const std::vector& data) 203 | { 204 | BIO* b64 = BIO_new(BIO_f_base64()); 205 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 206 | 207 | BIO* bmem = BIO_new(BIO_s_mem()); 208 | b64 = BIO_push(b64, bmem); 209 | 210 | BIO_write(b64, data.data(), data.size()); 211 | BIO_flush(b64); 212 | 213 | BUF_MEM* bptr = NULL; 214 | BIO_get_mem_ptr(b64, &bptr); 215 | 216 | std::string output(bptr->data, bptr->length); 217 | BIO_free_all(b64); 218 | 219 | return output; 220 | } 221 | 222 | std::vector hmac_sha512(const std::vector& data, const std::vector& key) 223 | { 224 | unsigned int len = EVP_MAX_MD_SIZE; 225 | std::vector digest(len); 226 | 227 | HMAC_CTX *ctx = HMAC_CTX_new(); 228 | if (ctx == NULL) { 229 | throw std::runtime_error("cannot create HMAC_CTX"); 230 | } 231 | 232 | HMAC_Init_ex(ctx, key.data(), key.size(), EVP_sha512(), NULL); 233 | HMAC_Update(ctx, data.data(), data.size()); 234 | HMAC_Final(ctx, digest.data(), &len); 235 | 236 | HMAC_CTX_free(ctx); 237 | 238 | return digest; 239 | } -------------------------------------------------------------------------------- /adapters/utils/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | namespace beast = boost::beast; 14 | 15 | using namespace std::chrono; 16 | using std::chrono::high_resolution_clock; 17 | using std::chrono::duration_cast; 18 | using std::chrono::duration; 19 | using std::chrono::milliseconds; 20 | using Clock = std::chrono::system_clock; 21 | using TimePoint = std::chrono::time_point; 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace boost::archive::iterators; 31 | 32 | 33 | 34 | 35 | TimePoint current_time(); 36 | 37 | std::chrono::milliseconds get_ms_timestamp(TimePoint time); 38 | 39 | std::chrono::seconds get_sec_timestamp(TimePoint time); 40 | 41 | boost::url make_url(boost::url base_api, boost::url method); 42 | 43 | void fail_http(beast::error_code ec, char const* what); 44 | 45 | void fail_ws(beast::error_code ec, char const* what); 46 | 47 | std::string encode64(std::string &val); 48 | 49 | std::string decode64(const std::string &val); 50 | 51 | std::string removeDecimalAndLeadingZeros(std::string str); 52 | 53 | uint32_t checksum32(const std::string &str); 54 | 55 | std::string getHmacSha256(const char* key, const char* data); 56 | 57 | std::string coinbase_HmacSha256(const char* key, const char* data); 58 | 59 | long generate_nonce(); 60 | 61 | std::string getHmacSha384(std::string &key,std::string &content); 62 | 63 | std::string encode_url(std::string input); 64 | 65 | 66 | #include 67 | #include 68 | #include 69 | #include 70 | 71 | std::vector hmac_sha512(const std::vector& data, const std::vector& key); 72 | std::string b64_encode(const std::vector& data); 73 | std::vector b64_decode(const std::string& data); 74 | std::vector sha256(const std::string& data); 75 | std::string krak_signature(std::string path, std::string nonce, std::string postdata, std::string secret); 76 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cd build; make -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cmake -S . -B build -------------------------------------------------------------------------------- /features/TapeSpeedIndicator.cpp: -------------------------------------------------------------------------------- 1 | #include "TapeSpeedIndicator.hpp" 2 | 3 | TapeSpeedIndicator::TapeSpeedIndicator(int lookback, size_t size) 4 | { 5 | lookback_period = lookback; 6 | times.reserve(size); 7 | } 8 | // right in the pussy 9 | int TapeSpeedIndicator::get_tapespeed() 10 | { 11 | remove(); 12 | return tapespeed; 13 | } 14 | 15 | void TapeSpeedIndicator::add(double &volume) 16 | { 17 | if(volume > 0) 18 | { 19 | tapespeed++; 20 | times.push_back(get_sec_timestamp(current_time()).count()); 21 | } 22 | } 23 | 24 | void TapeSpeedIndicator::remove() 25 | { 26 | double lbt = get_sec_timestamp(current_time()).count() - lookback_period; 27 | int temp=0; 28 | int pop=0; 29 | 30 | for(double x : times) 31 | { 32 | if(lbt > x) 33 | { 34 | temp--; 35 | pop++; 36 | } 37 | else 38 | { 39 | break; 40 | } 41 | 42 | } 43 | for(int x = 0; x < pop; x++) 44 | { 45 | times.erase(times.cbegin()); 46 | } 47 | 48 | tapespeed+=temp; 49 | } -------------------------------------------------------------------------------- /features/TapeSpeedIndicator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "utils.hpp" 6 | 7 | // No. of Trades in a given time 8 | class TapeSpeedIndicator 9 | { 10 | public: 11 | 12 | TapeSpeedIndicator(int lookback, size_t size); 13 | void clear(); 14 | void add(double &volume); 15 | int get_tapespeed(); 16 | void remove(); 17 | 18 | private: 19 | 20 | int lookback_period = 60; // seconds 21 | int tapespeed = 0; 22 | std::vector times; 23 | }; -------------------------------------------------------------------------------- /main/Ringbuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "types.hpp" 8 | 9 | /**/ 10 | static constexpr int CACHELINE_SIZE = 64; 11 | 12 | // Single Producer Single Consumer Ringbuffer Queue 13 | // https://rigtorp.se/ringbuffer/ 14 | 15 | template struct SPSCQueue { 16 | std::vector data_{}; 17 | alignas(CACHELINE_SIZE) std::atomic readIdx_{0}; 18 | alignas(CACHELINE_SIZE) size_t writeIdxCached_{0}; 19 | alignas(CACHELINE_SIZE) std::atomic writeIdx_{0}; 20 | alignas(CACHELINE_SIZE) size_t readIdxCached_{0}; 21 | 22 | SPSCQueue(size_t capacity) { data_.resize(capacity); } 23 | 24 | bool push(T&& val) { 25 | auto const writeIdx = writeIdx_.load(std::memory_order_relaxed); // memory order here is relaxed because no other threads are modifying this variable except the current thread its on, so latest value will always be in push() thread core cache or memory so that its always update to date like in normal single threaded application will be. 26 | auto nextWriteIdx = writeIdx + 1; 27 | if (nextWriteIdx == data_.size()) { // full 28 | nextWriteIdx = 0; 29 | } 30 | if (nextWriteIdx == readIdxCached_) { // full 31 | readIdxCached_ = readIdx_.load(std::memory_order_acquire); // lock this is acquire because it ensure that it sees latest value of readIdx written by pop thread 32 | if (nextWriteIdx == readIdxCached_) { 33 | return false; 34 | } 35 | } 36 | data_[writeIdx] = val; 37 | writeIdx_.store(nextWriteIdx, std::memory_order_release); // unlock 38 | return true; 39 | } 40 | 41 | bool pop() { 42 | auto const readIdx = readIdx_.load(std::memory_order_relaxed); 43 | if (readIdx == writeIdxCached_) { // if empty 44 | writeIdxCached_ = writeIdx_.load(std::memory_order_acquire); // this is acquire because it ensures that it sees the latest value of writeIdx_ written by push thread. because in multicore system it can be possible that latest value of writeidx_ is not flushed to memory from pop() thread cache so that means push() thread cannot see latest value, but std::memory_order_acquire ensures it is flushed to memory and updated in other thread as well 45 | if (readIdx == writeIdxCached_) { // latest value for writeIdxCached_ should be seen otherwise it is possible that the compiler and hardware could reorder memory accesses in a way that makes the latest value of writeIdx_ written by the other thread not visible to the current thread. This could result in the code not being able to correctly determine whether the queue is full or empty, which could lead to incorrect behavior. 46 | return false; 47 | } 48 | } 49 | // T val = data_[readIdx]; 50 | auto nextReadIdx = readIdx + 1; 51 | if (nextReadIdx == data_.size()) { 52 | nextReadIdx = 0; 53 | } 54 | readIdx_.store(nextReadIdx, std::memory_order_release); // unlock 55 | return true; 56 | } 57 | }; 58 | 59 | 60 | -------------------------------------------------------------------------------- /main/datafeeds.hpp: -------------------------------------------------------------------------------- 1 | #include "binance-ws.hpp" 2 | #include "binance-http.hpp" 3 | #include "ftx-http.hpp" 4 | #include "ftx-ws.hpp" 5 | #include "coinbase-ws.hpp" 6 | #include "coinbase-http.hpp" 7 | #include "kraken-http.hpp" 8 | #include "kraken-ws.hpp" 9 | #include 10 | #include "types.hpp" 11 | #include 12 | 13 | template 14 | class ExchangeOrderbookFeed{ 15 | public: 16 | std::map,Orderbook> orderbook_queues; 17 | //ExchangeOrderbookFeed()=default; 18 | void add_queue(std::string exchange, std::string symbol){ 19 | std::cout << "base add queue" << std::endl; 20 | 21 | } 22 | void build_orderbook(){ 23 | static_cast(this)->build_ob(); 24 | } 25 | 26 | void build_orderbooks_on_core(){ 27 | std::cout << "base on_core" << std::endl; 28 | 29 | } 30 | 31 | }; 32 | 33 | class BinanceOrderbookFeed : public ExchangeOrderbookFeed{ 34 | 35 | public: 36 | BinanceOrderbookFeed(){ 37 | std::cout << "binance OB feed" << std::endl; 38 | } 39 | void build_ob(){ 40 | std::cout << "build binance book" << std::endl; 41 | 42 | } 43 | 44 | }; 45 | 46 | 47 | class KrakenOrderbookFeed : public ExchangeOrderbookFeed{ 48 | 49 | public: 50 | KrakenOrderbookFeed(){ 51 | std::cout << "kraken ob feed" << std::endl; 52 | } 53 | void build_ob(){ 54 | std::cout << "build kraken book" << std::endl; 55 | 56 | } 57 | 58 | }; 59 | 60 | class CoinbaseOrderbookFeed : public ExchangeOrderbookFeed{ 61 | public: 62 | CoinbaseOrderbookFeed(){ 63 | std::cout << "coinbase OB feed " << std::endl; 64 | } 65 | 66 | void build_ob(){ 67 | std::cout << "build coinbase book" << std::endl; 68 | } 69 | 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include // can only include in one source file 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "TapeSpeedIndicator.hpp" 8 | #include "datafeeds.hpp" 9 | 10 | int main() 11 | { 12 | net::io_context ioc; 13 | ssl::context ctx{ssl::context::tlsv12_client}; 14 | 15 | ctx.set_verify_mode(ssl::verify_peer); 16 | ctx.set_default_verify_paths(); 17 | 18 | std::string coinbase_symbol = "ETH-USD"; 19 | std::string binance_symbol = "btcusdt"; 20 | std::string ftx_symbol = "BTC-PERP"; 21 | std::string kraken_symbol = "XBT/USD"; 22 | std::string kraken_symbol2 = "XBTUSD"; 23 | int levels = 10; 24 | 25 | // coinbase_producer_main(ioc,ctx,coinbase_symbol); 26 | // binance_producer_main(ioc,ctx,binance_symbol,levels); 27 | // ftx_producer_main(ioc,ctx,ftx_symbol); 28 | // kraken_producer_main(ioc,ctx,kraken_symbol,levels); 29 | 30 | // auto binanceapi = std::make_shared(ioc.get_executor(),ctx,ioc); 31 | // std::string l = "10"; 32 | // json out = binanceapi->orderbook(binance_symbol,l); 33 | // std::cout << "json response binance : " << out << std::endl; 34 | 35 | // auto krakenapi = std::make_shared(ioc.get_executor(), ctx, ioc); 36 | // std::string l = "25"; 37 | // json out = bitfinexapi->get_snapshot("tBTCUSD",l); 38 | // std::cout << "json bitfinex output : " << out << std::endl; 39 | // json out1 = bitfinexapi->place_market_sell("tBTCUSD","1"); 40 | // int id = 10; 41 | // json out2 = bitfinexapi->cancel_order(id); 42 | double price = 25000; 43 | double size = 1; 44 | 45 | //json out2 = krakenapi->submit_limit_order("sell", "XBTUSD", size, price); 46 | //json out2 = krakenapi->submit_market_order("sell", "XBTUSD", size); 47 | //json out2 = krakenapi->cancel_order(size); 48 | //json out2 = krakenapi->get_account_balance(); 49 | 50 | //std::cout << "market order kraken output : " << out2 << std::endl; 51 | //CoinbaseOrderbookFeed cs; 52 | auto binancews = std::make_shared(ioc.get_executor(),ctx); 53 | //binancews->subscribe_levelone("SUBSCRIBE","btcusdt"); 54 | binancews->subscribe_orderbook_diffs("SUBSCRIBE","btcusdt",10); 55 | 56 | ioc.run(); 57 | } 58 | -------------------------------------------------------------------------------- /main/notes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | * Context 5 | 6 | * So in summary, the WsStrategy struct derives from the Strategy trait to ensure that it implements the required methods 7 | to interact with WsAPI and also get the required functionality from the Strategy trait. -------------------------------------------------------------------------------- /main/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TapeSpeedIndicator.hpp" 3 | 4 | 5 | 6 | struct OrderBookMessage 7 | { 8 | bool is_bid; 9 | double price; 10 | double quantity; 11 | double update_id; 12 | OrderBookMessage(bool is, double p, double q, double u) : is_bid(is),price(p),quantity(q),update_id(u){} 13 | OrderBookMessage(){} 14 | }; 15 | 16 | struct OrderBookRow 17 | { 18 | double price; 19 | double quantity; 20 | double update_id; 21 | }; 22 | 23 | class Orderbook{ 24 | public: 25 | Orderbook(){ 26 | 27 | } 28 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MM++ 2 | 3 | WARNING : The author of this software will not be responsible for your losses on the market, use at your own discretion. 4 | This bot is still in construction. 5 | 6 | ## Introduction 7 | 8 | The cross exchange market making / Hedged Market Making strategy performs market making trades between two markets: 9 | 10 | * It emits limit orders to a less liquid, larger spread market. 11 | * It emits market orders on a more liquid, smaller spread market whenever the limit orders were hit. 12 | 13 | Market making isn't about placing bunch of bids and asks orders, you will soon realize orderflow isn't random at all and you get 14 | filled irregularly and accumulate inventory(which is a serious risk). Hedged Market making can help you with the inventory risk, unless you 15 | are slow somewhere. Basics remains the same, MM is all about capturing the spread and managing your inventory. 16 | You need to price in information as much as possible in a very low latency manner. 17 | 18 | ![Photo](https://user-images.githubusercontent.com/104965020/183235797-03f2f9d1-648e-4e12-a68b-62059a870f4d.png) 19 | 20 | ## Components 21 | 22 | 1) Feedhandlers 23 | 2) Book Builders 24 | 3) Strategy 25 | 4) OMS 26 | 27 | ## Architectural Design 28 | 29 | ![MM++ arch-Page-1 drawio (3)](https://user-images.githubusercontent.com/104965020/188065754-2ec5a554-9c3e-409c-84d5-10737a49e3b2.png) 30 | 31 | Components are scheduled to execute on distinct CPU cores using thread affinity and CPU isolation, which helps in preventing a thread from wandering between CPUs and reducing context switching overheads. Component Threads are expected to busy poll and never be interrupted, as it reduces the number of context switches 32 | because context switches only happens at each time slice. 33 | 34 | ## Risk Management Tools 35 | 36 | * If speed of arrival market orders picks up across exchanges, stop quoting. 37 | * BART move detections helps in determining taker exchange overloads. 38 | 39 | ## Build Instructions 40 | 41 | ### Install dependencies 42 | 43 | ``` 44 | # install dependent packages 45 | sudo apt-get update 46 | sudo apt-get install libboost-all-dev 47 | 48 | 49 | # install cmake 50 | sudo apt install cmake 51 | 52 | # install boost 1.79 53 | wget https://www.boost.org/users/history/version_1_79_0.html 54 | tar --bzip2 -xf boost_1_79_0.tar.bz2 55 | cd boost_1_79_0/ 56 | ./boostrap.sh 57 | ./b2 58 | sudo ./b2 install 59 | 60 | ``` 61 | 62 | Steps to build and run the bot: 63 | ``` 64 | git clone --recurse-submodules https://github.com/Naseefabu/HFTBOT.git 65 | cd HFTBOT/ 66 | ./configure.sh 67 | ./build.sh 68 | ./run.sh 69 | ``` 70 | 71 | 72 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | cd build ; ./hftbot --------------------------------------------------------------------------------