├── tests ├── main.cc ├── types_test.cc └── CMakeLists.txt ├── cmake ├── FindGumboQuery.cmake └── Findcurlpp.cmake ├── .gitignore ├── .gitmodules ├── .github └── FUNDING.yml ├── src ├── CMakeLists.txt └── at │ ├── crypt │ └── namespace.cc │ ├── fiat.cc │ ├── request.cc │ ├── coinmarketcap.cc │ ├── shapeshift.cc │ └── kraken.cc ├── include └── at │ ├── crypt │ └── namespace.hpp │ ├── namespace.hpp │ ├── market.hpp │ ├── fiat.hpp │ ├── request.hpp │ ├── exceptions.hpp │ ├── coinmarketcap.hpp │ ├── exchange.hpp │ ├── shapeshift.hpp │ ├── kraken.hpp │ └── types.hpp ├── CMakeLists.txt ├── .ycm_extra_conf.py ├── LICENSE └── README.md /tests/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char **argv) 4 | { 5 | ::testing::InitGoogleTest(&argc, argv); 6 | return RUN_ALL_TESTS(); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/types_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST(CurrencyPair, ShouldPrintCorrectly) { 5 | auto p = at::currency_pair_t("USD", "BTC"); 6 | ASSERT_EQ("USD_BTC", p.str()); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /cmake/FindGumboQuery.cmake: -------------------------------------------------------------------------------- 1 | find_path(GUMBO_QUERY_INCLUDE_DIR gq/Document.h) 2 | find_library(GUMBO_QUERY_SHARED_LIB libgq.so) 3 | 4 | include(FindPackageHandleStandardArgs) 5 | find_package_handle_standard_args(GumboQuery REQUIRED_VARS 6 | GUMBO_QUERY_SHARED_LIB GUMBO_QUERY_INCLUDE_DIR) 7 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.1) 2 | 3 | # all files .cc in . and its subfolders. 4 | # define variable TEST_SRC 5 | file(GLOB_RECURSE TEST_SRC "*.cc") 6 | 7 | # Find threads to link next in target_link_libraries 8 | find_package(Threads REQUIRED) 9 | 10 | add_executable (openat_tests "${TEST_SRC}") 11 | include_directories( 12 | ${OPENAT_INCLUDE_DIR} 13 | ${JSON_INCLUDE_DIR} 14 | ${GTEST_INCLUDE_DIR} 15 | ) 16 | 17 | target_link_libraries ( openat_tests LINK_PUBLIC 18 | openat 19 | gtest 20 | ) 21 | -------------------------------------------------------------------------------- /.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 | # Debug files 15 | *.dSYM 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.so.* 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | # Compile commands 39 | compile_commands.json 40 | 41 | # Build folder 42 | build/* 43 | 44 | config.json 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/curlpp"] 2 | path = libs/curlpp 3 | url = https://github.com/jpbarrette/curlpp 4 | [submodule "libs/googletest"] 5 | path = libs/googletest 6 | url = https://github.com/google/googletest 7 | [submodule "libs/rapidxml"] 8 | path = libs/rapidxml 9 | url = https://github.com/galeone/rapidxml 10 | [submodule "libs/spdlog"] 11 | path = libs/spdlog 12 | url = https://github.com/gabime/spdlog 13 | [submodule "libs/gumbo/parser"] 14 | path = libs/gumbo/parser 15 | url = https://github.com/google/gumbo-parser 16 | [submodule "libs/gumbo/query"] 17 | path = libs/gumbo/query 18 | url = https://github.com/lazytiger/gumbo-query 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [galeone] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /cmake/Findcurlpp.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | pkg_check_modules(PC_curlpp QUIET curlpp) 3 | 4 | find_path(curlpp_INCLUDE_DIR 5 | curlpp/Easy.hpp 6 | PATHS ${PC_curlpp_INCLUDE_DIRS} 7 | PATH_SUFFIXES curlpp) 8 | 9 | find_library(curlpp_SHARED_LIB 10 | libcurlpp.so 11 | PATHS ${PC_curlpp_LIBS} 12 | PATH_SUFFIXES curlpp) 13 | 14 | include(FindPackageHandleStandardArgs) 15 | find_package_handle_standard_args(curlpp REQUIRED_VARS 16 | curlpp_SHARED_LIB curlpp_INCLUDE_DIR) 17 | 18 | if(curlpp_FOUND) 19 | set(curlpp_INCLUDE_DIRS ${curlpp_INCLUDE_DIR}) 20 | endif() 21 | 22 | if(curlpp_FOUND AND NOT TARGET libcurlpp::libcurlpp) 23 | add_library(curlpp::curlpp INTERFACE IMPORTED) 24 | set_target_properties(curlpp::curlpp PROPERTIES 25 | INTERFACE_INCLUDE_DIRECTORIES ${curlpp_INCLUDE_DIR} 26 | INTERFACE_LINK_LIBRARIES ${curlpp_SHARED_LIB} 27 | ) 28 | endif() 29 | 30 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.1) 2 | 3 | # all files .cc in . and its subfolders. 4 | # define variable CURRENT_SRC 5 | file(GLOB_RECURSE CURRENT_SRC "*.cc") 6 | 7 | # Find OpenSSL 8 | find_package(OpenSSL REQUIRED) 9 | # sdplog 10 | find_package(spdlog REQUIRED) 11 | # gumbo query 12 | find_package(GumboQuery REQUIRED) 13 | # curlpp 14 | find_package(curlpp REQUIRED) 15 | # JSON 16 | find_package(nlohmann_json REQUIRED) 17 | # Threads 18 | find_package(Threads REQUIRED) 19 | 20 | # Executable at is the compilation of every .cc in this folder 21 | # and its subfolders 22 | 23 | add_library (openat SHARED "${CURRENT_SRC}") 24 | 25 | target_include_directories(openat 26 | PUBLIC 27 | ${OPENAT_INCLUDE_DIR} 28 | ${RAPIDXML_INCLUDE_DIR} 29 | ${GUMBO_QUERY_INCLUDE_DIR} 30 | ) 31 | 32 | target_link_libraries (openat 33 | PUBLIC 34 | Threads::Threads 35 | OpenSSL::SSL 36 | OpenSSL::Crypto 37 | curlpp::curlpp 38 | PRIVATE 39 | spdlog::spdlog 40 | nlohmann_json::nlohmann_json 41 | ${GUMBO_PARSER_SHARED_LIB} 42 | ${GUMBO_QUERY_SHARED_LIB} 43 | ) 44 | -------------------------------------------------------------------------------- /include/at/crypt/namespace.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_CRYPT_H_ 16 | #define AT_CRYPT_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace at::crypt { 23 | 24 | std::vector sha256(const std::string& data); 25 | std::vector base64_decode(const std::string& data); 26 | std::string base64_encode(const std::vector& data); 27 | std::vector hmac_sha512(const std::vector& data, 28 | const std::vector& key); 29 | 30 | } // end namespace at::crypt 31 | 32 | #endif // AT_CRYPT_H_ 33 | -------------------------------------------------------------------------------- /include/at/namespace.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_NAMESPACE_H_ 16 | #define AT_NAMESPACE_H_ 17 | 18 | #include 19 | 20 | namespace at { 21 | 22 | // check if the address is OK for the specificed currency 23 | inline bool isValidAddress(std::string symbol, hash_t address) 24 | { 25 | std::ostringstream stream; 26 | stream << "https://shapeshift.io/validateAddress/" << address << "/" 27 | << symbol; 28 | 29 | Request req; 30 | try { 31 | json result = req.get(stream.str()); 32 | return result["isvalid"]; // shapeshift API doc claim is "isValid", but 33 | // is "isvalid" 34 | } 35 | catch (std::runtime_error& e) { 36 | return false; 37 | } 38 | } 39 | 40 | } // end namespace at 41 | 42 | #endif // AT_NAMESPACE_H_ 43 | -------------------------------------------------------------------------------- /include/at/market.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_MARKET_H_ 16 | #define AT_MARKET_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace at { 26 | 27 | class Market { 28 | public: 29 | // Only common methods in markets 30 | virtual ~Market() {} 31 | 32 | // Pure virtual methods 33 | virtual deposit_info_t depositInfo(std::string currency) = 0; 34 | virtual std::vector info() = 0; 35 | virtual market_info_t info(currency_pair_t) = 0; 36 | virtual ticker_t ticker(currency_pair_t) = 0; 37 | virtual std::vector orderBook(currency_pair_t) = 0; 38 | virtual std::map coins() = 0; 39 | virtual std::map balance() = 0; 40 | virtual double balance(std::string currency) = 0; 41 | virtual std::vector openOrders() = 0; 42 | virtual std::vector closedOrders() = 0; 43 | virtual void place(order_t&) = 0; 44 | virtual void cancel(order_t&) = 0; 45 | }; 46 | 47 | } // end namespace at 48 | 49 | #endif // AT_MARKET_H_ 50 | -------------------------------------------------------------------------------- /include/at/fiat.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_FIAT_H_ 16 | #define AT_FIAT_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace at { 27 | 28 | /* Client for the daily fiat exchange rates at 29 | * https://www.ecb.europa.eu 30 | * 31 | * Every method can throw a response_error or a server_error. 32 | * A response_error is when the API handles the request but for some 33 | * reason an error occuurs. 34 | * 35 | * A server_error is when the status code of the request is != 200. 36 | * */ 37 | class Fiat : private Thrower { 38 | private: 39 | const std::string _host = "https://www.ecb.europa.eu/"; 40 | std::time_t _current_date = std::time(0); 41 | std::map _eur_to_currency_rate; 42 | 43 | // _update fetch and updates the XML with the update time and the 44 | // exhange rates 45 | void _update(); 46 | 47 | public: 48 | Fiat() { _update(); } 49 | ~Fiat() {} 50 | 51 | double rate(const currency_pair_t&); 52 | }; 53 | 54 | } // end namespace at 55 | 56 | #endif // AT_FIAT_H_ 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.1) 2 | project (openat VERSION 1.0.0 DESCRIPTION "OpenAT: Open Source Algorithmic Trading Library") 3 | 4 | # 5 | # BUILD SETTINGS 6 | # 7 | 8 | set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") 9 | 10 | # Create compile_commands.json in build dir while compiling 11 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON ) 12 | 13 | # Set C++17 standard 14 | set(CMAKE_CXX_STANDARD 17) 15 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 16 | 17 | if (NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt) 18 | if (NOT CMAKE_BUILD_TYPE) 19 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) 20 | endif() 21 | endif() 22 | 23 | # 24 | # BUILD DEPENDENCIES 25 | # 26 | # Build and setup the correct cmake variables for third-party libraries 27 | # 28 | 29 | # Rapidxml is a header-only library 30 | set(RAPIDXML "${PROJECT_SOURCE_DIR}/libs/rapidxml") 31 | set(RAPIDXML_INCLUDE_DIR "${RAPIDXML}") 32 | 33 | # 34 | # Build project 35 | # 36 | 37 | set(OPENAT_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") 38 | add_subdirectory(src) 39 | 40 | get_directory_property(hasParent PARENT_DIRECTORY) 41 | # Allows include directories outside this CMake, if it has been included 42 | if(hasParent) 43 | set( OPENAT_REQUIRED_INCLUDES ${OPENAT_REQUIRED_INCLUDES} PARENT_SCOPE ) 44 | endif() 45 | 46 | # 47 | # Build tests 48 | # 49 | enable_testing() 50 | 51 | # Build googletest 52 | set(GTEST "${PROJECT_SOURCE_DIR}/libs/googletest") 53 | set(GTEST_INCLUDE_DIR "${GTEST}/include") 54 | add_subdirectory(${GTEST}) 55 | 56 | add_subdirectory(tests) 57 | add_test (NAME openat_tests COMMAND openat_tests) 58 | 59 | if(NOT hasParent) 60 | # copy compile commands from build dir to project dir once compiled 61 | ADD_CUSTOM_TARGET(openat_do_always ALL COMMAND ${CMAKE_COMMAND} -E copy_if_different 62 | ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json 63 | ${PROJECT_SOURCE_DIR}/compile_commands.json) 64 | endif() 65 | -------------------------------------------------------------------------------- /include/at/request.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_REQUEST_H_ 16 | #define AT_REQUEST_H_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace at { 34 | 35 | class Request { 36 | private: 37 | std::list _headers; 38 | std::list _options; 39 | std::string _get(std::string url); 40 | 41 | public: 42 | Request() {} 43 | Request(std::list headers) : _headers(headers) {} 44 | Request(std::list options) : _options(options) {} 45 | Request(std::list headers, 46 | std::list options) 47 | : _headers(headers), _options(options) 48 | { 49 | } 50 | 51 | json get(std::string); 52 | std::string getHTML(std::string url); 53 | json post(std::string, json); 54 | json post(std::string, std::vector>); 55 | 56 | ~Request() 57 | { 58 | for (auto ptr : _options) { 59 | delete ptr; 60 | } 61 | } 62 | }; 63 | 64 | } // end namespace at 65 | 66 | #endif // AT_REQUEST_H_ 67 | -------------------------------------------------------------------------------- /include/at/exceptions.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_EXCEPTIONS_H_ 16 | #define AT_EXCEPTIONS_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace at { 23 | 24 | class server_error : public std::runtime_error { 25 | public: 26 | server_error(std::string message) : runtime_error(message) {} 27 | }; 28 | 29 | class response_error : public std::runtime_error { 30 | public: 31 | response_error(std::string message) : runtime_error(message) {} 32 | }; 33 | 34 | // Inherit from this class to use the method _throw_error_if_any 35 | // in order to throw a response_error if the json response contains 36 | // the "error" key 37 | class Thrower { 38 | protected: 39 | static void _throw_error_if_any(const json& res) 40 | { 41 | const auto e = res.find("error"); 42 | if (e != res.end()) { 43 | auto val = *e; 44 | if (val.is_string()) { 45 | auto message = val.get(); 46 | if (!message.empty()) { 47 | throw response_error(message); 48 | } 49 | } 50 | else if (val.is_array() && val.size() > 0) { 51 | auto message = val[0].get(); 52 | if (!message.empty()) { 53 | throw response_error(message); 54 | } 55 | } 56 | } 57 | } 58 | }; 59 | 60 | } // end namespace at 61 | 62 | #endif // AT_EXCEPTIONS_H_ 63 | -------------------------------------------------------------------------------- /include/at/coinmarketcap.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_COINMARKETCAP_H_ 16 | #define AT_COINMARKETCAP_H_ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace at { 27 | 28 | /* Client for CoinMarketCap JSON API 29 | * Documentation: https://coinmarketcap.com/api/ 30 | * 31 | * Every method can throw a response_error or a server_error. 32 | * A response_error is when the API handles the request but for some 33 | * reason 34 | * an error occuurs. 35 | * 36 | * A server_error is when the status code of the request is != 200. 37 | * */ 38 | class CoinMarketCap : private Thrower { 39 | private: 40 | // const std::string _host = "https://api.coinmarketcap.com/v1/"; 41 | const std::string _host = "https://api.alternative.me/v1/"; 42 | const std::string _reverse_host = "https://coinmarketcap.com/"; 43 | std::map _symbol_to_id; 44 | 45 | public: 46 | CoinMarketCap() 47 | { 48 | auto infos = ticker(); 49 | for (const auto& info : infos) { 50 | _symbol_to_id[info.symbol] = info.id; 51 | } 52 | } 53 | ~CoinMarketCap() {} 54 | 55 | std::vector ticker(); 56 | std::vector ticker(uint32_t limit); 57 | cm_ticker_t ticker(std::string currency_symbol); 58 | std::vector markets(std::string currency_symbol); 59 | gm_data_t global(); 60 | }; 61 | 62 | } // end namespace at 63 | 64 | #endif // AT_COINMARKETCAP_H_ 65 | -------------------------------------------------------------------------------- /src/at/crypt/namespace.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace at::crypt { 8 | 9 | std::vector sha256(const std::string& data) 10 | { 11 | std::vector digest(SHA256_DIGEST_LENGTH); 12 | 13 | SHA256_CTX ctx; 14 | SHA256_Init(&ctx); 15 | SHA256_Update(&ctx, data.c_str(), data.length()); 16 | SHA256_Final(digest.data(), &ctx); 17 | 18 | return digest; 19 | } 20 | 21 | std::vector base64_decode(const std::string& data) 22 | { 23 | BIO* b64 = BIO_new(BIO_f_base64()); 24 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 25 | 26 | BIO* bmem = BIO_new_mem_buf((void*)data.c_str(), data.length()); 27 | bmem = BIO_push(b64, bmem); 28 | 29 | std::vector output(data.length()); 30 | int decoded_size = BIO_read(bmem, output.data(), output.size()); 31 | BIO_free_all(bmem); 32 | 33 | if (decoded_size < 0) { 34 | throw std::runtime_error("failed while decoding base64."); 35 | } 36 | 37 | return output; 38 | } 39 | 40 | std::string base64_encode(const std::vector& data) 41 | { 42 | BIO* b64 = BIO_new(BIO_f_base64()); 43 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 44 | 45 | BIO* bmem = BIO_new(BIO_s_mem()); 46 | b64 = BIO_push(b64, bmem); 47 | 48 | BIO_write(b64, data.data(), data.size()); 49 | BIO_flush(b64); 50 | 51 | BUF_MEM* bptr = NULL; 52 | BIO_get_mem_ptr(b64, &bptr); 53 | 54 | std::string output(bptr->data, bptr->length); 55 | BIO_free_all(b64); 56 | 57 | return output; 58 | } 59 | 60 | std::vector hmac_sha512(const std::vector& data, 61 | const std::vector& key) 62 | { 63 | unsigned int len = EVP_MAX_MD_SIZE; 64 | std::vector digest(len); 65 | 66 | auto ctx = HMAC_CTX_new(); 67 | 68 | HMAC_Init_ex(ctx, key.data(), key.size(), EVP_sha512(), NULL); 69 | HMAC_Update(ctx, data.data(), data.size()); 70 | HMAC_Final(ctx, digest.data(), &len); 71 | 72 | HMAC_CTX_free(ctx); 73 | 74 | return digest; 75 | } 76 | 77 | } // end at::crypt namespace 78 | -------------------------------------------------------------------------------- /include/at/exchange.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_EXCHANGE_H_ 16 | #define AT_EXCHANGE_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace at { 26 | 27 | class Exchange { 28 | public: 29 | virtual ~Exchange() {} 30 | 31 | // Pure virtual methods 32 | 33 | /* Gets the current rate offered by the exchange for the 34 | * requested currency pair. This is an estimate because 35 | * the rate can occasionally change rapidly depending on the markets. The 36 | * rate is also a 'use-able' rate not a direct market rate. Meaning 37 | * multiplying your input coin amount times the rate should give you a close 38 | * approximation of what will be sent out. This rate does not include the 39 | * transaction (miner) fee taken off every transaction. */ 40 | virtual double rate(currency_pair_t) = 0; 41 | 42 | /* This gets the market info (pair, rate, limit, minimum limit, miner fee). 43 | */ 44 | virtual std::vector info() = 0; 45 | 46 | /* This gets the market info (pair, rate, limit, minimum limit, miner fee) 47 | * for the spcified pair. */ 48 | virtual exchange_info_t info(currency_pair_t) = 0; 49 | 50 | virtual min_max_t depositLimit(currency_pair_t) = 0; 51 | virtual json recentTransaction(uint32_t) = 0; 52 | virtual deposit_status_t depositStatus(hash_t) = 0; 53 | virtual std::pair timeRemeaningForTransaction( 54 | hash_t) = 0; 55 | virtual std::map coins() = 0; 56 | }; 57 | 58 | } // end namespace at 59 | 60 | #endif // AT_EXCHANGE_H_ 61 | -------------------------------------------------------------------------------- /src/at/fiat.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #include 16 | 17 | namespace at { 18 | 19 | void Fiat::_update() 20 | { 21 | // save the document & today date, at the 14:15 CET (12:15 GMT), ref: 22 | // https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html 23 | const char *precise_format = "%Y-%m-%d %H:%M"; 24 | std::time_t now = std::time(0); 25 | std::tm *now_tm = std::gmtime(&now); 26 | std::ostringstream oss; 27 | const char *day_format = "%Y-%m-%d"; 28 | oss << std::put_time(now_tm, day_format); 29 | std::tm tm{}; // initialize value 30 | std::stringstream ss; 31 | ss << oss.str(); 32 | ss << " 12:15"; 33 | ss >> std::get_time(&tm, precise_format); 34 | auto current_date = std::mktime(&tm); 35 | 36 | if (labs(_current_date - current_date) < 24 * 60 * 60) { 37 | Request req; 38 | std::string page = 39 | req.getHTML(_host + "stats/eurofxref/eurofxref-daily.xml"); 40 | 41 | rapidxml::xml_document doc; 42 | auto constant_page = page.c_str(); 43 | char *ptr_page = new char[page.length() + 1]; 44 | std::memcpy(ptr_page, constant_page, page.length() + 1); 45 | doc.parse<0>(ptr_page); 46 | auto cube = doc.first_node()->first_node("Cube"); 47 | if (!cube) { 48 | throw std::runtime_error("Unable to find Cube element on " + _host + 49 | "stats/eurofxref/eurofxref-daily.xml"); 50 | } 51 | 52 | cube = cube->first_node("Cube"); 53 | if (!cube) { 54 | throw std::runtime_error( 55 | "Unable to find Cube element under Cube element on " + _host + 56 | "stats/eurofxref/eurofxref-daily.xml"); 57 | } 58 | oss.str(""); 59 | oss.clear(); 60 | 61 | ss.str(""); 62 | ss.clear(); 63 | 64 | tm = {}; 65 | ss << cube->first_attribute("time")->value(); 66 | ss << " 12:15"; 67 | ss >> std::get_time(&tm, precise_format); 68 | 69 | // parse cubes 70 | auto cubes = cube->first_node("Cube"); 71 | if (!cubes) { 72 | throw std::runtime_error( 73 | "Unable to find Cube element list under Cube->Cube tag on " + 74 | _host + "stats/eurofxref/eurofxref-daily.xml"); 75 | } 76 | 77 | for (auto cube = cubes; cube; cube = cube->next_sibling()) { 78 | auto currency = 79 | std::string(cube->first_attribute("currency")->value()); 80 | toupper(currency); 81 | auto rate = std::stod(cube->first_attribute("rate")->value()); 82 | _eur_to_currency_rate[currency] = rate; 83 | } 84 | _eur_to_currency_rate["EUR"] = 1.; 85 | _current_date = std::mktime(&tm); 86 | } 87 | } 88 | 89 | // rate returns the exchange rate of the fiat pair 90 | double Fiat::rate(const currency_pair_t &pair) 91 | { 92 | try { 93 | _update(); 94 | } 95 | catch (...) { 96 | // if here _eur_to_currency_rate was aready filled by 97 | // the constructor, hence let's use the old values 98 | } 99 | 100 | std::string base, quote; 101 | base = pair.first; 102 | quote = pair.second; 103 | 104 | toupper(base); 105 | toupper(quote); 106 | 107 | if (base == "EUR") { 108 | return 1. / _eur_to_currency_rate[quote]; 109 | } 110 | 111 | if (quote == "EUR") { 112 | return _eur_to_currency_rate[base]; 113 | } 114 | 115 | return _eur_to_currency_rate[base] / _eur_to_currency_rate[quote]; 116 | } 117 | 118 | } // namespace at 119 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import ycm_core 4 | from clang_helpers import PrepareClangFlags 5 | 6 | 7 | def current_dir(): 8 | return os.path.dirname(os.path.abspath(__file__)) 9 | 10 | 11 | DATABASE = ycm_core.CompilationDatabase(current_dir()) 12 | 13 | SOURCE_EXTENSIONS = [ '.cc', '.cpp', '.cxx', '.c', '.m', '.mm' ] 14 | 15 | # This provides a safe fall-back if no compilation commands are available. You could also add a 16 | # includes relative to your project directory, for example. 17 | FLAGS = [ 18 | '-Wall', 19 | '-std=c++14', 20 | '-stdlib=libc++', 21 | '-x', 22 | 'c++', 23 | '-I', 24 | '.', 25 | '-isystem', 26 | '/usr/local/include', 27 | '-isystem', 28 | '/usr/include', 29 | ] 30 | 31 | 32 | def MakeRelativePathsInFlagsAbsolute(flags, working_directory): 33 | if not working_directory: 34 | return list(flags) 35 | new_flags = [] 36 | make_next_absolute = False 37 | path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] 38 | for flag in flags: 39 | new_flag = flag 40 | 41 | if make_next_absolute: 42 | make_next_absolute = False 43 | if not flag.startswith('/'): 44 | new_flag = os.path.join(working_directory, flag) 45 | 46 | for path_flag in path_flags: 47 | if flag == path_flag: 48 | make_next_absolute = True 49 | break 50 | 51 | if flag.startswith(path_flag): 52 | path = flag[len(path_flag):] 53 | new_flag = path_flag + os.path.join(working_directory, path) 54 | break 55 | 56 | if new_flag: 57 | new_flags.append(new_flag) 58 | return new_flags 59 | 60 | 61 | def IsHeaderFile(filename): 62 | extension = os.path.splitext(filename)[1] 63 | return extension in ['.h', '.hxx', '.hpp', '.hh'] 64 | 65 | 66 | def GetCompilationInfoForFile(filename): 67 | # The compilation_commands.json file generated by CMake does not have entries 68 | # for header files. So we do our best by asking the db for flags for a 69 | # corresponding source file, if any. If one exists, the flags for that file 70 | # should be good enough. 71 | if IsHeaderFile(filename): 72 | basename = os.path.splitext(filename)[0] 73 | 74 | for extension in SOURCE_EXTENSIONS: 75 | replacement_file = basename + extension 76 | if os.path.exists(replacement_file): 77 | compilation_info = DATABASE.GetCompilationInfoForFile( 78 | replacement_file) 79 | if compilation_info.compiler_flags_: 80 | return compilation_info 81 | # If here, not found, fallback to include the first file in the src dir 82 | filename = filename.replace("include", "src") 83 | #return None 84 | 85 | compilation_from_database = DATABASE.GetCompilationInfoForFile(filename) 86 | 87 | # If the file still not exists in the compilation database 88 | # guess the compilation flags (in reality we only need the include path 89 | # to have a nice autocompletion) 90 | # using the compilation db from a file in the same folder 91 | if not compilation_from_database.compiler_flags_: 92 | with open( 93 | os.path.join(DATABASE.database_directory, 94 | "compile_commands.json")) as jsondb: 95 | json_db = json.load(jsondb) 96 | 97 | for row in json_db: 98 | compiled_file_database_dir = "/".join(row["file"].split("/")[:-1]) 99 | print(compiled_file_database_dir, 'vs', os.path.dirname(filename)) 100 | 101 | if compiled_file_database_dir == os.path.dirname(filename): 102 | compilation_info = DATABASE.GetCompilationInfoForFile( 103 | row["file"]) 104 | if compilation_info.compiler_flags_: 105 | return compilation_info 106 | 107 | return compilation_from_database 108 | 109 | 110 | def FlagsForFile(filename, **kwargs): 111 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 112 | # python list, but a "list-like" StringVec object 113 | compilation_info = GetCompilationInfoForFile(filename) 114 | if not compilation_info: 115 | relative_to = current_dir() 116 | final_flags = MakeRelativePathsInFlagsAbsolute(FLAGS, relative_to) 117 | else: 118 | final_flags = MakeRelativePathsInFlagsAbsolute( 119 | compilation_info.compiler_flags_, 120 | compilation_info.compiler_working_dir_) 121 | return {'flags': PrepareClangFlags(final_flags, filename), 'do_cache': True} 122 | -------------------------------------------------------------------------------- /src/at/request.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace at { 20 | 21 | using namespace curlpp::options; 22 | 23 | std::string Request::getHTML(std::string url) { return _get(url); } 24 | 25 | json Request::get(std::string url) { return json::parse(_get(url)); } 26 | 27 | std::string Request::_get(std::string url) 28 | { 29 | curlpp::Easy req; 30 | std::ostringstream stream; 31 | try { 32 | req.setOpt(Url(url)); 33 | req.setOpt(FollowLocation(true)); 34 | req.setOpt(SslVersion(CURL_SSLVERSION_TLSv1_2)); 35 | for (auto opt : _options) { 36 | req.setOpt(*opt); 37 | } 38 | 39 | if (!_headers.empty()) { 40 | req.setOpt(HttpHeader(_headers)); 41 | } 42 | 43 | req.setOpt(WriteStream(&stream)); 44 | req.perform(); 45 | } 46 | catch (const curlpp::LibcurlRuntimeError& e) { 47 | throw server_error(e.what()); 48 | } 49 | 50 | long code = curlpp::infos::ResponseCode::get(req); 51 | if (code == 200L) { 52 | return stream.str(); 53 | } 54 | 55 | stream.str(""); 56 | stream.clear(); 57 | stream << "GET " << url << "; status = " << code; 58 | throw server_error(stream.str()); 59 | } 60 | 61 | json Request::post(std::string url, json params) 62 | { 63 | curlpp::Easy req; 64 | std::ostringstream stream; 65 | try { 66 | req.setOpt(Url(url)); 67 | req.setOpt(FollowLocation(true)); 68 | req.setOpt(SslVersion(CURL_SSLVERSION_TLSv1_2)); 69 | for (auto opt : _options) { 70 | req.setOpt(*opt); 71 | } 72 | 73 | std::list headers({"Content-Type: application/json"}); 74 | if (!_headers.empty()) { 75 | headers.insert(headers.end(), _headers.begin(), _headers.end()); 76 | } 77 | req.setOpt(HttpHeader(headers)); 78 | 79 | // Write params to a stream and use the stream to 80 | // convert to string 81 | stream << params; 82 | std::string data = stream.str(); 83 | req.setOpt(PostFields(data)); 84 | req.setOpt(PostFieldSize(data.length())); 85 | 86 | stream.str(""); 87 | stream.clear(); 88 | req.setOpt(WriteStream(&stream)); 89 | req.perform(); 90 | } 91 | catch (const curlpp::LibcurlRuntimeError& e) { 92 | throw server_error(e.what()); 93 | } 94 | 95 | long code = curlpp::infos::ResponseCode::get(req); 96 | if (code == 200L) { 97 | return json::parse(stream.str()); 98 | } 99 | 100 | stream.str(""); 101 | stream.clear(); 102 | stream << "POST JSON" << url << "; status = " << code; 103 | throw server_error(stream.str()); 104 | } 105 | 106 | json Request::post(std::string url, 107 | std::vector> params) 108 | { 109 | curlpp::Easy req; 110 | std::ostringstream stream; 111 | try { 112 | req.setOpt(Url(url)); 113 | req.setOpt(FollowLocation(true)); 114 | req.setOpt(SslVersion(CURL_SSLVERSION_TLSv1_2)); 115 | for (auto opt : _options) { 116 | req.setOpt(*opt); 117 | } 118 | 119 | std::list headers( 120 | {"Content-Type: application/x-www-form-urlencoded"}); 121 | if (!_headers.empty()) { 122 | headers.insert(headers.end(), _headers.begin(), _headers.end()); 123 | } 124 | req.setOpt(HttpHeader(headers)); 125 | 126 | for (auto& pair : params) { 127 | std::string key(pair.first); 128 | std::string value(pair.second); 129 | stream << key << "=" << curlpp::escape(value) << "&"; 130 | } 131 | 132 | auto postFields = stream.str(); 133 | postFields.pop_back(); // remove last & 134 | req.setOpt(PostFields(postFields)); 135 | req.setOpt(PostFieldSize(postFields.length())); 136 | 137 | stream.str(""); 138 | stream.clear(); 139 | req.setOpt(WriteStream(&stream)); 140 | req.perform(); 141 | } 142 | catch (const curlpp::LibcurlRuntimeError& e) { 143 | throw server_error(e.what()); 144 | } 145 | 146 | long code = curlpp::infos::ResponseCode::get(req); 147 | if (code == 200L) { 148 | return json::parse(stream.str()); 149 | } 150 | 151 | stream.str(""); 152 | stream.clear(); 153 | stream << "POST " << url << "; status = " << code; 154 | throw server_error(stream.str()); 155 | } 156 | 157 | } // end namespace at 158 | -------------------------------------------------------------------------------- /src/at/coinmarketcap.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #include 16 | 17 | namespace at { 18 | 19 | std::vector CoinMarketCap::ticker() 20 | { 21 | Request req; 22 | json res = req.get(_host + "ticker/"); 23 | _throw_error_if_any(res); 24 | return res; 25 | } 26 | 27 | std::vector CoinMarketCap::ticker(uint32_t limit) 28 | { 29 | Request req; 30 | std::ostringstream stream; 31 | stream << limit; 32 | json res = req.get(_host + "ticker/?limit=" + stream.str()); 33 | _throw_error_if_any(res); 34 | return res; 35 | } 36 | 37 | cm_ticker_t CoinMarketCap::ticker(std::string currency_symbol) 38 | { 39 | Request req; 40 | toupper(currency_symbol); 41 | auto id = _symbol_to_id.find(currency_symbol); 42 | currency_symbol = id != _symbol_to_id.end() ? id->second : currency_symbol; 43 | json res = req.get(_host + "ticker/" + currency_symbol + "/")[0]; 44 | _throw_error_if_any(res); 45 | return res; 46 | } 47 | 48 | gm_data_t CoinMarketCap::global() 49 | { 50 | Request req; 51 | json res = req.get(_host + "global"); 52 | _throw_error_if_any(res); 53 | return res; 54 | } 55 | 56 | std::vector CoinMarketCap::markets(std::string currency_symbol) 57 | { 58 | Request req; 59 | toupper(currency_symbol); 60 | 61 | // Some currency needs a different treatment (yeah...) 62 | if (currency_symbol == "XRP") { 63 | currency_symbol = "xrp"; 64 | } 65 | else { 66 | auto id = _symbol_to_id.find(currency_symbol); 67 | currency_symbol = 68 | id != _symbol_to_id.end() ? id->second : currency_symbol; 69 | } 70 | std::string page = req.getHTML(_reverse_host + "currencies/" + 71 | currency_symbol + "/markets/"); 72 | 73 | CDocument doc; 74 | doc.parse(page.c_str()); 75 | CSelection table = doc.find("tbody"); 76 | if (table.nodeNum() == 0) { 77 | throw std::runtime_error("Unable to find a table on " + _reverse_host + 78 | "currencies/" + currency_symbol + "/markets/"); 79 | } 80 | 81 | std::vector ret; 82 | CSelection rows = table.nodeAt(0).find("tr"); 83 | auto now = 84 | std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 85 | 86 | auto _to_number_string = [](std::string text) -> std::string { 87 | text.erase(std::remove(text.begin(), text.end(), ','), text.end()); 88 | text.erase(std::remove(text.begin(), text.end(), '$'), text.end()); 89 | text.erase(std::remove(text.begin(), text.end(), ' '), text.end()); 90 | return text; 91 | }; 92 | for (size_t i = 0; i < rows.nodeNum(); ++i) { 93 | CNode row = rows.nodeAt(i); 94 | CSelection fields = row.find("td"); 95 | if (fields.nodeNum() != 10) { 96 | throw std::runtime_error("CMC markets: expected 10 columns, got " + 97 | std::to_string(fields.nodeNum())); 98 | } 99 | 100 | // Skip markets not updated recently 101 | // 8: updated 102 | std::string updated_string = 103 | fields.nodeAt(9).find("div").nodeAt(0).text(); 104 | at::tolower(updated_string); 105 | if (updated_string != "recently") { 106 | continue; 107 | } 108 | 109 | // 0: rank, unused because we insert in the return vector following this 110 | // order 111 | // 1: name 112 | std::string name = fields.nodeAt(1).find("a").nodeAt(0).text(); 113 | // 2: cur1/cur2 114 | std::string pair_string = fields.nodeAt(2).find("a").nodeAt(0).text(); 115 | auto split_pos = pair_string.find("/"); 116 | auto first = pair_string.substr(0, split_pos); 117 | auto second = pair_string.substr(split_pos + 1, pair_string.length()); 118 | 119 | // 3: volumes
$1,2,34,5
120 | std::string usd_volume_string = 121 | _to_number_string(fields.nodeAt(3).find("div").nodeAt(0).text()); 122 | 123 | // if there is a * or a ? the volume is unknown or an outlier, thus ignore. 124 | if (usd_volume_string.find("?") != std::string::npos || 125 | usd_volume_string.find('*') != std::string::npos) { 126 | continue; 127 | } 128 | long long int day_volume_usd = std::stoull(usd_volume_string); 129 | 130 | // 4: prices $12,12,12.xx 131 | std::string price_usd_string = 132 | _to_number_string(fields.nodeAt(4).text()); 133 | 134 | // If there is a * in the string, the price is an outlier 135 | // and we ignore this row. 136 | if (price_usd_string.find('*') != std::string::npos) { 137 | continue; 138 | } 139 | double price_usd = std::stod(price_usd_string); 140 | 141 | // 5: xx.yy% percentage
a.b%
142 | std::string percentage_string = 143 | fields.nodeAt(5).find("div").nodeAt(0).text(); 144 | // remove % 145 | percentage_string.pop_back(); 146 | float percent_volume = std::stof(percentage_string); 147 | 148 | // 6 effective liquidity: unused 149 | // 7 category: unused 150 | // 8 fee type unused 151 | 152 | cm_market_t market{ 153 | .name = name, 154 | .pair = currency_pair_t(first, second), 155 | .day_volume_usd = day_volume_usd, 156 | .price_usd = price_usd, 157 | .percent_volume = percent_volume, 158 | .last_updated = now, 159 | }; 160 | ret.push_back(market); 161 | } 162 | 163 | return ret; 164 | } 165 | 166 | } // namespace at 167 | -------------------------------------------------------------------------------- /include/at/shapeshift.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_SHAPESHIFT_H_ 16 | #define AT_SHAPESHIFT_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace at { 23 | 24 | /* Client for ShapeShift API. 25 | * API doumentation available: https://info.shapeshift.io/api 26 | * Method descriptions are kept from that page. 27 | * 28 | * Every method can throw a response_error or a server_error. 29 | * A response_error is when the API handles the request but for some reason 30 | * an error occuurs. 31 | * 32 | * A server_error is when the status code of the request is != 200. */ 33 | class Shapeshift : public Exchange, private Thrower { 34 | private: 35 | const std::string _host = "https://shapeshift.io/"; 36 | const std::string _affiliate_private_key; 37 | std::map _shift_params(currency_pair_t pair, 38 | hash_t return_addr, 39 | hash_t withdrawal_addr); 40 | 41 | public: 42 | Shapeshift() {} 43 | Shapeshift(std::string affiliate_private_key) 44 | : _affiliate_private_key(affiliate_private_key) 45 | { 46 | } 47 | ~Shapeshift() {} 48 | 49 | /* Gets the current rate offered by Shapeshift. This is an estimate because 50 | * the rate can occasionally change rapidly depending on the markets. The 51 | * rate is also a 'use-able' rate not a direct market rate. Meaning 52 | * multiplying your input coin amount times the rate should give you a close 53 | * approximation of what will be sent out. This rate does not include the 54 | * transaction (miner) fee taken off every transaction. */ 55 | double rate(currency_pair_t) override; 56 | 57 | /* Gets the current deposit limit set by Shapeshift. Amounts deposited over 58 | * this limit will be sent to the return address if one was entered, 59 | * otherwise the user will need to contact ShapeShift support to retrieve 60 | * their coins. This is an estimate because a sudden market swing could move 61 | * the limit. */ 62 | min_max_t depositLimit(currency_pair_t) override; 63 | 64 | /* This gets the exchange info (pair, rate, limit, minimum limit, miner 65 | * fee). 66 | */ 67 | std::vector info() override; 68 | 69 | /* This gets the exchange info (pair, rate, limit, minimum limit, miner fee) 70 | * for the spcified pair. */ 71 | exchange_info_t info(currency_pair_t) override; 72 | 73 | /* Get a list of the most recent transactions. 74 | * max is the maximum number of transactions to return. 75 | * Also, max must be a number between 1 and 50 (inclusive). */ 76 | json recentTransaction(uint32_t) override; 77 | 78 | /* This returns the status of the most recent deposit transaction to the 79 | * address. */ 80 | deposit_status_t depositStatus(hash_t) override; 81 | 82 | /* When a transaction is created with a fixed amount requested there is a 10 83 | * minute window for the deposit. After the 10 minute window if the deposit 84 | * has not been received the transaction expires and a new one must be 85 | * created. This api call returns how many seconds are left before the 86 | * transaction expires. Please note that if the address is a ripple address, 87 | * it will include the "?dt=destTagNUM" appended on the end, and you will 88 | * need to use the URIEncodeComponent() function on the address before 89 | * sending it in as a param, to get a successful response. 90 | * 91 | * hash_t is the deposit address to look up. */ 92 | std::pair timeRemeaningForTransaction( 93 | hash_t) override; 94 | 95 | /* Allows anyone to get a list of all the currencies that Shapeshift 96 | * currently supports at any given time. The list will include the name, 97 | * symbol, availability status, and an icon link for each. */ 98 | std::map coins() override; 99 | 100 | /* Allows vendors to get a list of all transactions that have ever been done 101 | * using a specific API key. Transactions are created with an affilliate 102 | * PUBLIC KEY, but they are looked up using the linked PRIVATE KEY, to 103 | * protect the privacy of our affiliates' account details. */ 104 | std::vector transactionsList(); 105 | 106 | /* Allows vendors to get a list of all transactions that have ever been sent 107 | * to one of their addresses. The affilliate's PRIVATE KEY must be provided, 108 | * and will only return transactions that were sent to output address AND 109 | * were created using / linked to the affiliate's PUBLIC KEY. Please note 110 | * that if the address is a ripple address and it includes the 111 | * "?dt=destTagNUM" appended on the end, you will need to use the 112 | * URIEncodeComponent() function on the address before sending it in as a 113 | * param, to get a successful response. 114 | * 115 | * hash_t the address that output coin was sent to for the shift. */ 116 | std::vector transactionsList(hash_t); 117 | 118 | /* This is the primary data input into ShapeShift. 119 | * Use only certain fields of the data required by the API (no optional 120 | * fields for XRP or optional fields for NXT). 121 | * If the object was instantiate with an API Key, the API key is sent in the 122 | * body request. 123 | * Returns the address in which deposit the [input_coin] amount to convert 124 | * into [output_coin] 125 | * 126 | * pair = what coins are being exchanged 127 | * returnAddress = [input_coin address] address to return deposit to if 128 | * anything goes wrong with exchange 129 | * withdrawal = [output_coin address] the address for resulting coin to be 130 | * sent to. */ 131 | hash_t shift(currency_pair_t, hash_t return_addr, hash_t withdrawal_addr); 132 | 133 | /* This call allows you to request a fixed amount to be sent to the 134 | * withdrawal address. You provide a withdrawal address and the amount you 135 | * want sent to it. We return the amount to deposit and the address to 136 | * deposit to. This allows you to use shapeshift as a payment mechanism. */ 137 | hash_t shift(currency_pair_t, hash_t return_addr, hash_t withdrawal_addr, 138 | double amount); 139 | 140 | /* This call requests a receipt for a transaction. The email address will be 141 | * added to the conduit associated with that transaction as well. (Soon it 142 | * will also send receipts to subsequent transactions on that conduit). */ 143 | bool sendReceipt(std::string, hash_t); 144 | 145 | /* This call also allows you to request a quoted price on the amount of a 146 | * transaction. */ 147 | json quotedPrice(currency_pair_t, double amount); 148 | 149 | /* This call allows you to request for canceling a pending transaction by 150 | * the deposit address. If there is fund sent to the deposit address, this 151 | * pending transaction cannot be canceled. 152 | * 153 | * Throws a response_error if an error occur 154 | * */ 155 | void cancel(hash_t); 156 | }; 157 | 158 | } // end namespace at 159 | 160 | #endif // AT_SHAPESHIFT_H_ 161 | -------------------------------------------------------------------------------- /src/at/shapeshift.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #include 16 | 17 | namespace at { 18 | 19 | double Shapeshift::rate(currency_pair_t pair) 20 | { 21 | Request req; 22 | std::ostringstream url; 23 | url << _host; 24 | url << "rate/"; 25 | url << pair; 26 | json res = req.get(url.str()); 27 | _throw_error_if_any(res); 28 | return std::stod(res.at("rate").get()); 29 | } 30 | 31 | min_max_t Shapeshift::depositLimit(currency_pair_t pair) 32 | { 33 | Request req; 34 | std::ostringstream url; 35 | url << _host; 36 | url << "limit/"; 37 | url << pair; 38 | json res = req.get(url.str()); 39 | _throw_error_if_any(res); 40 | // {"limit":"1.81514557","min":"0.000821","pair":"btc_eth"} 41 | return min_max_t{.min = std::stod(res.at("min").get()), 42 | .max = std::stod(res.at("limit").get())}; 43 | } 44 | 45 | std::vector Shapeshift::info() 46 | { 47 | Request req; 48 | json res = req.get(_host + "marketinfo/"); 49 | _throw_error_if_any(res); 50 | // ,{"limit":0.43007489,"maxLimit":0.43007489,"min":0.01802469,"minerFee":0.01,"pair":"NMC_PPC","rate":"1.06196283"} 51 | std::vector markets; 52 | for (const auto& market : res) { 53 | auto pair_str = market.at("pair").get(); 54 | size_t delimiter_it = pair_str.find_last_of("_"); 55 | auto pair = currency_pair_t(pair_str.substr(0, delimiter_it), 56 | pair_str.substr(delimiter_it + 1)); 57 | try { 58 | markets.push_back(exchange_info_t{ 59 | .pair = pair, 60 | .limit = min_max_t{.min = market.at("min").get(), 61 | .max = market.at("limit").get()}, 62 | .rate = std::stod( 63 | market.at("rate").get()), // rate is a string 64 | .miner_fee = market.at("minerFee").get()}); 65 | } 66 | catch (const json::out_of_range&) { 67 | } // skip non complete rows 68 | } 69 | return markets; 70 | } 71 | 72 | exchange_info_t Shapeshift::info(currency_pair_t pair) 73 | { 74 | Request req; 75 | std::ostringstream url; 76 | url << _host; 77 | url << "marketinfo/"; 78 | url << pair; 79 | json market = req.get(url.str()); 80 | _throw_error_if_any(market); 81 | 82 | // {"limit":0.43558867,"maxLimit":0.43558867,"minerFee":0.01,"minimum":0.01753086,"pair":"nmc_ppc","rate":1.04852027} 83 | // shapeshift API is inconsistent as hell 84 | return exchange_info_t{ 85 | .pair = pair, 86 | .limit = min_max_t{.min = market.at("minimum") 87 | .get(), // minumum instead of min 88 | .max = market.at("limit").get()}, 89 | .rate = market.at("rate").get(), // rate is no more a string 90 | .miner_fee = market.at("minerFee").get()}; 91 | } 92 | 93 | json Shapeshift::recentTransaction(uint32_t max) 94 | { 95 | Request req; 96 | std::ostringstream stream; 97 | stream << max; 98 | json res = req.get(_host + "recenttx/" + stream.str()); 99 | _throw_error_if_any(res); 100 | return res; 101 | } 102 | 103 | deposit_status_t Shapeshift::depositStatus(hash_t address) 104 | { 105 | Request req; 106 | json res = req.get(_host + "txStat/" + address); 107 | _throw_error_if_any(res); 108 | return res.at("status").get(); 109 | } 110 | std::pair Shapeshift::timeRemeaningForTransaction( 111 | hash_t address) 112 | { 113 | Request req; 114 | json res = req.get(_host + "timeremaining/" + address); 115 | _throw_error_if_any(res); 116 | return std::pair(res["status"].get(), 117 | res.at("seconds_remaining").get()); 118 | } 119 | 120 | std::map Shapeshift::coins() 121 | { 122 | Request req; 123 | json res = req.get(_host + "getcoins/"); 124 | _throw_error_if_any(res); 125 | return res; 126 | } 127 | 128 | std::vector Shapeshift::transactionsList() 129 | { 130 | if (_affiliate_private_key.empty()) { 131 | throw std::runtime_error( 132 | "transactionsList require an affiliate private key"); 133 | } 134 | Request req; 135 | json res = req.get(_host + "txbyapikey/" + _affiliate_private_key); 136 | _throw_error_if_any(res); 137 | return res; 138 | } 139 | 140 | std::vector Shapeshift::transactionsList(hash_t address) 141 | { 142 | if (_affiliate_private_key.empty()) { 143 | throw std::runtime_error( 144 | "transactionList require an affiliate private key"); 145 | } 146 | Request req; 147 | json res = req.get(_host + "txbyaddress/" + address + "/" + 148 | _affiliate_private_key); 149 | _throw_error_if_any(res); 150 | return res; 151 | } 152 | 153 | std::map Shapeshift::_shift_params( 154 | currency_pair_t pair, hash_t return_addr, hash_t withdrawal_addr) 155 | { 156 | std::map body; 157 | if (!_affiliate_private_key.empty()) { 158 | body["apiKey"] = _affiliate_private_key; 159 | } 160 | body["withdrawal"] = withdrawal_addr; 161 | body["returnAddress"] = return_addr; 162 | body["pair"] = pair.str(); 163 | return body; 164 | } 165 | 166 | hash_t Shapeshift::shift(currency_pair_t pair, hash_t return_addr, 167 | hash_t withdrawal_addr) 168 | { 169 | std::map body = 170 | _shift_params(pair, return_addr, withdrawal_addr); 171 | json data = json(body); 172 | Request req; 173 | json res = req.post(_host + "shift", data); 174 | _throw_error_if_any(res); 175 | return hash_t(res["deposit"].get()); 176 | } 177 | 178 | hash_t Shapeshift::shift(currency_pair_t pair, hash_t return_addr, 179 | hash_t withdrawal_addr, double amount) 180 | { 181 | std::map body = 182 | _shift_params(pair, return_addr, withdrawal_addr); 183 | body["amount"] = std::to_string(amount); 184 | json data = json(body); 185 | Request req; 186 | json res = req.post(_host + "sendamount", data); 187 | _throw_error_if_any(res); 188 | return hash_t(res["success"]["deposit"].get()); 189 | } 190 | 191 | json Shapeshift::quotedPrice(currency_pair_t pair, double amount) 192 | { 193 | std::map body; 194 | if (!_affiliate_private_key.empty()) { 195 | body["apiKey"] = _affiliate_private_key; 196 | } 197 | body["amount"] = std::to_string(amount); 198 | body["pair"] = pair.str(); 199 | Request req; 200 | json data = json(body); 201 | json res = req.post(_host + "sendamount", data); 202 | _throw_error_if_any(res); 203 | return res["success"]; 204 | } 205 | 206 | void Shapeshift::cancel(hash_t deposit_address) 207 | { 208 | Request req; 209 | json data = {{"address", {deposit_address}}}; 210 | json res = req.post(_host + "cancelpending", data); 211 | _throw_error_if_any(res); 212 | } 213 | 214 | bool Shapeshift::sendReceipt(std::string email, hash_t txid) 215 | { 216 | Request req; 217 | json data = {{"email", email}, {"txid", txid}}; 218 | json res = req.post(_host + "mail", data); 219 | _throw_error_if_any(res); 220 | return res.at("status").get() == 221 | deposit_status_t::complete; 222 | } 223 | 224 | } // namespace at 225 | -------------------------------------------------------------------------------- /include/at/kraken.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_KRAKEN_H_ 16 | #define AT_KRAKEN_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace at { 31 | 32 | /* Client for Kraken API. 33 | * API doumentation available: https://www.kraken.com/help/api 34 | * Method descriptions are kept from that page. 35 | * 36 | * Every method can throw a response_error or a server_error. 37 | * A response_error is when the API handles the request but for some reason 38 | * an error occuurs. 39 | * 40 | * A server_error is when the status code of the request is != 200. 41 | * 42 | * Margin trading is too risky and thus is not supported. */ 43 | 44 | class Kraken : public Market, private Thrower { 45 | // class Kraken : private Thrower { 46 | private: 47 | const std::string _version = "0"; 48 | const std::string _host = "https://api.kraken.com/" + _version + "/"; 49 | const std::string _api_key, _api_secret; 50 | std::vector _available_symbols; 51 | std::mutex _mux; 52 | unsigned long long int _request_counter = 0; 53 | 54 | const std::map _minimumLimits = { 55 | // https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size- 56 | {"REP", 0.3}, {"XBT", 0.002}, {"BTC", 0.002}, {"BCH", 0.002}, 57 | {"DASH", 0.03}, {"DOGE", 3000}, {"EOS", 3}, {"ETH", 0.02}, 58 | {"ETC", 0.3}, {"GNO", 0.03}, {"ICN", 2}, {"LTC", 0.1}, 59 | {"MLN", 0.1}, {"XMR", 0.1}, {"XRP", 30}, {"XLM", 300}, 60 | {"ZEC", 0.03}, {"USDT", 5}}; 61 | 62 | const std::map _maxPrecision = { 63 | {currency_pair_t("BCH", "XBT"), 5}, 64 | {currency_pair_t("BCH", "EUR"), 1}, 65 | {currency_pair_t("BCH", "USD"), 1}, 66 | {currency_pair_t("XBT", "CAD"), 1}, 67 | {currency_pair_t("XBT", "EUR"), 1}, 68 | {currency_pair_t("XBT", "JPY"), 0}, 69 | {currency_pair_t("XBT", "USD"), 1}, 70 | {currency_pair_t("DASH", "XBT"), 5}, 71 | {currency_pair_t("DASH", "EUR"), 2}, 72 | {currency_pair_t("DASH", "USD"), 2}, 73 | {currency_pair_t("DOGE", "XBT"), 8}, 74 | {currency_pair_t("EOS", "XBT"), 7}, 75 | {currency_pair_t("EOS", "ETH"), 6}, 76 | {currency_pair_t("ETC", "XBT"), 6}, 77 | {currency_pair_t("ETC", "ETH"), 5}, 78 | {currency_pair_t("ETC", "EUR"), 3}, 79 | {currency_pair_t("ETC", "USD"), 3}, 80 | {currency_pair_t("ETH", "XBT"), 5}, 81 | {currency_pair_t("ETH", "CAD"), 2}, 82 | {currency_pair_t("ETH", "EUR"), 2}, 83 | {currency_pair_t("ETH", "JPY"), 0}, 84 | {currency_pair_t("ETH", "USD"), 2}, 85 | {currency_pair_t("GNO", "XBT"), 5}, 86 | {currency_pair_t("GNO", "ETH"), 4}, 87 | {currency_pair_t("ICN", "XBT"), 7}, 88 | {currency_pair_t("ICN", "ETH"), 6}, 89 | {currency_pair_t("LTC", "XBT"), 6}, 90 | {currency_pair_t("LTC", "EUR"), 2}, 91 | {currency_pair_t("LTC", "USD"), 2}, 92 | {currency_pair_t("MLN", "XBT"), 6}, 93 | {currency_pair_t("MLN", "ETH"), 5}, 94 | {currency_pair_t("REP", "XBT"), 6}, 95 | {currency_pair_t("REP", "ETH"), 5}, 96 | {currency_pair_t("REP", "EUR"), 3}, 97 | {currency_pair_t("USDT", "USD"), 4}, 98 | {currency_pair_t("XLM", "XBT"), 8}, 99 | {currency_pair_t("XMR", "XBT"), 6}, 100 | {currency_pair_t("XMR", "EUR"), 2}, 101 | {currency_pair_t("XMR", "USD"), 2}, 102 | {currency_pair_t("XRP", "XBT"), 8}, 103 | {currency_pair_t("XRP", "EUR"), 5}, 104 | {currency_pair_t("XRP", "USD"), 5}, 105 | {currency_pair_t("ZEC", "XBT"), 5}, 106 | {currency_pair_t("ZEC", "EUR"), 2}, 107 | {currency_pair_t("ZEC", "USD"), 2}}; 108 | 109 | // Returns available symbols 110 | std::vector _symbols(); 111 | 112 | // Kraken uses XBT while other uses BTC. 113 | // Replace inputs symbol BTC with XBT 114 | void _sanitize_pair(currency_pair_t& pair); 115 | 116 | // Returns the minimum amount tradable for the specified currency 117 | double _minTradable(const std::string& symbol); 118 | 119 | // Converts a XXXYYY string in a currenty_pair_t, if XXX and YYY are known 120 | // symbols 121 | currency_pair_t _str2pair(std::string str); 122 | 123 | // _nonce = [0prefix || timestamp] || [nanoseconds] 124 | std::string _nonce(); 125 | 126 | // base64encode( 127 | // hmac_sha512(path + sha256(nonce + postdata), 128 | // base64decode(_api_key)) 129 | // ) 130 | std::string _sign(const std::string& path, const std::string& nonce, 131 | const std::string& postdata) const; 132 | 133 | // Authenticated post request 134 | json _request(std::string method, 135 | std::vector> params); 136 | 137 | static void _throw_error_if_any(const json& res) 138 | { 139 | try { 140 | Thrower::_throw_error_if_any(res); 141 | } 142 | catch (const response_error& e) { 143 | // Kraken is shit and even when status code should be 500 144 | // it might return a status code 200 with a json error 145 | // If the error is present thrower::_throw_err_if_any 146 | // will throw a response error, even if the request failed 147 | // not because of the malformed url request but because the 148 | // server is busy, or unavailable. 149 | // 150 | // In this case a server error must be thrown, not a response error 151 | auto message = std::string(e.what()); 152 | auto found = message.find("EService:Unavailable"); 153 | if (found != std::string::npos) { 154 | throw server_error(message); 155 | } 156 | found = message.find("Service:Busy"); 157 | if (found != std::string::npos) { 158 | throw server_error(message); 159 | } 160 | 161 | throw e; 162 | } 163 | } 164 | 165 | public: 166 | Kraken() {} 167 | Kraken(std::string api_key, std::string api_secret) 168 | : _api_key(api_key), _api_secret(api_secret) 169 | { 170 | } 171 | ~Kraken() {} 172 | 173 | /* Get server time 174 | * URL: https://api.kraken.com/0/public/Time 175 | * 176 | * Result: Server's time */ 177 | std::time_t time() const; 178 | 179 | /* Get asset info 180 | * URL: https://api.kraken.com/0/public/Assets 181 | * Allows anyone to get a list of all the currencies that Kraken 182 | * currently supports at any given time. The list will include the name, 183 | * symbol, availability status, and an icon link for each. */ 184 | std::map coins() override; 185 | 186 | /* Gets the current deposit info set by Kraken for the 187 | * specified currency. */ 188 | deposit_info_t depositInfo(std::string currency) override; 189 | 190 | /* This gets the market info (pair, rate, limit, minimum limit, miner fee). 191 | */ 192 | std::vector info() override; 193 | 194 | /* This gets the market info (pair, limit, minimum limit, miner fee) 195 | * for the spcified currency. */ 196 | market_info_t info(currency_pair_t) override; 197 | 198 | /* This gets the account balance, amount for every currency */ 199 | std::map balance() override; 200 | 201 | /* This gets the account balance for the specified currency */ 202 | double balance(std::string currency) override; 203 | 204 | /* This gets the ticker for the specified pair at the current time */ 205 | ticker_t ticker(currency_pair_t) override; 206 | 207 | /* This get the order book for the specicified pair */ 208 | std::vector orderBook(currency_pair_t) override; 209 | 210 | /* This get the complete closed orders */ 211 | std::vector closedOrders() override; 212 | 213 | /* This get the complete open orders */ 214 | std::vector openOrders() override; 215 | 216 | /* This adds an order using only the meaningful fields of order and filling 217 | * the remeaning fields once the order has been placed */ 218 | void place(order_t&) override; 219 | 220 | /* This cancel the specified order idientified by order.txid */ 221 | void cancel(order_t&) override; 222 | }; // namespace at 223 | 224 | } // end namespace at 225 | 226 | #endif // AT_KRAKEN_H_ 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2017 Paolo Galeone . All Rights Reserved. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAT: Open Source Algorithmic Trading Library 2 | 3 | OpenAT provides an easy to use C++ interface for working with (crypto-)currencies markets and exchanges. 4 | 5 | The aim is to give to the user the possibility to build it's own generic (crypto-)currency trading bot or daemon (check out an example of crypto trading daemon and currency price monitor (WIP!): [Openatd: OpenAT Daemon](https://github.com/galeone/openatd/). 6 | 7 | OpenAT is divided in 3 different parts: 8 | 9 | 1. **Market**: place/cancel orders, monitor order book, access history of placed/cancelled orders, global balance, balance per coin, ... 10 | 2. **Global market data monitoring**: crypto currency price, volume, markets, fiat value, ... 11 | 3. **Exchange**: change a currency for another currency without defining the price (shapeshift like) 12 | 13 | Everything using a strong typing system: every request returns a well defined type, the request to the markets are created using C++ structure: you will never fill a JSON field manually. 14 | 15 | Have a look at [`types.hpp`](https://github.com/galeone/openat/blob/master/include/at/types.hpp). 16 | 17 | [This project is in early stage and needs your help!](https://github.com/galeone/openat#contributing) 18 | 19 | ## Examples 20 | 21 | The best way to understand how OpenAT works is looking at the examples. 22 | 23 | ### Market 24 | 25 | Every implemented market satisfies the contract with the [Market](https://github.com/galeone/openat/blob/master/include/at/market.hpp) interface: you can write code that works with a general `Market` and just use any available implementation. 26 | 27 | #### Market: available coins 28 | 29 | ```cpp 30 | // A call to `coins()` returns `std::map` where the key is the name of the coin 31 | // and the value is a `coin_t` type, which contains basic informations about the coin 32 | // like t's name, the symbol and it's status 33 | 34 | auto coins = market->coins(); 35 | for(const auto& pair : coins) { 36 | auto coin = pair.second; 37 | std::cout << "name: " << coin.name << " symbol: " << coin.symbol << " status: " 38 | << coin.status << "\n"; 39 | } 40 | ``` 41 | 42 | #### Market: available pairs and pair info 43 | 44 | ```cpp 45 | // Given the market variable `market` 46 | // market->info() returns a std::vector where each entry of the 47 | // vector contains the information about any available pair in the market 48 | 49 | auto pair_info = market->info(); 50 | for(const auto& info : pair_info) { 51 | std::cout << "pair: " << info.pair << " " << "limits: " << info.limit << " fees: " 52 | << "maker: " << info.maker_fee << " taker: " << info.taker_fee << "\n"; 53 | } 54 | ``` 55 | 56 | You can specify the pair you're interested in, hence: 57 | 58 | ```cpp 59 | // information about the ETH/EUR pair 60 | auto info = market->info(currency_pair_t("eth", "eur"); 61 | std::cout << "pair: " << info.pair << " " << "limits: " << info.limit << " fees: " 62 | << "maker: " << info.maker_fee << " taker: " << info.taker_fee << "\n"; 63 | ``` 64 | 65 | #### Market: ticker per pair 66 | 67 | ```cpp 68 | // given a certain pair, obtain the ticker information 69 | auto ticker = market->ticker(currency_pair_t("eth", "eur")); 70 | 71 | // ticker is a struct with 2 `quotation_t` fileds: bid and ask 72 | 73 | std::cout << "bid:\n\price: " << ticker.bid.price << " amount: " << ticker.bid.amount << " time: " << ticker.bid.time << "\n"; 74 | 75 | std::cout << "ask:\n\price: " << ticker.ask.price << " amount: " << ticker.ask.amount << " time: " << ticker.ask.time << "\n"; 76 | 77 | ``` 78 | 79 | ##### Market: order book per pair 80 | 81 | ```cpp 82 | // a call to the `orderBook(pair)` returns a std::vector 83 | auto order_book = market->orderBook(currency_pair_t("eth", "eur")); 84 | for(const auto& order : order_book) { 85 | // handle the `ticker_t` fields, see previous example 86 | } 87 | ``` 88 | 89 | ##### Market: balance per currency and global balance 90 | 91 | ```cpp 92 | // The method balance has 2 versions: 93 | // 1. `std::map balance()` which returns the pair `symbol`,`balance` 94 | auto balances = market->balance(); 95 | for(const auto& pair : balances) { 96 | std::cout << pair.first << ": " << pair.second << "\n"; 97 | } 98 | // 2. `double balance(std::string symbol)` which returns the balance for the specified symbol 99 | 100 | auto btc = market->balance("BTC"); 101 | std::cout << "BTC: " << btc << "\n"; 102 | ``` 103 | 104 | ##### Market: list of open and closed orders 105 | 106 | ```cpp 107 | // We can get the list of closed orders (a std::vector) calling 108 | auto closed_orders = market->closedOrders(); 109 | for(const auto order : closed_orders) { 110 | std::cout << "txid: " << order.txid 111 | << " status: " << order.status 112 | << " type: " << order.type 113 | << " action: " << order.action 114 | << " pair: " << order.pair 115 | << " open time: " << order.open 116 | << " close time: " << order.close 117 | << " volume: " << order.volume 118 | << " cost: " << order.cost 119 | << " fee: " << order.fee 120 | << " price: " << order.price; 121 | } 122 | 123 | // We can get the list of open orders calling 124 | auto open_orders = market->openOrders(); 125 | for(const auto order : open_orders) { 126 | std::cout << "txid: " << order.txid 127 | << " status: " << order.status 128 | << " type: " << order.type 129 | << " action: " << order.action 130 | << " pair: " << order.pair 131 | << " open time: " << order.open 132 | << " close time: " << order.close 133 | << " volume: " << order.volume 134 | << " cost: " << order.cost 135 | << " fee: " << order.fee 136 | << " price: " << order.price; 137 | } 138 | 139 | ``` 140 | 141 | ##### Market: place and cancel order 142 | 143 | ```cpp 144 | // Given an open order, close it (use the txid to identify the order on the market) 145 | auto close_me = open_orders[0]; 146 | market->close(close_me); 147 | 148 | // Place a limit order for a certain pair 149 | // let's buy a litecoin with EUR 150 | order_t limit; 151 | limit.pair = currency_pair_t("LTC", "EUR"); 152 | limit.volume = 1; 153 | limit.action = order_action_t::buy; // BUY 154 | limit.type = order_type_t::limit; // limit order 155 | // place 156 | try { 157 | market->place(limit); 158 | } 159 | catch(...) { 160 | // handle the exception in case of error 161 | } 162 | 163 | // Place a market order (no specify the price) 164 | // half ltc per eur 165 | order_t market; 166 | market.pair = currency_pair_t("LTC", "EUR"); 167 | market.volume = 0.5; 168 | market.action = order_action_t::buy; 169 | market.type = order_type_t::market; 170 | // place 171 | try { 172 | market->place(market); 173 | } catch(...) { 174 | // handle errors 175 | } 176 | 177 | // Sell LTC for eur, market order 178 | order_t market; 179 | market.pair = currency_pair_t("LTC", "EUR"); 180 | market.volume = 0.5; 181 | market.action = order_action_t::sell; // SELL order 182 | market.type = order_type_t::market; 183 | // place 184 | try { 185 | market->place(market); 186 | } catch(...) { 187 | // handle errors 188 | } 189 | 190 | ``` 191 | 192 | At the moment of writing the only implementation of the Market interface is for https://kraken.com/. But pull requests for any other market are more than welcome! 193 | 194 | ## Global market data monitor 195 | 196 | OpenAT contains a client for the [coinmarketcap.com](https://coinmarketcap.com) API and also it's able to parse the webpage of the currencies in order to extract information about certain currencies that are available only in the website and not in the API. 197 | 198 | The interface is easy and intuitive: 199 | 200 | ```cpp 201 | // ticker returns a vector of `cm_ticker_t`, where `cm` stands for cumulative 202 | // The struct it's easy, so no further explaination are required: 203 | /* 204 | typedef struct { 205 | std::string id, name, symbol; 206 | int rank; 207 | double price_usd, price_btc; 208 | long long int day_volume_usd, market_cap_usd, available_supply, 209 | total_supply; 210 | float percent_change_1h, percent_change_24h, percent_change_7d; 211 | std::time_t last_updated; 212 | } cm_ticker_t; 213 | */ 214 | std::vector ticker(); 215 | 216 | // The call to ticer(uint32_t limit) it's the same of limit() but returns only 217 | // the first `limit` currencies 218 | std::vector ticker(uint32_t limit); 219 | 220 | // ticker(std::string currency_symbol) returns a single `cm_ticker_t` 221 | // for the specified currency 222 | cm_ticker_t ticker(std::string currency_symbol); 223 | 224 | // markets(std::string currency_symbol) returns the information parsed from the website 225 | // about the markets where the specified symbol is traded on. 226 | // The cm_market_t struct is: 227 | /* 228 | typedef struct { 229 | std::string name; 230 | currency_pair_t pair; 231 | long long int day_volume_usd; 232 | double price_usd; 233 | float percent_volume; 234 | std::time_t last_updated; 235 | } cm_market_t; 236 | */ 237 | std::vector markets(std::string currency_symbol); 238 | 239 | // A call to global() returns the overall information about the cryptomarket 240 | // gm_data_t is: 241 | /* 242 | typedef struct { 243 | long long int total_market_cap_usd, total_24h_volume_usd; 244 | float bitcoin_percentage_of_market_cap; 245 | int active_currencies, active_assets, active_markets; 246 | } gm_data_t; 247 | */ 248 | gm_data_t global(); 249 | ``` 250 | 251 | ## Exchange 252 | 253 | OpenAT contains also a client for https://shapeshift.com/. The [`shapeshift.hpp`](https://github.com/galeone/openat/blob/master/include/at/shapeshift.hpp) file is documented (and it's nothing more than the shapeshift API documentation), you can use it as documentation. 254 | 255 | 256 | ## Build 257 | 258 | Clone the repository and make sure to clone the submodules too: 259 | 260 | ``` 261 | git clone --recursive https://github.com/galeone/openat 262 | ``` 263 | 264 | ### Building on (Arch)linux 265 | 266 | ``` 267 | # Install the required dependencies 268 | 269 | sudo pacman -S spdlog nlohmann-json gumbo-parser sqlite 270 | # install gumbo query to your system 271 | cd libs/gumbo/query/build 272 | cmake .. 273 | make 274 | sudo make install 275 | # if there are problem with the static library, remove the last line 276 | # `libfind_process(Gumbo)` 277 | # from libs/gumbo/query/cmake/FindGumbo.cmake 278 | cd - 279 | # Install curlpp, or with yay -S curlpp 280 | # or using the submodule 281 | cd libs/curlpp 282 | mkdir build 283 | cd build 284 | cmake .. 285 | make 286 | sudo make install 287 | cd - 288 | # build openat 289 | mkdir build 290 | cd build 291 | cmake .. 292 | CC=clang CXX=clang++ make 293 | ``` 294 | 295 | ### Building on macOS 296 | 297 | Install the needed dependencies and remember to link them: 298 | ``` 299 | brew install gcc 300 | brew install openssl sqlite 301 | brew link sqlite --force 302 | ``` 303 | 304 | For security reasons, HomeBrew doesn't allow to symlink OpenSSL to /usr/local. 305 | You will need to manually tell cmake where to find these libraries. Make sure 306 | to provide cmake with the correct version of the OpenSSL (1.1). 307 | 308 | You can now proceed and build `at`: 309 | ``` 310 | mkdir build && cd build 311 | CC=gcc-7 CXX=g++-7 cmake \ 312 | -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1/ \ 313 | -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl@1.1/include \ 314 | -DOPENSSL_CRYPTO_LIBRARY=/usr/local/opt/openssl@1.1/lib/libcrypto.dylib .. 315 | make 316 | ``` 317 | ## Test 318 | 319 | ``` 320 | cd build 321 | # ID is a test defined by gumbo-query that somehow appears into 322 | # the tests of the current project. Exclue it using cmame -E (exclude) ID 323 | ctest -E ID 324 | ``` 325 | 326 | ## Embed it as a submodule using CMake 327 | 328 | Copy or clone the project into the `libs` folder, than add to your `CMakeLists.txt`: 329 | 330 | ```cmake 331 | # Build OpenAT 332 | set(OPENAT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/libs/openat") 333 | set(OPENAT_INCLUDE_DIR "${OPENAT_SOURCE_DIR}/include") 334 | add_subdirectory(${OPENAT_SOURCE_DIR}) 335 | 336 | # Add the OPENAT_INCLUDE_DIR and the exported variable 337 | # OPENAT_REQUIRED_INCLUDES to the `include_directories` section 338 | include_directories( 339 | # ... other includes 340 | ${OPENAT_INCLUDE_DIR} 341 | ${OPENAT_REQUIRED_INCLUDES} 342 | ) 343 | 344 | # Add the `openat` project to the `target_link_libraries`: 345 | target_link_libraries(project_name LINK_PUBLIC 346 | openat 347 | # other linked libraries... 348 | ) 349 | ``` 350 | 351 | # Contributing 352 | 353 | The best way to contribute to OpenAT is via pull request and issues, here on GitHub. 354 | 355 | There are a lot of things to do to improve OpenAT (and [openatd: OpenAT Daemon](https://github.com/galeone/openatd/) too!): 356 | 357 | 1. **Add implementations of the Market interface**: this is the most important part. More market are implemented and more OpenAT can be useful. 358 | With more implementation with can easily write trading bot for arbitrage in the easiest way ever. 359 | 2. **Add data sources**: coinmarketcap is a good data source and it works well. But we can try to make OpenAT smarter, collecting any other data that talks about crypto currencies (just think, train a ML model with the stream of collected tweets and news feed... we can do sentiment analysis and many other cool things: a lot of (high quality) data is everything. 360 | 3. **Improve the documentation**: at the time of writing, the only documentation is the README and the comments in the header files. We can do better. 361 | 4. **Unit test**: test the server response it's something hard (especially when you work with idiotic APIs like the shapeshift ones, where a field change it's type from request to request): we have to create a mock server and test everything. 362 | 5. **OMG you're using double and not integers everywhere!**: yes you're right. But since OpenAT basically collects data and send request to API that accepts JSON, using doubles and integer changes nothing (you have to convert the data to a string in every case). But if you want to change OpenAT making it use integer and the information about the number of meaningful digits you're welcome. 363 | 364 | Also, if you want to donate instead of contributing with code, feel free do donate ETH at this address: `0xa1d283e77f8308559f62909526ccb2d9444d96fc` 365 | 366 | 367 | -------------------------------------------------------------------------------- /src/at/kraken.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #include 16 | 17 | namespace at { 18 | 19 | // private methods 20 | 21 | std::string Kraken::_nonce() 22 | { 23 | std::ostringstream oss; 24 | 25 | timespec tp; 26 | if (clock_gettime(CLOCK_REALTIME, &tp) != 0) { 27 | oss << "clock_gettime() failed: " << strerror(errno); 28 | throw std::runtime_error(oss.str()); 29 | } 30 | else { 31 | // format output string 32 | oss << std::setfill('0') << std::setw(10) 33 | << (tp.tv_sec + _request_counter) << std::setw(9) 34 | << (tp.tv_nsec + _request_counter); 35 | } 36 | return oss.str(); 37 | } 38 | 39 | std::string Kraken::_sign(const std::string& path, const std::string& nonce, 40 | const std::string& postdata) const 41 | { 42 | std::vector data(path.begin(), path.end()); 43 | std::vector nonce_postdata = 44 | at::crypt::sha256(nonce + postdata); 45 | data.insert(data.end(), nonce_postdata.begin(), nonce_postdata.end()); 46 | return at::crypt::base64_encode( 47 | at::crypt::hmac_sha512(data, at::crypt::base64_decode(_api_secret))); 48 | } 49 | 50 | std::vector Kraken::_symbols() 51 | { 52 | if (_available_symbols.size() == 0) { 53 | for (const auto& pair : _minimumLimits) { 54 | _available_symbols.push_back(pair.first); 55 | } 56 | return _available_symbols; 57 | } 58 | return _available_symbols; 59 | } 60 | 61 | // Kraken uses XBT while other uses BTC. 62 | // Replace inputs symbol BTC with XBT 63 | void Kraken::_sanitize_pair(currency_pair_t& pair) 64 | { 65 | if (pair.first == "BTC") { 66 | pair.first = "XBT"; 67 | } 68 | if (pair.second == "BTC") { 69 | pair.second = "XBT"; 70 | } 71 | } 72 | 73 | double Kraken::_minTradable(const std::string& symbol) 74 | { 75 | auto it = _minimumLimits.find(symbol); 76 | if (it != _minimumLimits.end()) { 77 | return it->second; 78 | } 79 | if (symbol[0] == 'X' && symbol.length() == 4) { // XXBTC 80 | it = _minimumLimits.find(symbol.substr(1, 3)); 81 | if (it != _minimumLimits.end()) { 82 | return it->second; 83 | } 84 | } 85 | return 0; 86 | } 87 | 88 | currency_pair_t Kraken::_str2pair(std::string str) 89 | { 90 | std::string first = str.substr(0, 3); 91 | auto symbols = _symbols(); 92 | if (std::find(symbols.begin(), symbols.end(), first) != symbols.end()) { 93 | return currency_pair_t(first, str.substr(3, str.size())); 94 | } 95 | first = str.substr(0, 4); 96 | if (std::find(symbols.begin(), symbols.end(), first) != symbols.end()) { 97 | return currency_pair_t(first, str.substr(4, str.size())); 98 | } 99 | throw std::runtime_error("Unable to extract pair from str" + str); 100 | } 101 | 102 | json Kraken::_request(std::string method, 103 | std::vector> params) 104 | { 105 | if (_api_key.empty() || _api_secret.empty()) { 106 | throw std::runtime_error("API KEY/SECRET required for private methods"); 107 | } 108 | 109 | // acquire lock for mutual exclusive access of _request_counter: 110 | // Nonce counter should be incremented AFTER the server knows the current 111 | // request. Hence the _request method that calls the _nonce methdo acquires 112 | // the lock on the mutex and locks any other request until a response has 113 | // not been received by the server 114 | std::unique_lock lock(_mux); 115 | ++_request_counter; 116 | auto private_method = "private/" + method; 117 | auto path = "/" + _version + "/" + private_method; 118 | auto nonce = _nonce(); 119 | params.push_back({"nonce", nonce}); 120 | 121 | std::list headers; 122 | headers.push_back("API-Key: " + _api_key); 123 | headers.push_back("API-Sign: " + _sign(path, nonce, [&]() { 124 | std::string ret; 125 | for (const auto& key_value : params) { 126 | ret.append(key_value.first + "=" + 127 | key_value.second + "&"); 128 | } 129 | ret.pop_back(); 130 | return ret; 131 | }())); 132 | Request req(headers); 133 | return req.post(_host + private_method, params); 134 | } 135 | 136 | // end private methods 137 | 138 | std::time_t Kraken::time() const 139 | { 140 | Request req; 141 | json res = req.get(_host + "public/Time"); 142 | _throw_error_if_any(res); 143 | res = res["result"]; 144 | std::time_t timestamp = res.at("unixtime").get(); 145 | return timestamp; 146 | } 147 | 148 | std::map Kraken::coins() 149 | { 150 | // "BCH":{"aclass":"currency","altname":"BCH","decimals":10,"display_decimals":5} 151 | Request req; 152 | json res = req.get(_host + "public/Assets?aclass=currency"); 153 | _throw_error_if_any(res); 154 | res = res["result"]; 155 | std::map ret; 156 | for (auto it = res.begin(); it != res.end(); ++it) { 157 | auto value = *it; 158 | auto coin = coin_t{.name = value["altname"], 159 | .symbol = it.key(), 160 | .status = "available"}; 161 | ret[value["altname"]] = coin; 162 | } 163 | return ret; 164 | } 165 | 166 | std::vector Kraken::info() 167 | { 168 | Request req; 169 | json res = req.get(_host + "public/AssetPairs"); 170 | _throw_error_if_any(res); 171 | 172 | res = res["result"]; 173 | std::vector markets; 174 | for (auto it = res.begin(); it != res.end(); ++it) { 175 | auto market = *it; 176 | // Skip darkpool markets 177 | if (market.at("altname").get().rfind(".d") != 178 | std::string::npos) { 179 | continue; 180 | } 181 | 182 | markets.push_back(market_info_t{ 183 | .pair = currency_pair_t(market["base"], market["quote"]), 184 | .limit = min_max_t{.min = _minTradable(market["base"]), 185 | .max = std::numeric_limits::infinity()}, 186 | .maker_fee = market["fees_maker"][0][1].get(), // low 187 | .taker_fee = market["fees"][0][1].get()}); // high 188 | } 189 | return markets; 190 | } 191 | 192 | market_info_t Kraken::info(currency_pair_t pair) 193 | { 194 | _sanitize_pair(pair); 195 | Request req; 196 | std::ostringstream url; 197 | url << _host; 198 | url << "public/AssetPairs?pair="; 199 | // << pair == c1_c2. Kraken needs c1c2, thus 200 | url << pair.first; 201 | url << pair.second; 202 | json res = req.get(url.str()); 203 | _throw_error_if_any(res); 204 | json market = res["result"].begin().value(); 205 | 206 | return market_info_t{ 207 | .pair = pair, 208 | .limit = min_max_t{.min = _minTradable(market["base"]), 209 | .max = std::numeric_limits::infinity()}, 210 | .maker_fee = market["fees_maker"][0][1].get(), 211 | .taker_fee = market["fees"][0][1].get()}; 212 | } 213 | 214 | deposit_info_t Kraken::depositInfo(std::string currency) 215 | { 216 | toupper(currency); 217 | json res = _request("DepositMethods", {{"asset", currency}}); 218 | _throw_error_if_any(res); 219 | res = res["result"][0]; 220 | // [{"fee":"0.0000000000","gen-address":true,"limit":false,"method":"Zcash 221 | // (Transparent)"}] 222 | double limit = std::numeric_limits::infinity(); 223 | try { 224 | limit = std::stod(res.at("limit").get()); 225 | } 226 | catch (const std::domain_error&) { 227 | // if here, limit = false = no limits 228 | } 229 | return deposit_info_t{ 230 | .limit = min_max_t{.min = _minTradable(currency), .max = limit}, 231 | .fee = std::stod(res.at("fee").get()), 232 | .currency = currency, 233 | .method = res.at("method").get(), 234 | }; 235 | } 236 | 237 | std::map Kraken::balance() 238 | { 239 | json res = _request("Balance", {}); 240 | _throw_error_if_any(res); 241 | res = res["result"]; 242 | std::map ret; 243 | 244 | for (auto it = res.begin(); it != res.end(); ++it) { 245 | ret[it.key()] = std::stod((*it).get()); 246 | } 247 | return ret; 248 | } 249 | 250 | double Kraken::balance(std::string currency) 251 | { 252 | toupper(currency); 253 | auto balances = balance(); 254 | if (balances.find(currency) != balances.end()) { 255 | return balances[currency]; 256 | } 257 | if (currency.size() < 4) { 258 | auto xcurrency = "X" + currency; 259 | if (balances.find(xcurrency) != balances.end()) { 260 | return balances[xcurrency]; 261 | } 262 | auto zcurrency = "Z" + currency; 263 | if (balances.find(zcurrency) != balances.end()) { 264 | return balances[zcurrency]; 265 | } 266 | } 267 | return 0; 268 | } 269 | 270 | ticker_t Kraken::ticker(currency_pair_t pair) 271 | { 272 | _sanitize_pair(pair); 273 | 274 | Request req; 275 | std::ostringstream url; 276 | url << _host; 277 | url << "public/Ticker?pair="; 278 | // << pair == c1_c2. Kraken needs c1c2, thus 279 | url << pair.first; 280 | url << pair.second; 281 | json res = req.get(url.str()); 282 | _throw_error_if_any(res); 283 | res = res["result"].begin().value(); 284 | auto now = 285 | std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 286 | auto ask = quotation_t{ 287 | .price = std::stod(res["a"][0].get()), 288 | .amount = std::stod(res["a"][2].get()), 289 | .time = now, 290 | }; 291 | auto bid = quotation_t{ 292 | .price = std::stod(res["b"][0].get()), 293 | .amount = std::stod(res["b"][2].get()), 294 | .time = now, 295 | }; 296 | 297 | ticker_t ret; 298 | ret.ask = ask; 299 | ret.bid = bid; 300 | return ret; 301 | } 302 | 303 | std::vector Kraken::orderBook(currency_pair_t pair) 304 | { 305 | _sanitize_pair(pair); 306 | Request req; 307 | std::ostringstream url; 308 | url << _host; 309 | url << "public/Depth?pair="; 310 | // << pair == c1_c2. Kraken needs c1c2, thus 311 | url << pair.first; 312 | url << pair.second; 313 | json res = req.get(url.str()); 314 | _throw_error_if_any(res); 315 | res = res["result"].begin().value(); 316 | 317 | std::vector ret; 318 | size_t idx = 0; 319 | for (auto it = res["asks"].begin(); it != res["asks"].end(); ++idx, ++it) { 320 | auto ask = quotation_t{ 321 | .price = std::stod(res["asks"][idx][0].get()), 322 | .amount = std::stod(res["asks"][idx][1].get()), 323 | .time = 324 | static_cast(res["asks"][idx][2].get()), 325 | }; 326 | auto bid = quotation_t{ 327 | .price = std::stod(res["bids"][idx][0].get()), 328 | .amount = std::stod(res["bids"][idx][1].get()), 329 | .time = 330 | static_cast(res["bids"][idx][2].get()), 331 | }; 332 | 333 | ticker_t ticker; 334 | ticker.ask = ask; 335 | ticker.bid = bid; 336 | ret.push_back(ticker); 337 | } 338 | return ret; 339 | } 340 | 341 | std::vector Kraken::closedOrders() 342 | { 343 | json res = _request("ClosedOrders", {}); 344 | _throw_error_if_any(res); 345 | res = res["result"]["closed"]; 346 | std::vector ret; 347 | 348 | for (auto it = res.begin(); it != res.end(); it++) { 349 | auto row = it.value(); 350 | order_t order; 351 | order.txid = it.key(); 352 | order.status = row.at("status").get(); 353 | order.open = row.at("opentm").get(); 354 | order.close = row.at("closetm").get(); 355 | order.pair = _str2pair(row["descr"]["pair"].get()); 356 | order.action = row["descr"]["type"].get(); 357 | order.type = row["descr"]["ordertype"].get(); 358 | order.volume = std::stod(row.at("vol_exec").get()); 359 | order.cost = std::stod(row.at("cost").get()); 360 | order.fee = std::stod(row.at("fee").get()); 361 | order.price = std::stod(row["descr"]["price"].get()); 362 | ret.push_back(order); 363 | } 364 | return ret; 365 | } 366 | 367 | std::vector Kraken::openOrders() 368 | { 369 | json res = _request("OpenOrders", {}); 370 | _throw_error_if_any(res); 371 | res = res["result"]["open"]; 372 | std::vector ret; 373 | 374 | for (auto it = res.begin(); it != res.end(); it++) { 375 | auto row = it.value(); 376 | order_t order; 377 | order.txid = it.key(); 378 | order.status = row.at("status").get(); 379 | order.open = row.at("opentm").get(); 380 | order.close = 0; 381 | order.pair = _str2pair(row["descr"]["pair"].get()); 382 | order.action = row["descr"]["type"].get(); 383 | order.type = row["descr"]["ordertype"].get(); 384 | order.volume = std::stod(row.at("vol").get()); 385 | order.cost = std::stod(row.at("cost").get()); 386 | order.fee = std::stod(row.at("fee").get()); 387 | order.price = std::stod(row["descr"]["price"].get()); 388 | ret.push_back(order); 389 | } 390 | return ret; 391 | } 392 | 393 | void Kraken::place(order_t& order) 394 | { 395 | _sanitize_pair(order.pair); 396 | std::vector> params; 397 | params.push_back({"pair", order.pair.first + order.pair.second}); 398 | std::stringstream ss; 399 | ss << order.action; 400 | params.push_back({"type", ss.str()}); 401 | ss.str(""); 402 | ss.clear(); 403 | ss << order.type; 404 | params.push_back({"ordertype", ss.str()}); 405 | params.push_back({"volume", std::to_string(order.volume)}); 406 | 407 | switch (order.type) { 408 | case at::order_type_t::market: { 409 | if (order.volume <= 0) { 410 | throw std::runtime_error("order.volume can't be <= 0"); 411 | } 412 | break; 413 | } 414 | case at::order_type_t::limit: { 415 | if (order.volume * order.price <= 0) { 416 | throw std::runtime_error( 417 | "order.volume * order.price can't be <= 0"); 418 | } 419 | ss.str(""); 420 | ss.clear(); 421 | // hopefully a precision of 2 is not too much for the current 422 | // pair. Kraken just give the precision for certain pairs 423 | // but other pairs have no specification at all. 424 | int precision = 2; 425 | try { 426 | precision = _maxPrecision.at(order.pair); 427 | } 428 | catch (const std::out_of_range&) { 429 | } 430 | ss << std::setprecision(precision) << order.price; 431 | params.push_back({"price", ss.str()}); 432 | break; 433 | } 434 | } 435 | 436 | json res = _request("AddOrder", params); 437 | _throw_error_if_any(res); 438 | res = res["result"]; 439 | order.txid = res["txid"][0].get(); 440 | } 441 | 442 | void Kraken::cancel(order_t& order) 443 | { 444 | json res = _request("CancelOrder", {{"txid", order.txid}}); 445 | _throw_error_if_any(res); 446 | order = {}; 447 | } 448 | 449 | } // namespace at 450 | -------------------------------------------------------------------------------- /include/at/types.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Paolo Galeone . All Rights Reserved. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License.*/ 14 | 15 | #ifndef AT_TYPE_H_ 16 | #define AT_TYPE_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace at { 27 | 28 | static inline void toupper(std::string& str) 29 | { 30 | transform(str.begin(), str.end(), str.begin(), ::toupper); 31 | } 32 | 33 | static inline void tolower(std::string& str) 34 | { 35 | transform(str.begin(), str.end(), str.begin(), ::tolower); 36 | } 37 | 38 | // Remember: 39 | // Functions included in multiple source files must be inline 40 | // http://en.cppreference.com/w/cpp/language/inline 41 | // 42 | // Handle user defined types & json: 43 | // https://github.com/nlohmann/json#basic-usage 44 | 45 | using json = nlohmann::json; 46 | 47 | // Returns a std::string if field is a string 48 | // Otherwise returns the string "0.0" 49 | // Throws runtime_error otherwise 50 | inline std::string numeric_string(const json& field) 51 | { 52 | if (field.is_string()) { 53 | return field.get(); 54 | } 55 | if (field.is_null()) { 56 | return std::string("0.0"); 57 | } 58 | std::ostringstream stream; 59 | stream << "field " << field << " is not string or null"; 60 | throw std::runtime_error(stream.str()); 61 | } 62 | 63 | class currency_pair_t { 64 | private: 65 | std::pair _pair; 66 | 67 | public: 68 | std::string first, second; 69 | currency_pair_t() {} 70 | currency_pair_t(const std::string& first, const std::string& second) 71 | { 72 | std::string ucFirst = first; 73 | std::string ucSecond = second; 74 | toupper(ucFirst); 75 | toupper(ucSecond); 76 | _pair = std::pair(ucFirst, ucSecond); 77 | this->first = _pair.first; 78 | this->second = _pair.second; 79 | } 80 | 81 | std::string str() const { return first + "_" + second; } 82 | bool operator<(const currency_pair_t& pair) const 83 | { 84 | return str() < pair.str(); 85 | } 86 | 87 | bool operator==(const currency_pair_t& pair) const 88 | { 89 | return str() == pair.str(); 90 | } 91 | }; 92 | 93 | // overload of << between ostream and currency_pair_t 94 | inline std::ostream& operator<<(std::ostream& o, const currency_pair_t& pair) 95 | { 96 | return o << pair.str(); 97 | } 98 | 99 | inline void to_json(json& j, const currency_pair_t c) 100 | { 101 | j = json{c.first, c.second}; 102 | } 103 | inline void from_json(const json& j, currency_pair_t& c) 104 | { 105 | c.first = j.at(0).get(); 106 | c.second = j.at(1).get(); 107 | } 108 | 109 | typedef std::string hash_t; 110 | inline void to_json(json& j, const hash_t& a) { j = json::parse(a); } 111 | inline void from_json(const json& j, hash_t& a) { a = j.get(); } 112 | 113 | typedef struct { 114 | std::string name, symbol, status; 115 | } coin_t; 116 | inline void to_json(json& j, const coin_t& c) 117 | { 118 | j = json{{"name", c.name}, {"symbol", c.symbol}, {"status", c.status}}; 119 | } 120 | inline void from_json(const json& j, coin_t& c) 121 | { 122 | c.name = j.at("name").get(); 123 | c.symbol = j.at("symbol").get(); 124 | c.status = j.at("status").get(); 125 | } 126 | 127 | typedef struct { 128 | double min; 129 | double max; 130 | } min_max_t; 131 | 132 | inline void to_json(json& j, const min_max_t& d) 133 | { 134 | j = json{{"min", d.min}, {"max", d.max}}; 135 | } 136 | inline void from_json(const json& j, min_max_t& d) 137 | { 138 | d.min = j.at("min").get(); 139 | d.max = j.at("max").get(); 140 | } 141 | 142 | typedef struct { 143 | min_max_t limit; 144 | double fee; 145 | std::string currency; 146 | std::string method; 147 | } deposit_info_t; 148 | 149 | inline void to_json(json& j, const deposit_info_t& d) 150 | { 151 | j = json{{"limit", d.limit}, 152 | {"fee", d.fee}, 153 | {"currency", d.currency}, 154 | {"method", d.method}}; 155 | } 156 | inline void from_json(const json& j, deposit_info_t& d) 157 | { 158 | d.limit.max = j["limit"]["max"].get(); 159 | d.limit.min = j["limit"]["min"].get(); 160 | d.fee = j.at("fee").get(); 161 | d.currency = j.at("currency").get(); 162 | d.method = j.at("method").get(); 163 | } 164 | 165 | typedef struct { 166 | currency_pair_t pair; 167 | min_max_t limit; 168 | double rate; 169 | double miner_fee; 170 | } exchange_info_t; 171 | inline void to_json(json& j, const exchange_info_t& m) 172 | { 173 | j = json{{"pair", m.pair.str()}, 174 | {"limit", m.limit}, 175 | {"rate", m.rate}, 176 | {"miner_fee", m.miner_fee}}; 177 | } 178 | inline void from_json(const json& j, exchange_info_t& m) 179 | { 180 | m.limit = j.at("limit").get(); 181 | m.rate = j.at("rate").get(); 182 | m.miner_fee = j.at("miner_fee").get(); 183 | auto pair_str = j.at("pair").get(); 184 | std::size_t delimiter_it = pair_str.find_last_of("_"); 185 | if (delimiter_it != std::string::npos) { 186 | m.pair = currency_pair_t(pair_str.substr(0, delimiter_it), 187 | pair_str.substr(delimiter_it + 1)); 188 | } 189 | } 190 | 191 | typedef struct { 192 | currency_pair_t pair; 193 | min_max_t limit; 194 | double maker_fee, taker_fee; 195 | } market_info_t; 196 | inline void to_json(json& j, const market_info_t& m) 197 | { 198 | j = json{{"pair", m.pair.str()}, 199 | {"limit", m.limit}, 200 | {"taker_fee", m.taker_fee}, 201 | {"maker_fee", m.maker_fee}}; 202 | } 203 | inline void from_json(const json& j, market_info_t& m) 204 | { 205 | m.limit = j.at("limit").get(); 206 | m.maker_fee = j.at("maker_fee").get(); 207 | m.taker_fee = j.at("taker_fee").get(); 208 | auto pair_str = j.at("pair").get(); 209 | std::size_t delimiter_it = pair_str.find_last_of("_"); 210 | if (delimiter_it != std::string::npos) { 211 | m.pair = currency_pair_t(pair_str.substr(0, delimiter_it), 212 | pair_str.substr(delimiter_it + 1)); 213 | } 214 | } 215 | 216 | enum class deposit_status_t : char { 217 | no_deposists = 'a', 218 | initial, // initial 219 | received, // received 220 | complete, // = success 221 | settled, // settled 222 | pending, // pending 223 | failed, // failure 224 | partial, // partial 225 | expired, // experied 226 | }; 227 | 228 | inline std::ostream& operator<<(std::ostream& o, const deposit_status_t& s) 229 | { 230 | switch (s) { 231 | case deposit_status_t::no_deposists: 232 | return o << "no_deposists"; 233 | case deposit_status_t::initial: 234 | return o << "initial"; 235 | case deposit_status_t::received: 236 | return o << "received"; 237 | case deposit_status_t::complete: 238 | return o << "complete"; 239 | case deposit_status_t::settled: 240 | return o << "settled"; 241 | case deposit_status_t::pending: 242 | return o << "pending"; 243 | case deposit_status_t::failed: 244 | return o << "failed"; 245 | case deposit_status_t::partial: 246 | return o << "partial"; 247 | default: 248 | return o << "expired"; 249 | } 250 | } 251 | 252 | inline void to_json(json& j, const deposit_status_t& s) 253 | { 254 | std::stringstream o; 255 | o << s; 256 | j = json{o.str()}; 257 | } 258 | inline void from_json(const json& j, deposit_status_t& s) 259 | { 260 | auto val = j.get(); 261 | if (val == "no_deposists") { 262 | s = deposit_status_t::no_deposists; 263 | } 264 | else if (val == "initial") { 265 | s = deposit_status_t::initial; 266 | } 267 | else if (val == "received") { 268 | s = deposit_status_t::received; 269 | } 270 | else if (val == "complete" or val == "success") { 271 | s = deposit_status_t::complete; 272 | } 273 | else if (val == "settled") { 274 | s = deposit_status_t::settled; 275 | } 276 | else if (val == "pending") { 277 | s = deposit_status_t::pending; 278 | } 279 | else if (val == "failed") { 280 | s = deposit_status_t::failed; 281 | } 282 | else if (val == "partial") { 283 | s = deposit_status_t::partial; 284 | } 285 | else { 286 | s = deposit_status_t::expired; 287 | } 288 | } 289 | 290 | enum class tx_status_t : char { 291 | pending = 'A', // order pending book entry 292 | open, // open order 293 | closed, // closed order 294 | canceled, // order canceled 295 | expired, // order expired 296 | }; 297 | 298 | inline std::ostream& operator<<(std::ostream& o, const tx_status_t& s) 299 | { 300 | switch (s) { 301 | case tx_status_t::pending: 302 | return o << "pending"; 303 | case tx_status_t::open: 304 | return o << "open"; 305 | case tx_status_t::closed: 306 | return o << "closed"; 307 | case tx_status_t::canceled: 308 | return o << "canceled"; 309 | default: 310 | return o << "expired"; 311 | } 312 | } 313 | 314 | inline void to_json(json& j, const tx_status_t& s) 315 | { 316 | std::stringstream o; 317 | o << s; 318 | j = json{o.str()}; 319 | } 320 | inline void from_json(const json& j, tx_status_t& s) 321 | { 322 | auto val = j.get(); 323 | tolower(val); 324 | if (val == "pending") { 325 | s = tx_status_t::pending; 326 | } 327 | else if (val == "open") { 328 | s = tx_status_t::open; 329 | } 330 | else if (val == "closed") { 331 | s = tx_status_t::closed; 332 | } 333 | else if (val == "canceled") { 334 | s = tx_status_t::canceled; 335 | } 336 | else { 337 | s = tx_status_t::expired; 338 | } 339 | } 340 | 341 | enum class order_action_t : char { 342 | buy = 'B', 343 | sell, 344 | }; 345 | 346 | inline std::ostream& operator<<(std::ostream& o, const order_action_t& t) 347 | { 348 | switch (t) { 349 | case order_action_t::buy: 350 | return o << "buy"; 351 | case order_action_t::sell: 352 | return o << "sell"; 353 | } 354 | return o << "error"; 355 | } 356 | 357 | inline void to_json(json& j, const order_action_t& t) 358 | { 359 | std::stringstream o; 360 | o << t; 361 | j = json{o.str()}; 362 | } 363 | 364 | inline void from_json(const json& j, order_action_t& t) 365 | { 366 | auto val = j.get(); 367 | tolower(val); 368 | if (val == "buy") { 369 | t = order_action_t::buy; 370 | } 371 | else if (val == "sell") { 372 | t = order_action_t::sell; 373 | } 374 | } 375 | 376 | enum class order_type_t : char { 377 | limit = 'L', 378 | market, 379 | }; 380 | 381 | inline std::ostream& operator<<(std::ostream& o, const order_type_t& t) 382 | { 383 | switch (t) { 384 | case order_type_t::limit: 385 | return o << "limit"; 386 | case order_type_t::market: 387 | return o << "market"; 388 | } 389 | return o << "error"; 390 | } 391 | 392 | inline void to_json(json& j, const order_type_t& t) 393 | { 394 | std::stringstream o; 395 | o << t; 396 | j = json{o.str()}; 397 | } 398 | 399 | inline void from_json(const json& j, order_type_t& t) 400 | { 401 | auto val = j.get(); 402 | tolower(val); 403 | if (val == "limit") { 404 | t = order_type_t::limit; 405 | } 406 | else if (val == "market") { 407 | t = order_type_t::market; 408 | } 409 | } 410 | 411 | typedef struct { 412 | double price; 413 | double amount; 414 | std::time_t time; 415 | } quotation_t; 416 | 417 | typedef struct { 418 | quotation_t bid; 419 | quotation_t ask; 420 | } ticker_t; 421 | 422 | typedef struct { 423 | hash_t txid; // transaction ID 424 | tx_status_t status; 425 | order_type_t type; // market/limit 426 | order_action_t action; // buy/sell 427 | currency_pair_t pair; 428 | std::time_t open; 429 | std::time_t close; 430 | double volume; 431 | double cost; 432 | double fee; 433 | double price; 434 | } order_t; 435 | 436 | typedef struct { 437 | std::string inputTXID, inputAddress, inputCurrency; 438 | double inputAmount; 439 | std::string outputTXID, outputAddress, outputCurrency, outputAmount, 440 | shiftRate; 441 | tx_status_t status; 442 | } shapeshift_tx_t; 443 | 444 | inline void to_json(json& j, const shapeshift_tx_t& t) 445 | { 446 | j = json{{"inputTXID", t.inputTXID}, 447 | {"inputAddress", t.inputAddress}, 448 | {"inputCurrency", t.inputCurrency}, 449 | {"inputAmount", t.inputAmount}, 450 | {"outputTXID", t.outputTXID}, 451 | {"outputAddress", t.outputAddress}, 452 | {"outputCurrency", t.outputCurrency}, 453 | {"outputAmount", t.outputAmount}, 454 | {"status", t.status}}; 455 | } 456 | 457 | inline void from_json(const json& j, shapeshift_tx_t& t) 458 | { 459 | t.inputTXID = j.at("inputTXID").get(); 460 | t.inputAddress = j.at("inputAddress").get(); 461 | t.inputCurrency = j.at("inputCurrency").get(); 462 | t.inputAmount = j.at("inputAmount").get(); 463 | 464 | t.outputAmount = j.at("outputTXID").get(); 465 | t.outputAmount = j.at("outputAddress").get(); 466 | t.outputAmount = j.at("outputCurrency").get(); 467 | t.outputAmount = j.at("outputAmount").get(); 468 | 469 | t.shiftRate = j.at("shiftRate").get(); 470 | t.status = j.at("status").get(); 471 | } 472 | 473 | // Cumulative Ticker 474 | typedef struct { 475 | std::string id, name, symbol; 476 | int rank; 477 | double price_usd, price_btc; 478 | long long int day_volume_usd, market_cap_usd, available_supply, 479 | total_supply; 480 | float percent_change_1h, percent_change_24h, percent_change_7d; 481 | std::time_t last_updated; 482 | } cm_ticker_t; 483 | 484 | // Global markets data 485 | typedef struct { 486 | long long int total_market_cap_usd, total_24h_volume_usd; 487 | float bitcoin_percentage_of_market_cap; 488 | int active_currencies, active_assets, active_markets; 489 | } gm_data_t; 490 | 491 | // Cumulative market info per currency 492 | typedef struct { 493 | std::string name; 494 | currency_pair_t pair; 495 | long long int day_volume_usd; 496 | double price_usd; 497 | float percent_volume; 498 | std::time_t last_updated; 499 | } cm_market_t; 500 | 501 | inline void to_json(json& j, const cm_market_t& t) 502 | { 503 | j = json{{"name", t.name}, 504 | {"pair", t.pair}, 505 | {"day_volume_usd", t.day_volume_usd}, 506 | {"price_usd", t.price_usd}, 507 | {"percent_volume", t.percent_volume}, 508 | {"last_updated", t.last_updated}}; 509 | } 510 | 511 | inline void from_json(const json& j, cm_market_t& t) 512 | { 513 | t.name = j.at("name").get(); 514 | t.pair = j.at("pair").get(); 515 | t.day_volume_usd = 516 | static_cast(j.at("day_volume_usd").get()); 517 | t.percent_volume = j.at("percent_volume").get(); 518 | t.last_updated = j.at("last_updated").get(); 519 | } 520 | 521 | inline void to_json(json& j, const gm_data_t& t) 522 | { 523 | j = json{{"total_market_cap_usd", t.total_market_cap_usd}, 524 | {"total_24h_volume_usd", t.total_24h_volume_usd}, 525 | {"bitcoin_percentage_of_market_cap", 526 | t.bitcoin_percentage_of_market_cap}, 527 | {"active_currencies", t.active_currencies}, 528 | {"active_assets", t.active_assets}, 529 | {"active_markets", t.active_markets}}; 530 | } 531 | 532 | inline void from_json(const json& j, gm_data_t& t) 533 | { 534 | t.total_market_cap_usd = 535 | static_cast(j.at("total_market_cap_usd").get()); 536 | t.total_24h_volume_usd = 537 | static_cast(j.at("total_24h_volume_usd").get()); 538 | t.bitcoin_percentage_of_market_cap = 539 | j.at("bitcoin_percentage_of_market_cap").get(); 540 | t.active_currencies = j.at("active_currencies").get(); 541 | t.active_assets = j.at("active_assets").get(); 542 | t.active_markets = j.at("active_markets").get(); 543 | } 544 | 545 | inline void to_json(json& j, const cm_ticker_t& t) 546 | { 547 | j = json{{"id", t.id}, 548 | {"name", t.name}, 549 | {"symbol", t.symbol}, 550 | {"rank", t.rank}, 551 | {"price_usd", t.price_usd}, 552 | {"price_btc", t.price_btc}, 553 | {"24h_volume_usd", t.day_volume_usd}, 554 | {"market_cap_usd", t.market_cap_usd}, 555 | {"available_supply", t.available_supply}, 556 | {"total_supply", t.total_supply}, 557 | {"percent_change_1h", t.percent_change_1h}, 558 | {"percent_change_24h", t.percent_change_24h}, 559 | {"percent_change_7d", t.percent_change_7d}, 560 | {"last_updated", t.last_updated}}; 561 | } 562 | 563 | inline void from_json(const json& j, cm_ticker_t& t) 564 | { 565 | t.id = j.at("id").get(); 566 | t.name = j.at("name").get(); 567 | t.symbol = j.at("symbol").get(); 568 | t.rank = std::stoi(j.at("rank").get()); 569 | t.price_usd = std::stod(numeric_string(j.at("price_usd"))); 570 | t.price_btc = std::stod(numeric_string(j.at("price_btc"))); 571 | t.day_volume_usd = std::stoull(numeric_string(j.at("24h_volume_usd"))); 572 | t.market_cap_usd = std::stoull(numeric_string(j.at("market_cap_usd"))); 573 | t.available_supply = std::stoull(numeric_string(j.at("available_supply"))); 574 | t.total_supply = std::stoull(numeric_string(j.at("total_supply"))); 575 | t.percent_change_1h = std::stof(numeric_string(j.at("percent_change_1h"))); 576 | t.percent_change_24h = 577 | std::stof(numeric_string(j.at("percent_change_24h"))); 578 | t.percent_change_7d = std::stof(numeric_string(j.at("percent_change_7d"))); 579 | t.last_updated = std::stol(numeric_string(j.at("last_updated"))); 580 | } 581 | 582 | } // end namespace at 583 | 584 | #endif // AT_TYPE_H 585 | --------------------------------------------------------------------------------