├── .gitattributes ├── output └── .gitignore ├── .gitmodules ├── .gitignore ├── stop_after_notrade_ ├── docker-compose.yml ├── src ├── utils │ ├── base64.h │ ├── send_email.h │ ├── hmac_sha512.hpp │ ├── restapi.h │ ├── send_email.cpp │ ├── restapi.cpp │ └── base64.cpp ├── getpid.h ├── unique_json.hpp ├── db_fun.h ├── curl_fun.h ├── exchanges │ ├── itbit.h │ ├── wex.h │ ├── bitstamp.h │ ├── quadrigacx.h │ ├── poloniex.h │ ├── gemini.h │ ├── bittrex.h │ ├── binance.h │ ├── gdax.h │ ├── exmo.h │ ├── bitfinex.h │ ├── kraken.h │ ├── okcoin.h │ ├── cexio.h │ ├── itbit.cpp │ ├── bitstamp.cpp │ ├── wex.cpp │ ├── poloniex.cpp │ ├── bitfinex.cpp │ ├── exmo.cpp │ ├── gemini.cpp │ ├── gdax.cpp │ ├── quadrigacx.cpp │ ├── binance.cpp │ ├── okcoin.cpp │ ├── kraken.cpp │ ├── bittrex.cpp │ └── cexio.cpp ├── unique_sqlite.hpp ├── quote_t.h ├── time_fun.h ├── check_entry_exit.h ├── hex_str.hpp ├── bitcoin.h ├── bitcoin.cpp ├── time_fun.cpp ├── result.h ├── db_fun.cpp ├── curl_fun.cpp ├── parameters.h ├── result.cpp ├── check_entry_exit.cpp └── parameters.cpp ├── docker-compose.debug.yml ├── debug.sh ├── Dockerfile.debug ├── .vscode ├── tasks.json ├── settings.json ├── launch.json └── c_cpp_properties.json ├── start_blackbird.sh ├── Dockerfile ├── blackbird.code-workspace ├── LICENSE ├── Makefile ├── cmake └── Modules │ ├── FindSQLite3.cmake │ └── FindJansson.cmake ├── CMakeLists.txt ├── .travis.yml └── blackbird.conf /.gitattributes: -------------------------------------------------------------------------------- 1 | cmake/* linguist-vendored 2 | -------------------------------------------------------------------------------- /output/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/sqlite3"] 2 | path = extern/sqlite3 3 | url = https://github.com/greatwolf/sqlite3 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.csv 3 | *.o 4 | *.a 5 | blackbird 6 | blackbird.db 7 | sqlite3 8 | core 9 | build 10 | .DS_Store 11 | blackbird.db-journal 12 | test.conf -------------------------------------------------------------------------------- /stop_after_notrade_: -------------------------------------------------------------------------------- 1 | (while Blackbird is running, use this file in the working directory to exit Blackbird after the last arbitrage trade closes. Rename or delete it if you don't want to stop Blackbird) 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | blackbird: 5 | build: . 6 | volumes: 7 | - ./blackbird.conf:/blackbird/blackbird.conf 8 | - ./output:/blackbird/output/ 9 | command: /blackbird/start_blackbird.sh -------------------------------------------------------------------------------- /src/utils/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE64_H 2 | #define BASE64_H 3 | 4 | #include 5 | 6 | std::string base64_encode(unsigned char const* , unsigned int len); 7 | std::string base64_decode(std::string const& s); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/getpid.h: -------------------------------------------------------------------------------- 1 | #ifndef GETPID_H 2 | #define GETPID_H 3 | 4 | #if defined(_MSC_VER) 5 | #include 6 | static DWORD getpid() { return GetCurrentProcessId(); } 7 | #else 8 | #include 9 | #endif 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /docker-compose.debug.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | blackbird: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.debug 8 | privileged: true 9 | ports: ["9091:9091"] 10 | volumes: 11 | - .:/blackbird 12 | environment: 13 | DEBUG: 'true' 14 | -------------------------------------------------------------------------------- /src/unique_json.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UNIQUE_JSON_HPP 2 | #define UNIQUE_JSON_HPP 3 | 4 | #include "jansson.h" 5 | #include 6 | 7 | struct json_deleter { 8 | void operator () (json_t *J) { 9 | json_decref(J); 10 | } 11 | }; 12 | 13 | using unique_json = std::unique_ptr; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/utils/send_email.h: -------------------------------------------------------------------------------- 1 | #ifndef SEND_EMAIL_H 2 | #define SEND_EMAIL_H 3 | 4 | struct Result; 5 | struct Parameters; 6 | 7 | // After a trade is done, sends an email with the details 8 | // of the results. 9 | // The email credentials are in the configuration file. 10 | void sendEmail(const Result &res, Parameters ¶ms); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #this script is intended to run inside the docker container 3 | #it compiles the app with debug symbols and launches it via GDB server 4 | # 5 | cd "$(dirname "$0")" 6 | 7 | #compile with debug flags on 8 | make -B DEBUG=true VERBOSE=true 9 | #launch GDB server for remote debugger to attach 10 | gdbserver :9091 /debug-app/blackbird -------------------------------------------------------------------------------- /src/db_fun.h: -------------------------------------------------------------------------------- 1 | #ifndef DB_FUN_H 2 | #define DB_FUN_H 3 | 4 | #include 5 | 6 | struct Parameters; 7 | 8 | int createDbConnection(Parameters& params); 9 | 10 | int createTable(std::string exchangeName, Parameters& params); 11 | 12 | int addBidAskToDb(std::string exchangeName, std::string datetime, double bid, double ask, Parameters& params); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN apt-get update \ 6 | && apt-get install -yq --no-install-recommends \ 7 | g++ \ 8 | gcc \ 9 | gdb \ 10 | gdbserver \ 11 | libcurl4-openssl-dev \ 12 | libjansson-dev \ 13 | libssl-dev \ 14 | libsqlite3-dev \ 15 | make \ 16 | sendemail \ 17 | && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /src/curl_fun.h: -------------------------------------------------------------------------------- 1 | #ifndef CURL_FUN_H 2 | #define CURL_FUN_H 3 | 4 | #include 5 | 6 | struct json_t; 7 | struct Parameters; 8 | 9 | size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); 10 | 11 | // Gets a JSON object from a REST API call to an exchange. 12 | json_t* getJsonFromUrl(Parameters& params, std::string url, std::string postField, bool getRequest); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "launch debug container" 8 | ,"type": "shell" 9 | ,"command": "docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /src/exchanges/itbit.h: -------------------------------------------------------------------------------- 1 | #ifndef ITBIT_H 2 | #define ITBIT_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace ItBit { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | double getActivePos(Parameters& params); 17 | 18 | double getLimitPrice(Parameters& params, double volume, bool isBid); 19 | 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /start_blackbird.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #this script is intended to run inside the docker container 3 | #it compiles the app with debug symbols and launches it via GDB server 4 | # 5 | cd "$(dirname "$0")" 6 | 7 | echo "$DEBUG" 8 | 9 | if [ -z "$DEBUG" ]; then 10 | ./blackbird 11 | else 12 | #compile code with debug flags 13 | make -B DEBUG=true VERBOSE=true 14 | #launch GDB server for remote debugger to attach 15 | gdbserver :9091 /blackbird/blackbird 16 | fi -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER Alex Gandy 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | RUN apt-get update \ 7 | && apt-get install -yq --no-install-recommends \ 8 | libssl-dev \ 9 | libjansson-dev \ 10 | libcurl4-openssl-dev \ 11 | libsqlite3-dev \ 12 | sendemail \ 13 | make \ 14 | gcc \ 15 | g++ \ 16 | && rm -rf /var/lib/apt/lists/* 17 | 18 | COPY . /blackbird 19 | 20 | WORKDIR /blackbird 21 | 22 | RUN make -B 23 | -------------------------------------------------------------------------------- /src/unique_sqlite.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UNIQUE_SQLITE_HPP 2 | #define UNIQUE_SQLITE_HPP 3 | 4 | #include "sqlite3.h" 5 | #include 6 | 7 | struct sqlite_deleter { 8 | void operator () (sqlite3 *S) { 9 | sqlite3_close(S); 10 | } 11 | void operator () (char *errmsg) { 12 | sqlite3_free(errmsg); 13 | } 14 | }; 15 | 16 | using unique_sqlite = std::unique_ptr; 17 | using unique_sqlerr = std::unique_ptr; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/quote_t.h: -------------------------------------------------------------------------------- 1 | #ifndef QUOTE_T_H 2 | #define QUOTE_T_H 3 | 4 | #include 5 | 6 | class quote_t { 7 | 8 | typedef double bid_t; 9 | typedef double ask_t; 10 | std::pair quote_; 11 | 12 | public: 13 | quote_t(bid_t bid, ask_t ask) : quote_(std::make_pair(bid, ask)) {} 14 | quote_t(std::pair &"e) : quote_(std::move(quote)) {} 15 | bid_t bid() const { return quote_.first; } 16 | ask_t ask() const { return quote_.second; } 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/exchanges/wex.h: -------------------------------------------------------------------------------- 1 | #ifndef WEX_H 2 | #define WEX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct Parameters; 8 | 9 | namespace WEX { 10 | 11 | quote_t getQuote(Parameters& params); 12 | 13 | double getAvail(Parameters& params, std::string currency); 14 | 15 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price); 16 | 17 | bool isOrderComplete(Parameters& params, std::string orderId); 18 | 19 | double getActivePos(Parameters& params); 20 | 21 | double getLimitPrice(Parameters& params, double volume, bool isBid); 22 | 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/time_fun.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_FUN_H 2 | #define TIME_FUN_H 3 | 4 | #include 5 | #include 6 | 7 | time_t getTime_t(int y, int m, int d, int h, int n, int s); 8 | 9 | // Returns 'yyyy-mm-dd_hh:nn:ss' 10 | extern std::string (*const printDateTimeCsv)(const time_t &t); 11 | 12 | // Returns 'yyyy-mm-dd hh:nn:ss' 13 | extern std::string (*const printDateTimeDb)(const time_t &t); 14 | 15 | // Returns 'yyymmdd_hhnnss' 16 | std::string printDateTimeFileName(); 17 | 18 | // Returns 'mm/dd/yyyy hh:nn:ss' 19 | std::string printDateTime(time_t t); 20 | 21 | // Returns current 'mm/dd/yyyy hh:mm:ss' 22 | std::string printDateTime(); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/exchanges/bitstamp.h: -------------------------------------------------------------------------------- 1 | #ifndef BITSTAMP_H 2 | #define BITSTAMP_H 3 | 4 | #include "quote_t.h" 5 | 6 | #include 7 | 8 | struct json_t; 9 | struct Parameters; 10 | 11 | namespace Bitstamp { 12 | 13 | quote_t getQuote(Parameters& params); 14 | 15 | double getAvail(Parameters& params, std::string currency); 16 | 17 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 18 | 19 | bool isOrderComplete(Parameters& params, std::string orderId); 20 | 21 | double getActivePos(Parameters& params); 22 | 23 | double getLimitPrice(Parameters& params, double volume, bool isBid); 24 | 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/check_entry_exit.h: -------------------------------------------------------------------------------- 1 | #ifndef CHECK_ENTRY_EXIT_H 2 | #define CHECK_ENTRY_EXIT_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | class Bitcoin; 9 | struct Result; 10 | struct Parameters; 11 | 12 | std::string percToStr(double perc); 13 | 14 | // Checks for entry opportunity between two exchanges 15 | // and returns True if an opporunity is found. 16 | bool checkEntry(Bitcoin* btcLong, Bitcoin* btcShort, Result& res, Parameters& params); 17 | 18 | // Checks for exit opportunity between two exchanges 19 | // and returns True if an opporunity is found. 20 | bool checkExit(Bitcoin* btcLong, Bitcoin* btcShort, Result& res, Parameters& params, std::time_t period); 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /src/exchanges/quadrigacx.h: -------------------------------------------------------------------------------- 1 | #ifndef QUADRIGACX_H 2 | #define QUADRIGACX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | #include 7 | 8 | struct json_t; 9 | struct Parameters; 10 | 11 | namespace QuadrigaCX { 12 | 13 | quote_t getQuote(Parameters& params); 14 | 15 | double getAvail(Parameters& params, std::string currency); 16 | 17 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 18 | 19 | bool isOrderComplete(Parameters& params, std::string orderId); 20 | 21 | double getActivePos(Parameters& params); 22 | 23 | double getLimitPrice(Parameters& params, double volume, bool isBid); 24 | 25 | void testQuadriga(); 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/exchanges/poloniex.h: -------------------------------------------------------------------------------- 1 | #ifndef POLONIEX_H 2 | #define POLONIEX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct Parameters; 8 | 9 | namespace Poloniex { 10 | 11 | quote_t getQuote(Parameters& params); 12 | 13 | double getAvail(Parameters& params, std::string currency); 14 | 15 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 16 | 17 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 18 | 19 | bool isOrderComplete(Parameters& params, std::string orderId); 20 | 21 | double getActivePos(Parameters& params); 22 | 23 | double getLimitPrice(Parameters& params, double volume, bool isBid); 24 | 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/exchanges/gemini.h: -------------------------------------------------------------------------------- 1 | #ifndef GEMINI_H 2 | #define GEMINI_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace Gemini { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 17 | 18 | bool isOrderComplete(Parameters& params, std::string orderId); 19 | 20 | double getActivePos(Parameters& params); 21 | 22 | double getLimitPrice(Parameters& params, double volume, bool isBid); 23 | 24 | json_t* authRequest(Parameters& params, std::string url, std::string request, std::string options); 25 | 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/exchanges/bittrex.h: -------------------------------------------------------------------------------- 1 | #ifndef BITTREX_H 2 | #define BITTREX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct Parameters; 8 | 9 | namespace Bittrex { 10 | 11 | quote_t getQuote(Parameters& params); 12 | 13 | double getAvail(Parameters& params, std::string currency); 14 | 15 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 16 | 17 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 18 | 19 | bool isOrderComplete(Parameters& params, std::string orderId); 20 | 21 | double getActivePos(Parameters& params); 22 | 23 | double getLimitPrice(Parameters& params, double volume, bool isBid); 24 | 25 | void testBittrex(); 26 | } 27 | 28 | #endif -------------------------------------------------------------------------------- /src/exchanges/binance.h: -------------------------------------------------------------------------------- 1 | #ifndef BINANCE_H 2 | #define BINANCE_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct Parameters; 8 | 9 | namespace Binance 10 | { 11 | 12 | quote_t getQuote(Parameters ¶ms); 13 | 14 | double getAvail(Parameters ¶ms, std::string currency); 15 | 16 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price); 17 | 18 | std::string sendShortOrder(Parameters ¶ms, std::string direction, double quantity, double price); 19 | 20 | bool isOrderComplete(Parameters ¶ms, std::string orderId); 21 | 22 | double getActivePos(Parameters ¶ms); 23 | 24 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid); 25 | 26 | void testBinance(); 27 | } 28 | 29 | #endif -------------------------------------------------------------------------------- /src/exchanges/gdax.h: -------------------------------------------------------------------------------- 1 | #ifndef GDAX_H 2 | #define GDAX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace GDAX { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | double getActivePos(Parameters& params); 17 | 18 | double getLimitPrice(Parameters& params, double volume, bool isBid); 19 | 20 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 21 | 22 | bool isOrderComplete(Parameters& params, std::string orderId); 23 | 24 | json_t* authRequest(Parameters& params, std::string method, std::string request,const std::string &options); 25 | 26 | std::string gettime(); 27 | 28 | void testGDAX(); 29 | 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /blackbird.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "*.tcc": "cpp", 10 | "cctype": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "cstdarg": "cpp", 14 | "cstddef": "cpp", 15 | "cstdio": "cpp", 16 | "cstdlib": "cpp", 17 | "cstring": "cpp", 18 | "ctime": "cpp", 19 | "cwchar": "cpp", 20 | "cwctype": "cpp", 21 | "exception": "cpp", 22 | "fstream": "cpp", 23 | "iomanip": "cpp", 24 | "iosfwd": "cpp", 25 | "iostream": "cpp", 26 | "istream": "cpp", 27 | "limits": "cpp", 28 | "memory": "cpp", 29 | "new": "cpp", 30 | "ostream": "cpp", 31 | "sstream": "cpp", 32 | "streambuf": "cpp", 33 | "array": "cpp", 34 | "type_traits": "cpp", 35 | "typeinfo": "cpp" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/hex_str.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HEX_STR_HPP 2 | #define HEX_STR_HPP 3 | 4 | #include 5 | 6 | enum { 7 | upperhex, 8 | lowerhex 9 | }; 10 | 11 | template 12 | std::string hex_str(FwdIt first, FwdIt last) { 13 | static_assert(sizeof(typename std::iterator_traits::value_type) == 1, 14 | "value_type must be 1 byte."); 15 | constexpr const char *bytemap = Caps ? 16 | "0123456789abcdef" : 17 | "0123456789ABCDEF"; 18 | 19 | std::string result(std::distance(first, last) * 2, '0'); 20 | 21 | auto pos = begin(result); 22 | while (first != last) { 23 | *pos++ = bytemap[ *first >> 4 & 0xF ]; 24 | *pos++ = bytemap[ *first++ & 0xF ]; 25 | } 26 | 27 | return result; 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/exchanges/exmo.h: -------------------------------------------------------------------------------- 1 | #ifndef EXMO_H 2 | #define EXMO_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | #include 7 | #include 8 | 9 | struct json_t; 10 | struct Parameters; 11 | 12 | namespace Exmo { 13 | 14 | quote_t getQuote(Parameters& params); 15 | 16 | double getAvail(Parameters& params, std::string currency); 17 | 18 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 19 | // TODO multi currency support 20 | //std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price, std::string pair = "btc_usd"); 21 | 22 | bool isOrderComplete(Parameters& params, std::string orderId); 23 | 24 | double getActivePos(Parameters& params); 25 | 26 | double getLimitPrice(Parameters& params, double volume, bool isBid); 27 | 28 | void testExmo(); 29 | 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/bitcoin.h: -------------------------------------------------------------------------------- 1 | #ifndef BITCOIN_H 2 | #define BITCOIN_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | // Contains all the information for a given exchange, 8 | // like fees and wether we can short on that exchange. 9 | // FIXME: short selling should depend on the currency. 10 | class Bitcoin { 11 | 12 | private: 13 | unsigned id; 14 | std::string exchName; 15 | double fees; 16 | bool hasShort; 17 | bool isImplemented; 18 | double bid, ask; 19 | 20 | public: 21 | Bitcoin(unsigned id, std::string n, double f, bool h, bool m); 22 | void updateData(quote_t quote); 23 | unsigned getId() const; 24 | double getAsk() const; 25 | double getBid() const; 26 | double getMidPrice() const; 27 | std::string getExchName() const; 28 | double getFees() const; 29 | bool getHasShort() const; 30 | bool getIsImplemented() const; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex.h: -------------------------------------------------------------------------------- 1 | #ifndef BITFINEX_H 2 | #define BITFINEX_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace Bitfinex { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 17 | 18 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 19 | 20 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price); 21 | 22 | bool isOrderComplete(Parameters& params, std::string orderId); 23 | 24 | double getActivePos(Parameters& params); 25 | 26 | double getLimitPrice(Parameters& params, double volume, bool isBid); 27 | 28 | json_t* authRequest(Parameters ¶ms, std::string request, std::string options); 29 | 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/exchanges/kraken.h: -------------------------------------------------------------------------------- 1 | #ifndef KRAKEN_H 2 | #define KRAKEN_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace Kraken { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 17 | 18 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 19 | 20 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price); 21 | 22 | bool isOrderComplete(Parameters& params, std::string orderId); 23 | 24 | double getActivePos(Parameters& params); 25 | 26 | double getLimitPrice(Parameters& params, double volume, bool isBid); 27 | 28 | json_t* authRequest(Parameters& params, std::string request, std::string options = ""); 29 | 30 | void testKraken(); 31 | 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/bitcoin.cpp: -------------------------------------------------------------------------------- 1 | #include "bitcoin.h" 2 | #include 3 | 4 | Bitcoin::Bitcoin(unsigned i, std::string n, double f, bool h, bool m) { 5 | id = i; 6 | exchName = n; 7 | fees = f; 8 | hasShort = h; 9 | isImplemented = m; 10 | bid = 0.0; 11 | ask = 0.0; 12 | } 13 | 14 | void Bitcoin::updateData(quote_t quote) { 15 | bid = quote.bid(); 16 | ask = quote.ask(); 17 | } 18 | 19 | unsigned Bitcoin::getId() const { return id; } 20 | 21 | double Bitcoin::getBid() const { return bid; } 22 | 23 | double Bitcoin::getAsk() const { return ask; } 24 | 25 | double Bitcoin::getMidPrice() const { 26 | if (bid > 0.0 && ask > 0.0) { 27 | return (bid + ask) / 2.0; 28 | } else { 29 | return 0.0; 30 | } 31 | } 32 | 33 | std::string Bitcoin::getExchName() const { return exchName; } 34 | 35 | double Bitcoin::getFees() const { return fees; } 36 | 37 | bool Bitcoin::getHasShort() const { return hasShort; } 38 | 39 | bool Bitcoin::getIsImplemented() const { return isImplemented; } 40 | -------------------------------------------------------------------------------- /src/exchanges/okcoin.h: -------------------------------------------------------------------------------- 1 | #ifndef OKCOIN_H 2 | #define OKCOIN_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | 7 | struct json_t; 8 | struct Parameters; 9 | 10 | namespace OKCoin { 11 | 12 | quote_t getQuote(Parameters& params); 13 | 14 | double getAvail(Parameters& params, std::string currency); 15 | 16 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 17 | 18 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 19 | 20 | bool isOrderComplete(Parameters& params, std::string orderId); 21 | 22 | double getActivePos(Parameters& params); 23 | 24 | double getLimitPrice(Parameters& params, double volume, bool isBid); 25 | 26 | json_t* authRequest(Parameters& params, std::string url, std::string signature, std::string content); 27 | 28 | void getBorrowInfo(Parameters& params); 29 | 30 | int borrowBtc(Parameters& params, double amount); 31 | 32 | void repayBtc(Parameters& params, int borrowId); 33 | 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "*.tcc": "cpp", 5 | "cctype": "cpp", 6 | "chrono": "cpp", 7 | "clocale": "cpp", 8 | "cmath": "cpp", 9 | "cstdint": "cpp", 10 | "cstdio": "cpp", 11 | "cstdlib": "cpp", 12 | "ctime": "cpp", 13 | "cwchar": "cpp", 14 | "cwctype": "cpp", 15 | "exception": "cpp", 16 | "fstream": "cpp", 17 | "functional": "cpp", 18 | "initializer_list": "cpp", 19 | "iomanip": "cpp", 20 | "iosfwd": "cpp", 21 | "iostream": "cpp", 22 | "istream": "cpp", 23 | "limits": "cpp", 24 | "memory": "cpp", 25 | "new": "cpp", 26 | "ostream": "cpp", 27 | "ratio": "cpp", 28 | "sstream": "cpp", 29 | "stdexcept": "cpp", 30 | "streambuf": "cpp", 31 | "system_error": "cpp", 32 | "thread": "cpp", 33 | "tuple": "cpp", 34 | "type_traits": "cpp", 35 | "typeinfo": "cpp", 36 | "utility": "cpp" 37 | } 38 | } -------------------------------------------------------------------------------- /src/exchanges/cexio.h: -------------------------------------------------------------------------------- 1 | #ifndef CEXIO_H 2 | #define CEXIO_H 3 | 4 | #include "quote_t.h" 5 | #include 6 | #include 7 | 8 | struct json_t; 9 | struct Parameters; 10 | 11 | namespace Cexio { 12 | 13 | quote_t getQuote(Parameters& params); 14 | 15 | double getAvail(Parameters& params, std::string currency); 16 | 17 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price); 18 | 19 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price); 20 | 21 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price); 22 | 23 | std::string openPosition(Parameters& params,std::string direction, double quantity, double price); 24 | 25 | std::string closePosition(Parameters& params); 26 | 27 | 28 | bool isOrderComplete(Parameters& params, std::string orderId); 29 | 30 | double getActivePos(Parameters& params); 31 | 32 | double getLimitPrice(Parameters& params, double volume, bool isBid); 33 | 34 | void testCexio(); 35 | 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Julien Hamilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/exchanges/itbit.cpp: -------------------------------------------------------------------------------- 1 | #include "itbit.h" 2 | #include "parameters.h" 3 | #include "curl_fun.h" 4 | #include "utils/restapi.h" 5 | #include "unique_json.hpp" 6 | 7 | 8 | namespace ItBit { 9 | 10 | static RestApi& queryHandle(Parameters ¶ms) 11 | { 12 | static RestApi query ("https://api.itbit.com", 13 | params.cacert.c_str(), *params.logFile); 14 | return query; 15 | } 16 | 17 | quote_t getQuote(Parameters ¶ms) 18 | { 19 | auto &exchange = queryHandle(params); 20 | unique_json root { exchange.getRequest("/v1/markets/XBTUSD/ticker") }; 21 | 22 | const char *quote = json_string_value(json_object_get(root.get(), "bid")); 23 | auto bidValue = quote ? std::stod(quote) : 0.0; 24 | 25 | quote = json_string_value(json_object_get(root.get(), "ask")); 26 | auto askValue = quote ? std::stod(quote) : 0.0; 27 | 28 | return std::make_pair(bidValue, askValue); 29 | } 30 | 31 | double getAvail(Parameters& params, std::string currency) { 32 | // TODO 33 | return 0.0; 34 | } 35 | 36 | double getActivePos(Parameters& params) { 37 | // TODO 38 | return 0.0; 39 | } 40 | 41 | double getLimitPrice(Parameters& params, double volume, bool isBid) { 42 | // TODO 43 | return 0.0; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/hmac_sha512.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HMAC_SHA512_HPP 2 | #define HMAC_SHA512_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class HMAC_SHA512 { 9 | public: 10 | HMAC_SHA512(const std::string& key, const std::string& msg) { 11 | HMAC_CTX ctx; 12 | HMAC_CTX_init(&ctx); 13 | 14 | // Set HMAC key. 15 | HMAC_Init_ex(&ctx, reinterpret_cast(key.c_str()), key.size(), EVP_sha512(), NULL); 16 | 17 | // May be called repeatedly to insert all your data. 18 | HMAC_Update(&ctx, reinterpret_cast(msg.c_str()), msg.size()); 19 | 20 | // Finish HMAC computation and fetch result. 21 | unsigned char* result = new unsigned char[129]; 22 | unsigned int result_len = 129; 23 | HMAC_Final(&ctx, result, &result_len); 24 | for (unsigned int i = 0; i < result_len; i++) { 25 | digest_.push_back(int(result[i])); 26 | } 27 | HMAC_CTX_cleanup(&ctx); 28 | } 29 | 30 | std::string hex_digest() { 31 | std::string str; 32 | const char hex_chars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 33 | for (auto i : digest_) { 34 | const char byte = i; 35 | str.push_back(hex_chars[(byte & 0xF0) >> 4]); 36 | str.push_back(hex_chars[(byte & 0x0F) >> 0]); 37 | } 38 | return str; 39 | } 40 | 41 | private: 42 | std::vector digest_; 43 | }; 44 | 45 | #endif // HMAC_SHA512_HPP 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Blackbird Bitcoin Arbitrage Makefile 2 | 3 | override INC_DIR += -I ./src -I ./extern/sqlite3/include 4 | override LIB_DIR += -L . 5 | CFLAGS := -std=c99 6 | CXXFLAGS := -Wall -pedantic -std=c++11 -Wno-missing-braces 7 | LDFLAGS := 8 | LDLIBS := -lsqlite3 -lcrypto -ljansson -lcurl 9 | CC := gcc 10 | 11 | 12 | EXEC = blackbird 13 | SOURCES = $(wildcard src/*.cpp) $(wildcard src/*/*.cpp) 14 | OBJECTS = $(SOURCES:.cpp=.o) 15 | 16 | SQLITE3 = libsqlite3.a 17 | SQLITE3CLI = sqlite3 18 | SQLITE3LIBS := 19 | 20 | ifndef VERBOSE 21 | Q := @ 22 | else 23 | Q := 24 | endif 25 | 26 | ifndef DEBUG 27 | CFLAGS += -O2 -DNDEBUG 28 | CXXFLAGS += -O2 -DNDEBUG 29 | else 30 | CFLAGS += -O0 -g 31 | CXXFLAGS += -O0 -g 32 | endif 33 | 34 | ifneq ($(OS),Windows_NT) 35 | SQLITE3LIBS += -lpthread -ldl 36 | endif 37 | 38 | all: $(SQLITE3CLI) $(EXEC) 39 | 40 | $(SQLITE3CLI): shell.o $(SQLITE3) 41 | @echo Linking $@: 42 | $(Q)$(CC) $(LDFLAGS) $(LIB_DIR) $< -o $@ -lsqlite3 $(SQLITE3LIBS) 43 | 44 | $(SQLITE3): sqlite3.o 45 | @echo Archiving $@: 46 | $(Q)$(AR) $(ARFLAGS) $@ $^ 47 | 48 | $(EXEC): $(SQLITE3) $(OBJECTS) 49 | @echo Linking $@: 50 | $(Q)$(CXX) $(LDFLAGS) $(LIB_DIR) $(OBJECTS) -o $@ $(LDLIBS) $(SQLITE3LIBS) 51 | 52 | %.o: %.cpp 53 | @echo Compiling $@: 54 | $(Q)$(CXX) $(CXXFLAGS) $(INC_DIR) -c $< -o $@ 55 | 56 | %.o: extern/sqlite3/src/%.c 57 | @echo Compiling $@: 58 | $(Q)$(CC) $(CFLAGS) $(INC_DIR) -c $< -o $@ 59 | 60 | clean: 61 | $(Q)rm -rf core $(OBJECTS) 62 | -------------------------------------------------------------------------------- /src/utils/restapi.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTAPI_H 2 | #define RESTAPI_H 3 | 4 | #include "curl/curl.h" 5 | #include 6 | #include 7 | #include 8 | 9 | struct json_t; 10 | class RestApi 11 | { 12 | struct CURL_deleter 13 | { 14 | void operator () (CURL *); 15 | void operator () (curl_slist *); 16 | }; 17 | 18 | typedef std::unique_ptr unique_curl; 19 | typedef std::string string; 20 | 21 | unique_curl C; 22 | const string host; 23 | std::ostream &log; 24 | 25 | public: 26 | using unique_slist = std::unique_ptr; 27 | 28 | RestApi (string host, const char *cacert = nullptr, 29 | std::ostream &log = std::cerr); 30 | RestApi (const RestApi &) = delete; 31 | RestApi& operator = (const RestApi &) = delete; 32 | 33 | json_t* getRequest (const string &uri, unique_slist headers = nullptr); 34 | json_t* postRequest (const string &uri, unique_slist headers = nullptr, 35 | const string &post_data = ""); 36 | json_t* postRequest (const string &uri, const string &post_data); 37 | }; 38 | 39 | template 40 | RestApi::unique_slist make_slist(T begin, T end) 41 | { 42 | RestApi::unique_slist::pointer res; 43 | for (res = nullptr; begin != end; ++begin) 44 | res = curl_slist_append(res, begin->c_str()); 45 | 46 | return RestApi::unique_slist(res); // unique_ptr constructor is explicit 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/time_fun.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "time_fun.h" 4 | 5 | time_t getTime_t(int y, int m, int d, int h, int n, int s) { 6 | tm ttm = {0}; 7 | ttm.tm_year = y - 1900; 8 | ttm.tm_mon = m - 1; 9 | ttm.tm_mday = d; 10 | ttm.tm_hour = h; 11 | ttm.tm_min = n; 12 | ttm.tm_sec = s; 13 | ttm.tm_isdst = -1; 14 | return mktime(&ttm); 15 | } 16 | 17 | template 18 | std::string fmtDateTime(const time_t &t) { 19 | std::string buff(20, '\0'); 20 | buff.resize(strftime(&buff[0], buff.size(), fmt, localtime(&t))); 21 | return buff; 22 | } 23 | 24 | /* 25 | * Apparently gcc and msvc have a bug 26 | * 'auto' deduces a type different from the earlier 27 | * extern declaration. 28 | * clang compiles with 'auto' however. 29 | * Also see SO question: 30 | * http://stackoverflow.com/q/28835198/234175 31 | */ 32 | extern const char csvfmt[] = "%Y-%m-%d_%H:%M:%S"; 33 | const decltype(&fmtDateTime) printDateTimeCsv = &fmtDateTime; 34 | 35 | extern const char dbfmt[] = "%Y-%m-%d %H:%M:%S"; 36 | const decltype(&fmtDateTime) printDateTimeDb = &fmtDateTime; 37 | 38 | extern const char filenamefmt[] = "%Y%m%d_%H%M%S"; 39 | 40 | std::string printDateTimeFileName() { 41 | return fmtDateTime(time(NULL)); 42 | } 43 | 44 | extern const char defaultfmt[] = "%m/%d/%Y %H:%M:%S"; 45 | 46 | std::string printDateTime(time_t t) { 47 | return fmtDateTime(t); 48 | } 49 | 50 | std::string printDateTime() { 51 | return printDateTime(time(NULL)); 52 | } 53 | -------------------------------------------------------------------------------- /cmake/Modules/FindSQLite3.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Created by Peter Kapec 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Note: 6 | # Searching headers and libraries is very simple and is NOT as powerful as scripts 7 | # distributed with CMake, because LuaDist defines directories to search for. 8 | # Everyone is encouraged to contact the author with improvements. Maybe this file 9 | # becomes part of CMake distribution sometimes. 10 | 11 | # - Find sqlite3 12 | # Find the native SQLITE3 headers and libraries. 13 | # 14 | # SQLITE3_INCLUDE_DIRS - where to find sqlite3.h, etc. 15 | # SQLITE3_LIBRARIES - List of libraries when using sqlite. 16 | # SQLITE3_FOUND - True if sqlite found. 17 | 18 | # Look for the header file. 19 | FIND_PATH(SQLITE3_INCLUDE_DIR NAMES sqlite3.h) 20 | 21 | # Look for the library. 22 | FIND_LIBRARY(SQLITE3_LIBRARY NAMES sqlite3) 23 | 24 | # Handle the QUIETLY and REQUIRED arguments and set SQLITE3_FOUND to TRUE if all listed variables are TRUE. 25 | INCLUDE(FindPackageHandleStandardArgs) 26 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SQLITE3 DEFAULT_MSG SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) 27 | 28 | # Copy the results to the output variables. 29 | IF(SQLITE3_FOUND) 30 | SET(SQLITE3_LIBRARIES ${SQLITE3_LIBRARY}) 31 | SET(SQLITE3_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIR}) 32 | ELSE(SQLITE3_FOUND) 33 | SET(SQLITE3_LIBRARIES) 34 | SET(SQLITE3_INCLUDE_DIRS) 35 | ENDIF(SQLITE3_FOUND) 36 | 37 | MARK_AS_ADVANCED(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES) 38 | -------------------------------------------------------------------------------- /cmake/Modules/FindJansson.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Jansson 2 | # Once done this will define 3 | # 4 | # JANSSON_FOUND - system has Jansson 5 | # JANSSON_INCLUDE_DIRS - the Jansson include directory 6 | # JANSSON_LIBRARIES - Link these to use Jansson 7 | # 8 | # Copyright (c) 2011 Lee Hambley 9 | # 10 | # Redistribution and use is allowed according to the terms of the New 11 | # BSD license. 12 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 13 | # 14 | 15 | if (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) 16 | # in cache already 17 | set(JANSSON_FOUND TRUE) 18 | else (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) 19 | find_path(JANSSON_INCLUDE_DIR 20 | NAMES 21 | jansson.h 22 | PATHS 23 | /usr/include 24 | /usr/local/include 25 | /opt/local/include 26 | /sw/include 27 | ) 28 | 29 | find_library(JANSSON_LIBRARY 30 | NAMES 31 | jansson 32 | PATHS 33 | /usr/lib 34 | /usr/local/lib 35 | /opt/local/lib 36 | /sw/lib 37 | ) 38 | 39 | set(JANSSON_INCLUDE_DIRS 40 | ${JANSSON_INCLUDE_DIR} 41 | ) 42 | 43 | if (JANSSON_LIBRARY) 44 | set(JANSSON_LIBRARIES 45 | ${JANSSON_LIBRARIES} 46 | ${JANSSON_LIBRARY} 47 | ) 48 | endif (JANSSON_LIBRARY) 49 | 50 | include(FindPackageHandleStandardArgs) 51 | find_package_handle_standard_args(Jansson DEFAULT_MSG 52 | JANSSON_LIBRARIES JANSSON_INCLUDE_DIRS) 53 | 54 | # show the JANSSON_INCLUDE_DIRS and JANSSON_LIBRARIES variables only in the advanced view 55 | mark_as_advanced(JANSSON_INCLUDE_DIRS JANSSON_LIBRARIES) 56 | 57 | endif (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) 58 | 59 | 60 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach to Debug Container" 9 | ,"type": "cppdbg" 10 | ,"request": "launch" 11 | ,"program": "${workspaceRoot}/blackbird" 12 | ,"miDebuggerServerAddress": "localhost:9091" 13 | ,"args": [] 14 | ,"stopAtEntry": false 15 | ,"cwd": "${workspaceRoot}" 16 | ,"environment": [] 17 | ,"externalConsole": true 18 | ,"linux": { 19 | "MIMode": "gdb" 20 | } 21 | ,"osx": { 22 | "MIMode": "gdb" 23 | } 24 | ,"windows": { 25 | "MIMode": "gdb" 26 | } 27 | ,"preLaunchTask": "launch debug container" 28 | }, 29 | { 30 | "name": "(gdb) Launch", 31 | "type": "cppdbg", 32 | "request": "launch", 33 | "program": "${workspaceFolder}/blackbird", 34 | "args": [], 35 | "stopAtEntry": true, 36 | "cwd": "${workspaceFolder}", 37 | "environment": [], 38 | "externalConsole": true, 39 | "MIMode": "gdb", 40 | "setupCommands": [ 41 | { 42 | "description": "Enable pretty-printing for gdb", 43 | "text": "-enable-pretty-printing", 44 | "ignoreFailures": true 45 | } 46 | ] 47 | }, 48 | ] 49 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(blackbird) 4 | 5 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/") 6 | include(cotire) 7 | 8 | find_package(CURL) 9 | find_package(Jansson) 10 | find_package(OpenSSL) 11 | find_package(SQLite3) 12 | 13 | file(GLOB SOURCES 14 | src/*.cpp 15 | src/exchanges/*.cpp 16 | src/utils/*.cpp) 17 | add_executable(${PROJECT_NAME} ${SOURCES}) 18 | 19 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR 20 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 21 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -pedantic -Wno-missing-braces) 22 | endif() 23 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 24 | target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX _CRT_SECURE_NO_WARNINGS) 25 | endif() 26 | set_target_properties(${PROJECT_NAME} PROPERTIES 27 | CXX_STANDARD 11 28 | CXX_STANDARD_REQUIRED YES 29 | CXX_EXTENSIONS NO) 30 | target_include_directories (${PROJECT_NAME} PRIVATE 31 | ${PROJECT_SOURCE_DIR}/src 32 | ${SQLITE3_INCLUDE_DIR} 33 | ${CURL_INCLUDE_DIR} 34 | ${OPENSSL_INCLUDE_DIR} 35 | ${JANSSON_INCLUDE_DIR}) 36 | target_link_libraries(${PROJECT_NAME} ${CURL_LIBRARIES} 37 | ${JANSSON_LIBRARIES} 38 | ${OPENSSL_LIBRARIES} 39 | ${SQLITE3_LIBRARIES}) 40 | install(TARGETS ${PROJECT_NAME} 41 | RUNTIME DESTINATION ${CMAKE_BINARY_DIR}/..) 42 | cotire(${PROJECT_NAME}) 43 | -------------------------------------------------------------------------------- /src/result.h: -------------------------------------------------------------------------------- 1 | #ifndef RESULT_H 2 | #define RESULT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Stores the information of a complete 10 | // long/short trade (2 entry trades, 2 exit trades). 11 | struct Result { 12 | 13 | unsigned id; 14 | unsigned idExchLong; 15 | unsigned idExchShort; 16 | double exposure; 17 | double feesLong; 18 | double feesShort; 19 | std::time_t entryTime; 20 | std::time_t exitTime; 21 | std::string exchNameLong; 22 | std::string exchNameShort; 23 | double priceLongIn; 24 | double priceShortIn; 25 | double priceLongOut; 26 | double priceShortOut; 27 | double spreadIn; 28 | double spreadOut; 29 | double exitTarget; 30 | // FIXME: the arrays should have a dynamic size 31 | double minSpread[13][13]; 32 | double maxSpread[13][13]; 33 | double trailing[13][13]; 34 | unsigned trailingWaitCount[13][13]; 35 | std::list volatility[13][13]; 36 | double leg2TotBalanceBefore; 37 | double leg2TotBalanceAfter; 38 | 39 | double targetPerfLong() const; 40 | double targetPerfShort() const; 41 | double actualPerf() const; 42 | double getTradeLengthInMinute() const; 43 | 44 | // Prints the entry trade info to the log file 45 | void printEntryInfo(std::ostream &logFile) const; 46 | // Prints the exit trade info to the log file 47 | void printExitInfo(std::ostream &logFile) const; 48 | 49 | // Resets the structures 50 | void reset(); 51 | 52 | // Tries to load the state from a previous position 53 | // from the restore.txt file. 54 | bool loadPartialResult(std::string filename); 55 | 56 | // Saves the state from a previous position 57 | // into the restore.txt file. 58 | void savePartialResult(std::string filename); 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/db_fun.cpp: -------------------------------------------------------------------------------- 1 | #include "parameters.h" 2 | #include "unique_sqlite.hpp" 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Defines some helper overloads to ease sqlite resource management 9 | namespace { 10 | template 11 | class sqlite_proxy { 12 | typename UNIQUE_T::pointer S; 13 | UNIQUE_T &owner; 14 | 15 | public: 16 | sqlite_proxy(UNIQUE_T &owner) : S(nullptr), owner(owner) 17 | {} 18 | ~sqlite_proxy() { owner.reset(S); } 19 | operator typename UNIQUE_T::pointer* () { return &S; } 20 | }; 21 | 22 | template 23 | sqlite_proxy< std::unique_ptr > 24 | acquire(std::unique_ptr &owner) { return owner; } 25 | } 26 | 27 | int createDbConnection(Parameters& params) { 28 | int res = sqlite3_open(params.dbFile.c_str(), acquire(params.dbConn)); 29 | 30 | if (res != SQLITE_OK) 31 | std::cerr << sqlite3_errmsg(params.dbConn.get()) << std::endl; 32 | 33 | return res; 34 | } 35 | 36 | int createTable(std::string exchangeName, Parameters& params) { 37 | 38 | std::string query = "CREATE TABLE IF NOT EXISTS `" + exchangeName + 39 | "` (Datetime DATETIME NOT NULL, bid DECIMAL(8, 2), ask DECIMAL(8, 2));"; 40 | unique_sqlerr errmsg; 41 | int res = sqlite3_exec(params.dbConn.get(), query.c_str(), nullptr, nullptr, acquire(errmsg)); 42 | if (res != SQLITE_OK) 43 | std::cerr << errmsg.get() << std::endl; 44 | 45 | return res; 46 | } 47 | 48 | int addBidAskToDb(std::string exchangeName, std::string datetime, double bid, double ask, Parameters& params) { 49 | std::string query = "INSERT INTO `" + exchangeName + 50 | "` VALUES ('" + datetime + 51 | "'," + std::to_string(bid) + 52 | "," + std::to_string(ask) + ");"; 53 | unique_sqlerr errmsg; 54 | int res = sqlite3_exec(params.dbConn.get(), query.c_str(), nullptr, nullptr, acquire(errmsg)); 55 | if (res != SQLITE_OK) 56 | std::cerr << errmsg.get() << std::endl; 57 | 58 | return res; 59 | } 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | sudo: required 4 | dist: trusty 5 | 6 | compiler: 7 | - clang 8 | - gcc 9 | 10 | os: 11 | - linux 12 | - osx 13 | 14 | env: 15 | - BUILD_TYPE=Debug 16 | - BUILD_TYPE=Release 17 | 18 | before_install: 19 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ;fi 20 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ;fi 21 | 22 | install: 23 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y libjansson-dev libcurl4-openssl-dev sendemail ;fi 24 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install cmake || brew upgrade cmake ;fi 25 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl || brew upgrade openssl ;fi 26 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install jansson || brew upgrade jansson ;fi 27 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install curl || brew upgrade curl ;fi 28 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sqlite3 || brew upgrade sqlite3 ;fi 29 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sendemail || brew upgrade sendemail ;fi 30 | 31 | before_script: 32 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LDFLAGS="-L/usr/local/opt/curl/lib" ;fi 33 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CPPFLAGS="-I/usr/local/opt/curl/include" ;fi 34 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export OPENSSL_ROOT_DIR="/usr/local/opt/openssl" ;fi 35 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export OPENSSL_LIBRARIES="/usr/local/opt/openssl/lib" ;fi 36 | - cmake --version 37 | 38 | script: 39 | - cmake -B./build -H. -DCMAKE_BUILD_TYPE=$BUILD_TYPE 40 | - cmake --build ./build -- install 41 | -------------------------------------------------------------------------------- /blackbird.conf: -------------------------------------------------------------------------------- 1 | # Blackbird Bitcoin Arbitrage 2 | # Configuration file 3 | 4 | 5 | # Program parameters 6 | DemoMode=true 7 | Leg1=BTC 8 | Leg2=USD 9 | UseFullExposure=false 10 | TestedExposure=25.00 11 | MaxExposure=25000.00 12 | MaxLength=5184000 13 | DebugMaxIteration=3200000 14 | Verbose=true 15 | CACert=curl-ca-bundle.crt 16 | 17 | # Strategy parameters 18 | Interval=3.0 19 | SpreadEntry=0.0080 20 | SpreadTarget=0.0050 21 | PriceDeltaLimit=0.10 22 | TrailingSpreadLim=0.0008 23 | TrailingSpreadCount=1 24 | OrderBookFactor=3.0 25 | UseVolatility=false 26 | VolatilityPeriod=600 27 | 28 | # Email settings 29 | SendEmail=false 30 | SenderAddress= 31 | SenderUsername= 32 | SenderPassword= 33 | SmtpServerAddress= 34 | ReceiverAddress= 35 | 36 | # Database settings 37 | DBFile=blackbird.db 38 | 39 | # Bitfinex 40 | BitfinexApiKey= 41 | BitfinexSecretKey= 42 | BitfinexFees=0.0020 43 | BitfinexEnable=true 44 | 45 | # OkCoin 46 | OkCoinApiKey= 47 | OkCoinSecretKey= 48 | OkCoinFees=0.0020 49 | OkCoinEnable=true 50 | 51 | # Bitstamp 52 | BitstampClientId= 53 | BitstampApiKey= 54 | BitstampSecretKey= 55 | BitstampFees=0.0025 56 | BitstampEnable=true 57 | 58 | # Gemini 59 | GeminiApiKey= 60 | GeminiSecretKey= 61 | GeminiFees=0.0025 62 | GeminiEnable=true 63 | 64 | # Kraken 65 | KrakenApiKey= 66 | KrakenSecretKey= 67 | KrakenFees=0.0025 68 | KrakenEnable=true 69 | 70 | # ItBit 71 | ItBitApiKey= 72 | ItBitSecretKey= 73 | ItBitFees=0.0020 74 | ItBitEnable=true 75 | 76 | # WEX 77 | WEXApiKey= 78 | WEXSecretKey= 79 | WEXFees=0.0020 80 | WEXEnable=false 81 | 82 | # Poloniex 83 | # Note: no BTC/USD on Poloniex 84 | PoloniexApiKey= 85 | PoloniexSecretKey= 86 | PoloniexFees=0.0020 87 | PoloniexEnable=true 88 | 89 | # GDAX 90 | GDAXApiKey= 91 | GDAXSecretKey= 92 | GDAXPhrase= 93 | GDAXFees=0.0025 94 | GDAXEnable=true 95 | 96 | # QuadrigaCX 97 | QuadrigaApiKey= 98 | QuadrigaSecretKey= 99 | QuadrigaFees=0.005 100 | QuadrigaClientId= 101 | QuadrigaEnable=true 102 | 103 | # Exmo 104 | ExmoApiKey= 105 | ExmoSecretKey= 106 | ExmoFees=0.002 107 | ExmoEnable=true 108 | 109 | # Cex.io 110 | CexioClientId= 111 | CexioApiKey= 112 | CexioSecretKey= 113 | CexioFees=0.0025 114 | CexioEnable=true 115 | 116 | # Bittrex 117 | BittrexApiKey= 118 | BittrexSecretKey= 119 | BittrexFees= 120 | BittrexEnable=true 121 | 122 | # Binance 123 | BinanceApiKey= 124 | BinanceSecretKey= 125 | BinanceFees= 126 | BinanceEnable=true 127 | -------------------------------------------------------------------------------- /src/curl_fun.cpp: -------------------------------------------------------------------------------- 1 | #include "curl_fun.h" 2 | #include "parameters.h" 3 | 4 | #include "curl/curl.h" 5 | #include "jansson.h" 6 | #include 7 | #include 8 | 9 | 10 | size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { 11 | ((std::string*)userp)->append((char*)contents, size * nmemb); 12 | return size * nmemb; 13 | } 14 | 15 | json_t* getJsonFromUrl(Parameters& params, std::string url, std::string postFields, 16 | bool getRequest) { 17 | 18 | if (!params.cacert.empty()) 19 | curl_easy_setopt(params.curl, CURLOPT_CAINFO, params.cacert.c_str()); 20 | 21 | curl_easy_setopt(params.curl, CURLOPT_URL, url.c_str()); 22 | curl_easy_setopt(params.curl, CURLOPT_CONNECTTIMEOUT, 10L); 23 | curl_easy_setopt(params.curl, CURLOPT_TIMEOUT, 20L); 24 | curl_easy_setopt(params.curl, CURLOPT_ENCODING, "gzip"); 25 | if (!postFields.empty()) 26 | curl_easy_setopt(params.curl, CURLOPT_POSTFIELDS, postFields.c_str()); 27 | 28 | curl_easy_setopt(params.curl, CURLOPT_WRITEFUNCTION, WriteCallback); 29 | std::string readBuffer; 30 | curl_easy_setopt(params.curl, CURLOPT_WRITEDATA, &readBuffer); 31 | curl_easy_setopt(params.curl, CURLOPT_DNS_CACHE_TIMEOUT, 3600); 32 | // Some calls must be GET requests 33 | if (getRequest) 34 | curl_easy_setopt(params.curl, CURLOPT_CUSTOMREQUEST, "GET"); 35 | 36 | goto curl_state; 37 | 38 | retry_state: 39 | std::this_thread::sleep_for(std::chrono::seconds(2)); 40 | readBuffer.clear(); 41 | curl_easy_setopt(params.curl, CURLOPT_DNS_CACHE_TIMEOUT, 0); 42 | 43 | curl_state: 44 | CURLcode resCurl = curl_easy_perform(params.curl); 45 | if (resCurl != CURLE_OK) { 46 | *params.logFile << "Error with cURL: " << curl_easy_strerror(resCurl) << '\n' 47 | << " URL: " << url << '\n' 48 | << " Retry in 2 sec..." << std::endl; 49 | 50 | goto retry_state; 51 | } 52 | 53 | /* documentation label */ 54 | // json_state: 55 | json_error_t error; 56 | json_t *root = json_loads(readBuffer.c_str(), 0, &error); 57 | if (!root) { 58 | *params.logFile << "Error with JSON:\n" << error.text << '\n' 59 | << "Buffer:\n" << readBuffer << '\n' 60 | << "Retrying..." << std::endl; 61 | 62 | goto retry_state; 63 | } 64 | 65 | // Change back to POST request 66 | if (getRequest) 67 | curl_easy_setopt(params.curl, CURLOPT_CUSTOMREQUEST, "POST"); 68 | 69 | return root; 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/send_email.cpp: -------------------------------------------------------------------------------- 1 | #include "send_email.h" 2 | #include "result.h" 3 | #include "parameters.h" 4 | #include "time_fun.h" 5 | #include 6 | 7 | 8 | void sendEmail(const Result &res, Parameters ¶ms) { 9 | 10 | char tdStyle[] = "font-family:Georgia;font-size:11px;border-color:#A1A1A1;border-width:1px;border-style:solid;padding:2px;"; 11 | char captionStyle[] = "font-family:Georgia;font-size:13px;font-weight:normal;color:#0021BF;padding-bottom:6px;text-align:left;"; 12 | char tableTitleStyle[] = "font-family:Georgia;font-variant:small-caps;font-size:13px;text-align:center;border-color:#A1A1A1;border-width:1px;border-style:solid;background-color:#EAEAEA;"; 13 | std::ostringstream oss; 14 | oss.precision(2); 15 | oss << std::fixed; 16 | oss << "sendemail -f " << params.senderAddress << " -t " << params.receiverAddress << " -u \"Blackbird Bitcoin Arbitrage - Trade " << res.id <<" ("; 17 | 18 | if (res.actualPerf() >= 0) oss << "+"; 19 | 20 | oss << res.actualPerf() * 100.0; 21 | oss << "%)\" -m \""; 22 | oss << ""; 23 | oss << "
"; 24 | oss << "

"; 25 | oss << " "; 26 | oss << " "; 27 | oss << " "; 28 | oss << " "; 29 | oss << " "; 30 | oss << " "; 31 | oss << " "; 32 | oss << " "; 33 | oss << " "; 34 | oss << " "; 35 | oss << " "; 36 | oss << " "; 37 | oss << " "; 38 | oss << " "; 39 | oss << " "; 40 | oss << " "; 41 | oss << " "; 42 | oss << " "; 43 | 44 | oss << ""; 48 | oss << "
Blackbird Bitcoin Arbitrage - Trade " << res.id << "
Entry DateExit DateLongShortExposureProfitReturn
" << printDateTime(res.entryTime) << "" << printDateTime(res.exitTime) << "" << res.exchNameLong << "" << res.exchNameShort << "\\$" << res.exposure * 2.0 << "\\$" << res.leg2TotBalanceAfter - res.leg2TotBalanceBefore << "= 0 ? "color:#000092;\\\">+" : "color:#920000;\\\">"); 46 | 47 | oss << res.actualPerf() * 100.0 << "%
"; 49 | oss << "
"; 50 | oss << "\" -s " << params.smtpServerAddress << " -xu " << params.senderUsername << " -xp " << params.senderPassword << " -o tls=yes -o message-content-type=html >/dev/null" << std::endl; 51 | 52 | if (system(oss.str().c_str()) == -1) 53 | *params.logFile << " Error with system call" << std::endl; 54 | } 55 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1", 7 | "/usr/local/include", 8 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include", 9 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include", 10 | "/usr/include", 11 | "${workspaceRoot}", 12 | "${workspaceRoot}/src", 13 | "/usr/include/c++/4.2.1", 14 | "/System/Library/Frameworks/Kernel.framework/Versions/A/Headers", 15 | "/usr/include/c++/4.2.1/tr1" 16 | ], 17 | "defines": [], 18 | "intelliSenseMode": "clang-x64", 19 | "browse": { 20 | "path": [ 21 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1", 22 | "/usr/local/include", 23 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include", 24 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include", 25 | "/usr/include", 26 | "${workspaceRoot}", 27 | "${workspaceRoot}/src" 28 | ], 29 | "limitSymbolsToIncludedHeaders": true, 30 | "databaseFilename": "" 31 | }, 32 | "macFrameworkPath": [ 33 | "/System/Library/Frameworks", 34 | "/Library/Frameworks" 35 | ] 36 | }, 37 | { 38 | "name": "Linux", 39 | "includePath": [ 40 | "/usr/include", 41 | "/usr/local/include", 42 | "${workspaceRoot}" 43 | ], 44 | "defines": [], 45 | "intelliSenseMode": "clang-x64", 46 | "browse": { 47 | "path": [ 48 | "/usr/include", 49 | "/usr/local/include", 50 | "${workspaceRoot}" 51 | ], 52 | "limitSymbolsToIncludedHeaders": true, 53 | "databaseFilename": "" 54 | } 55 | }, 56 | { 57 | "name": "Win32", 58 | "includePath": [ 59 | "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include", 60 | "${workspaceRoot}" 61 | ], 62 | "defines": [ 63 | "_DEBUG", 64 | "UNICODE" 65 | ], 66 | "intelliSenseMode": "msvc-x64", 67 | "browse": { 68 | "path": [ 69 | "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", 70 | "${workspaceRoot}" 71 | ], 72 | "limitSymbolsToIncludedHeaders": true, 73 | "databaseFilename": "" 74 | } 75 | } 76 | ], 77 | "version": 3 78 | } -------------------------------------------------------------------------------- /src/parameters.h: -------------------------------------------------------------------------------- 1 | #ifndef PARAMETERS_H 2 | #define PARAMETERS_H 3 | 4 | #include "unique_sqlite.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Stores all the parameters defined 12 | // in the configuration file. 13 | struct Parameters { 14 | 15 | std::vector exchName; 16 | std::vector fees; 17 | std::vector canShort; 18 | std::vector isImplemented; 19 | std::vector tickerUrl; 20 | 21 | CURL* curl; 22 | double spreadEntry; 23 | double spreadTarget; 24 | unsigned maxLength; 25 | double priceDeltaLim; 26 | double trailingLim; 27 | unsigned trailingCount; 28 | double orderBookFactor; 29 | bool isDemoMode; 30 | std::string leg1; 31 | std::string leg2; 32 | bool verbose; 33 | std::ofstream* logFile; 34 | unsigned interval; 35 | unsigned debugMaxIteration; 36 | bool useFullExposure; 37 | double testedExposure; 38 | double maxExposure; 39 | bool useVolatility; 40 | unsigned volatilityPeriod; 41 | std::string cacert; 42 | 43 | std::string bitfinexApi; 44 | std::string bitfinexSecret; 45 | double bitfinexFees; 46 | bool bitfinexEnable; 47 | std::string okcoinApi; 48 | std::string okcoinSecret; 49 | double okcoinFees; 50 | bool okcoinEnable; 51 | std::string bitstampClientId; 52 | std::string bitstampApi; 53 | std::string bitstampSecret; 54 | double bitstampFees; 55 | bool bitstampEnable; 56 | std::string geminiApi; 57 | std::string geminiSecret; 58 | double geminiFees; 59 | bool geminiEnable; 60 | std::string krakenApi; 61 | 62 | std::string krakenSecret; 63 | double krakenFees; 64 | bool krakenEnable; 65 | std::string itbitApi; 66 | std::string itbitSecret; 67 | double itbitFees; 68 | bool itbitEnable; 69 | std::string wexApi; 70 | std::string wexSecret; 71 | double wexFees; 72 | bool wexEnable; 73 | std::string poloniexApi; 74 | std::string poloniexSecret; 75 | double poloniexFees; 76 | bool poloniexEnable; 77 | std::string gdaxApi; 78 | std::string gdaxSecret; 79 | std::string gdaxPhrase; 80 | double gdaxFees; 81 | bool gdaxEnable; 82 | std::string quadrigaApi; 83 | std::string quadrigaSecret; 84 | std::string quadrigaClientId; 85 | double quadrigaFees; 86 | bool quadrigaEnable; 87 | std::string exmoApi; 88 | std::string exmoSecret; 89 | double exmoFees; 90 | bool exmoEnable; 91 | std::string cexioClientId; 92 | std::string cexioApi; 93 | std::string cexioSecret; 94 | double cexioFees; 95 | bool cexioEnable; 96 | std::string bittrexApi; 97 | std::string bittrexSecret; 98 | double bittrexFees; 99 | bool bittrexEnable; 100 | std::string binanceApi; 101 | std::string binanceSecret; 102 | double binanceFees; 103 | bool binanceEnable; 104 | 105 | bool sendEmail; 106 | std::string senderAddress; 107 | std::string senderUsername; 108 | std::string senderPassword; 109 | std::string smtpServerAddress; 110 | std::string receiverAddress; 111 | 112 | std::string dbFile; 113 | unique_sqlite dbConn; 114 | 115 | Parameters(std::string fileName); 116 | 117 | void addExchange(std::string n, double f, bool h, bool m); 118 | 119 | int nbExch() const; 120 | }; 121 | 122 | // Copies the parameters from the configuration file 123 | // to the Parameter structure. 124 | std::string getParameter(std::string parameter, std::ifstream& configFile); 125 | 126 | bool getBool(std::string value); 127 | 128 | double getDouble(std::string value); 129 | 130 | unsigned getUnsigned(std::string value); 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/utils/restapi.cpp: -------------------------------------------------------------------------------- 1 | #include "restapi.h" 2 | 3 | #include "jansson.h" 4 | #include 5 | #include 6 | #include // sleep 7 | 8 | 9 | namespace { 10 | 11 | // automatic init of curl's systems 12 | struct CurlStartup { 13 | CurlStartup() { curl_global_init(CURL_GLOBAL_ALL); } 14 | ~CurlStartup() { curl_global_cleanup(); } 15 | }runCurlStartup; 16 | 17 | // internal helpers 18 | size_t recvCallback(void *contents, size_t size, size_t nmemb, void *userp) { 19 | auto &buffer = *static_cast (userp); 20 | auto n = size * nmemb; 21 | return buffer.append((char*)contents, n), n; 22 | } 23 | 24 | json_t* doRequest(CURL *C, 25 | const std::string &url, 26 | const curl_slist *headers, 27 | std::ostream &log) { 28 | std::string recvBuffer; 29 | curl_easy_setopt(C, CURLOPT_WRITEDATA, &recvBuffer); 30 | 31 | curl_easy_setopt(C, CURLOPT_URL, url.c_str()); 32 | curl_easy_setopt(C, CURLOPT_HTTPHEADER, headers); 33 | curl_easy_setopt(C, CURLOPT_DNS_CACHE_TIMEOUT, 3600); 34 | 35 | goto curl_state; 36 | 37 | retry_state: 38 | log << " Retry in 2 sec..." << std::endl; 39 | std::this_thread::sleep_for(std::chrono::seconds(2)); 40 | recvBuffer.clear(); 41 | curl_easy_setopt(C, CURLOPT_DNS_CACHE_TIMEOUT, 0); 42 | 43 | curl_state: 44 | CURLcode resCurl = curl_easy_perform(C); 45 | if (resCurl != CURLE_OK) { 46 | log << "Error with cURL: " << curl_easy_strerror(resCurl) << '\n' 47 | << " URL: " << url << '\n'; 48 | 49 | goto retry_state; 50 | } 51 | 52 | /* documentation label */ 53 | // json_state: 54 | json_error_t error; 55 | json_t *root = json_loads(recvBuffer.c_str(), 0, &error); 56 | if (!root) { 57 | long resp_code; 58 | curl_easy_getinfo(C, CURLINFO_RESPONSE_CODE, &resp_code); 59 | log << "Server Response: " << resp_code << " - " << url << '\n' 60 | << "Error with JSON: " << error.text << '\n' 61 | << "Buffer:\n" << recvBuffer << '\n'; 62 | 63 | goto retry_state; 64 | } 65 | 66 | return root; 67 | } 68 | } 69 | 70 | void RestApi::CURL_deleter::operator () (CURL *C) { 71 | curl_easy_cleanup(C); 72 | } 73 | 74 | void RestApi::CURL_deleter::operator () (curl_slist *slist) { 75 | curl_slist_free_all(slist); 76 | } 77 | 78 | RestApi::RestApi(string host, const char *cacert, std::ostream &log) 79 | : C(curl_easy_init()), host(std::move(host)), log(log) { 80 | assert(C != nullptr); 81 | 82 | if (cacert) 83 | curl_easy_setopt(C.get(), CURLOPT_CAINFO, cacert); 84 | else curl_easy_setopt(C.get(), CURLOPT_SSL_VERIFYPEER, false); 85 | 86 | curl_easy_setopt(C.get(), CURLOPT_CONNECTTIMEOUT, 10L); 87 | curl_easy_setopt(C.get(), CURLOPT_TIMEOUT, 20L); 88 | curl_easy_setopt(C.get(), CURLOPT_USERAGENT, "Blackbird"); 89 | curl_easy_setopt(C.get(), CURLOPT_ACCEPT_ENCODING, "gzip"); 90 | 91 | curl_easy_setopt(C.get(), CURLOPT_WRITEFUNCTION, recvCallback); 92 | } 93 | 94 | json_t* RestApi::getRequest(const string &uri, unique_slist headers) { 95 | curl_easy_setopt(C.get(), CURLOPT_HTTPGET, true); 96 | return doRequest(C.get(), host + uri, headers.get(), log); 97 | } 98 | 99 | json_t* RestApi::postRequest (const string &uri, 100 | unique_slist headers, 101 | const string &post_data) { 102 | curl_easy_setopt(C.get(), CURLOPT_POSTFIELDS, post_data.data()); 103 | curl_easy_setopt(C.get(), CURLOPT_POSTFIELDSIZE, post_data.size()); 104 | return doRequest(C.get(), host + uri, headers.get(), log); 105 | } 106 | 107 | json_t* RestApi::postRequest (const string &uri, const string &post_data) { 108 | return postRequest(uri, nullptr, post_data); 109 | } 110 | -------------------------------------------------------------------------------- /src/utils/base64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | base64.cpp and base64.h 3 | 4 | Copyright (C) 2004-2008 René Nyffenegger 5 | 6 | This source code is provided 'as-is', without any express or implied 7 | warranty. In no event will the author be held liable for any damages 8 | arising from the use of this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, 11 | including commercial applications, and to alter it and redistribute it 12 | freely, subject to the following restrictions: 13 | 14 | 1. The origin of this source code must not be misrepresented; you must not 15 | claim that you wrote the original source code. If you use this source code 16 | in a product, an acknowledgment in the product documentation would be 17 | appreciated but is not required. 18 | 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original source code. 21 | 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | René Nyffenegger rene.nyffenegger@adp-gmbh.ch 25 | 26 | */ 27 | 28 | #include "base64.h" 29 | 30 | 31 | static const std::string base64_chars = 32 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 33 | "abcdefghijklmnopqrstuvwxyz" 34 | "0123456789+/"; 35 | 36 | 37 | static bool is_base64(unsigned char c) 38 | { 39 | return (isalnum(c) || (c == '+') || (c == '/')); 40 | } 41 | 42 | std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) 43 | { 44 | std::string ret; 45 | int i = 0; 46 | int j = 0; 47 | unsigned char char_array_3[3]; 48 | unsigned char char_array_4[4]; 49 | 50 | while (in_len--) 51 | { 52 | char_array_3[i++] = *(bytes_to_encode++); 53 | if (i == 3) 54 | { 55 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 56 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 57 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 58 | char_array_4[3] = char_array_3[2] & 0x3f; 59 | 60 | for(i = 0; (i <4) ; i++) 61 | ret += base64_chars[char_array_4[i]]; 62 | i = 0; 63 | } 64 | } 65 | 66 | if (i) 67 | { 68 | for(j = i; j < 3; j++) 69 | char_array_3[j] = '\0'; 70 | 71 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 72 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 73 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 74 | char_array_4[3] = char_array_3[2] & 0x3f; 75 | 76 | for (j = 0; (j < i + 1); j++) 77 | ret += base64_chars[char_array_4[j]]; 78 | 79 | while(i++ < 3) 80 | ret += '='; 81 | } 82 | 83 | return ret; 84 | } 85 | 86 | std::string base64_decode(std::string const& encoded_string) 87 | { 88 | int in_len = encoded_string.size(); 89 | int i = 0; 90 | int j = 0; 91 | int in_ = 0; 92 | unsigned char char_array_4[4], char_array_3[3]; 93 | std::string ret; 94 | 95 | while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) 96 | { 97 | char_array_4[i++] = encoded_string[in_]; in_++; 98 | if (i == 4) 99 | { 100 | for (i = 0; i <4; i++) 101 | char_array_4[i] = base64_chars.find(char_array_4[i]); 102 | 103 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 104 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 105 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 106 | 107 | for (i = 0; (i < 3); i++) 108 | ret += char_array_3[i]; 109 | i = 0; 110 | } 111 | } 112 | 113 | if (i) 114 | { 115 | for (j = i; j < 4; j++) 116 | char_array_4[j] = 0; 117 | 118 | for (j = 0; j <4; j++) 119 | char_array_4[j] = base64_chars.find(char_array_4[j]); 120 | 121 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 122 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 123 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 124 | 125 | for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; 126 | } 127 | 128 | return ret; 129 | } 130 | -------------------------------------------------------------------------------- /src/result.cpp: -------------------------------------------------------------------------------- 1 | #include "result.h" 2 | #include "time_fun.h" 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | double Result::targetPerfLong() const { 9 | return (priceLongOut - priceLongIn) / priceLongIn - 2.0 * feesLong; 10 | } 11 | 12 | double Result::targetPerfShort() const { 13 | return (priceShortIn - priceShortOut) / priceShortIn - 2.0 * feesShort; 14 | } 15 | 16 | double Result::actualPerf() const { 17 | if (exposure == 0.0) return 0.0; 18 | // The performance is computed as an "actual" performance, 19 | // by simply comparing what amount was on our leg2 account 20 | // before, and after, the arbitrage opportunity. This way, 21 | // we are sure that every fees was taking out of the performance 22 | // computation. Hence the "actual" performance. 23 | return (leg2TotBalanceAfter - leg2TotBalanceBefore) / (exposure * 2.0); 24 | } 25 | 26 | double Result::getTradeLengthInMinute() const { 27 | if (entryTime > 0 && exitTime > 0) return (exitTime - entryTime) / 60.0; 28 | return 0; 29 | } 30 | 31 | void Result::printEntryInfo(std::ostream &logFile) const { 32 | logFile.precision(2); 33 | logFile << "\n[ ENTRY FOUND ]" << std::endl; 34 | logFile << " Date & Time: " << printDateTime(entryTime) << std::endl; 35 | logFile << " Exchange Long: " << exchNameLong << " (id " << idExchLong << ")" << std::endl; 36 | logFile << " Exchange Short: " << exchNameShort << " (id " << idExchShort << ")" << std::endl; 37 | logFile << " Fees: " << feesLong * 100.0 << "% / " << feesShort * 100.0 << "%" << std::endl; 38 | logFile << " Price Long: " << priceLongIn << " (target)" << std::endl; 39 | logFile << " Price Short: " << priceShortIn << " (target)" << std::endl; 40 | logFile << " Spread: " << spreadIn * 100.0 << "%" << std::endl; 41 | logFile << " Cash used: " << exposure << " on each exchange" << std::endl; 42 | logFile << " Exit Target: " << exitTarget * 100.0 << "%" << std::endl; 43 | logFile << std::endl; 44 | } 45 | 46 | void Result::printExitInfo(std::ostream &logFile) const { 47 | logFile.precision(2); 48 | logFile << "\n[ EXIT FOUND ]" << std::endl; 49 | logFile << " Date & Time: " << printDateTime(exitTime) << std::endl; 50 | logFile << " Duration: " << getTradeLengthInMinute() << " minutes" << std::endl; 51 | logFile << " Price Long: " << priceLongOut << " (target)" << std::endl; 52 | logFile << " Price Short: " << priceShortOut << " (target)" << std::endl; 53 | logFile << " Spread: " << spreadOut * 100.0 << "%" << std::endl; 54 | logFile << " ---------------------------" << std::endl; 55 | logFile << " Target Perf Long: " << targetPerfLong() * 100.0 << "% (fees incl.)" << std::endl; 56 | logFile << " Target Perf Short: " << targetPerfShort() * 100.0 << "% (fees incl.)" << std::endl; 57 | logFile << " ---------------------------\n" << std::endl; 58 | } 59 | 60 | // not sure to understand how this function is implemented ;-) 61 | void Result::reset() { 62 | typedef std::remove_reference< decltype(minSpread[0][0]) >::type arr2d_t; 63 | auto arr2d_size = sizeof(minSpread) / sizeof(arr2d_t); 64 | Result tmp {}; 65 | std::swap(tmp, *this); 66 | // Resets all the values of min, max and trailing arrays to values 67 | // that will be erased by the very first value entered in the respective arrays. 68 | // That's why the reset value for min is 1.0 and for max is -1.0. 69 | std::fill_n(reinterpret_cast(minSpread), arr2d_size, 1.0); 70 | std::fill_n(reinterpret_cast(maxSpread), arr2d_size, -1.0); 71 | std::fill_n(reinterpret_cast(trailing), arr2d_size, -1.0); 72 | } 73 | 74 | bool Result::loadPartialResult(std::string filename) { 75 | 76 | std::ifstream resFile(filename, std::ifstream::ate); 77 | if(!resFile || int(resFile.tellg()) == 0) return false; 78 | 79 | resFile.seekg(0); 80 | resFile >> id 81 | >> idExchLong >> idExchShort 82 | >> exchNameLong >> exchNameShort 83 | >> exposure 84 | >> feesLong 85 | >> feesShort 86 | >> entryTime 87 | >> spreadIn 88 | >> priceLongIn 89 | >> priceShortIn 90 | >> leg2TotBalanceBefore 91 | >> exitTarget; 92 | 93 | resFile >> maxSpread[idExchLong][idExchShort] 94 | >> minSpread[idExchLong][idExchShort] 95 | >> trailing[idExchLong][idExchShort] 96 | >> trailingWaitCount[idExchLong][idExchShort]; 97 | 98 | return true; 99 | } 100 | 101 | void Result::savePartialResult(std::string filename) { 102 | std::ofstream resFile(filename, std::ofstream::trunc); 103 | 104 | resFile << id << '\n' 105 | << idExchLong << '\n' 106 | << idExchShort << '\n' 107 | << exchNameLong << '\n' 108 | << exchNameShort << '\n' 109 | << exposure << '\n' 110 | << feesLong << '\n' 111 | << feesShort << '\n' 112 | << entryTime << '\n' 113 | << spreadIn << '\n' 114 | << priceLongIn << '\n' 115 | << priceShortIn << '\n' 116 | << leg2TotBalanceBefore << '\n' 117 | << exitTarget << '\n'; 118 | 119 | resFile << maxSpread[idExchLong][idExchShort] << '\n' 120 | << minSpread[idExchLong][idExchShort] << '\n' 121 | << trailing[idExchLong][idExchShort] << '\n' 122 | << trailingWaitCount[idExchLong][idExchShort] 123 | << std::endl; 124 | } 125 | -------------------------------------------------------------------------------- /src/exchanges/bitstamp.cpp: -------------------------------------------------------------------------------- 1 | #include "bitstamp.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "unique_json.hpp" 6 | #include "hex_str.hpp" 7 | 8 | #include "openssl/sha.h" 9 | #include "openssl/hmac.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace Bitstamp { 19 | 20 | static json_t* authRequest(Parameters &, std::string, std::string); 21 | 22 | static RestApi& queryHandle(Parameters ¶ms) 23 | { 24 | static RestApi query ("https://www.bitstamp.net", 25 | params.cacert.c_str(), *params.logFile); 26 | return query; 27 | } 28 | 29 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 30 | { 31 | auto errstatus = json_object_get(root, "error"); 32 | if (errstatus) 33 | { 34 | // Bitstamp errors could sometimes be strings or objects containing error string 35 | auto errmsg = json_dumps(errstatus, JSON_ENCODE_ANY); 36 | logFile << " Error with response: " 37 | << errmsg << '\n'; 38 | free(errmsg); 39 | } 40 | 41 | return root; 42 | } 43 | 44 | quote_t getQuote(Parameters& params) 45 | { 46 | auto &exchange = queryHandle(params); 47 | unique_json root { exchange.getRequest("/api/ticker") }; 48 | 49 | const char *quote = json_string_value(json_object_get(root.get(), "bid")); 50 | auto bidValue = quote ? atof(quote) : 0.0; 51 | 52 | quote = json_string_value(json_object_get(root.get(), "ask")); 53 | auto askValue = quote ? atof(quote) : 0.0; 54 | 55 | return std::make_pair(bidValue, askValue); 56 | } 57 | 58 | double getAvail(Parameters& params, std::string currency) 59 | { 60 | unique_json root { authRequest(params, "/api/balance/", "") }; 61 | while (json_object_get(root.get(), "message") != NULL) 62 | { 63 | std::this_thread::sleep_for(std::chrono::seconds(1)); 64 | auto dump = json_dumps(root.get(), 0); 65 | *params.logFile << " Error with JSON: " << dump << ". Retrying..." << std::endl; 66 | free(dump); 67 | root.reset(authRequest(params, "/api/balance/", "")); 68 | } 69 | double availability = 0.0; 70 | const char* returnedText = NULL; 71 | if (currency == "btc") 72 | { 73 | returnedText = json_string_value(json_object_get(root.get(), "btc_balance")); 74 | } 75 | else if (currency == "usd") 76 | { 77 | returnedText = json_string_value(json_object_get(root.get(), "usd_balance")); 78 | } 79 | if (returnedText != NULL) 80 | { 81 | availability = atof(returnedText); 82 | } 83 | else 84 | { 85 | *params.logFile << " Error with the credentials." << std::endl; 86 | availability = 0.0; 87 | } 88 | 89 | return availability; 90 | } 91 | 92 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) 93 | { 94 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 95 | << std::setprecision(6) << quantity << "@$" 96 | << std::setprecision(2) << price << "...\n"; 97 | std::string url = "/api/" + direction + '/'; 98 | 99 | std::ostringstream oss; 100 | oss << "amount=" << quantity << "&price=" << std::fixed << std::setprecision(2) << price; 101 | std::string options = oss.str(); 102 | unique_json root { authRequest(params, url, options) }; 103 | auto orderId = std::to_string(json_integer_value(json_object_get(root.get(), "id"))); 104 | if (orderId == "0") 105 | { 106 | auto dump = json_dumps(root.get(), 0); 107 | *params.logFile << " Order ID = 0. Message: " << dump << '\n'; 108 | free(dump); 109 | } 110 | *params.logFile << " Done (order ID: " << orderId << ")\n" << std::endl; 111 | 112 | return orderId; 113 | } 114 | 115 | bool isOrderComplete(Parameters& params, std::string orderId) 116 | { 117 | if (orderId == "0") return true; 118 | 119 | auto options = "id=" + orderId; 120 | unique_json root { authRequest(params, "/api/order_status/", options) }; 121 | auto status = json_string_value(json_object_get(root.get(), "status")); 122 | return status && status == std::string("Finished"); 123 | } 124 | 125 | double getActivePos(Parameters& params) { return getAvail(params, "btc"); } 126 | 127 | double getLimitPrice(Parameters& params, double volume, bool isBid) 128 | { 129 | auto &exchange = queryHandle(params); 130 | unique_json root { exchange.getRequest("/api/order_book") }; 131 | auto orderbook = json_object_get(root.get(), isBid ? "bids" : "asks"); 132 | 133 | // loop on volume 134 | *params.logFile << " Looking for a limit price to fill " 135 | << std::setprecision(6) << fabs(volume) << " BTC...\n"; 136 | double tmpVol = 0.0; 137 | double p = 0.0; 138 | double v; 139 | int i = 0; 140 | while (tmpVol < fabs(volume) * params.orderBookFactor) 141 | { 142 | p = atof(json_string_value(json_array_get(json_array_get(orderbook, i), 0))); 143 | v = atof(json_string_value(json_array_get(json_array_get(orderbook, i), 1))); 144 | *params.logFile << " order book: " 145 | << std::setprecision(6) << v << "@$" 146 | << std::setprecision(2) << p << std::endl; 147 | tmpVol += v; 148 | i++; 149 | } 150 | return p; 151 | } 152 | 153 | json_t* authRequest(Parameters ¶ms, std::string request, std::string options) 154 | { 155 | static uint64_t nonce = time(nullptr) * 4; 156 | auto msg = std::to_string(++nonce) + params.bitstampClientId + params.bitstampApi; 157 | uint8_t *digest = HMAC (EVP_sha256(), 158 | params.bitstampSecret.c_str(), params.bitstampSecret.size(), 159 | reinterpret_cast(msg.data()), msg.size(), 160 | nullptr, nullptr); 161 | 162 | std::string postParams = "key=" + params.bitstampApi + 163 | "&signature=" + hex_str(digest, digest + SHA256_DIGEST_LENGTH) + 164 | "&nonce=" + std::to_string(nonce); 165 | if (!options.empty()) 166 | { 167 | postParams += "&"; 168 | postParams += options; 169 | } 170 | 171 | auto &exchange = queryHandle(params); 172 | return checkResponse(*params.logFile, exchange.postRequest(request, postParams)); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/exchanges/wex.cpp: -------------------------------------------------------------------------------- 1 | #include "wex.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "unique_json.hpp" 5 | #include "hex_str.hpp" 6 | 7 | #include "openssl/sha.h" 8 | #include "openssl/hmac.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include // fabs 14 | #include 15 | 16 | namespace WEX { 17 | 18 | static json_t* authRequest(Parameters &, const char *, const std::string & = ""); 19 | static json_t* adjustResponse(json_t *); 20 | 21 | static RestApi& queryHandle(Parameters ¶ms) 22 | { 23 | static RestApi query ("https://wex.nz", 24 | params.cacert.c_str(), *params.logFile); 25 | return query; 26 | } 27 | 28 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 29 | { 30 | unique_json own { root }; 31 | auto success = json_object_get(root, "success"); 32 | if (json_integer_value(success) == 0) 33 | { 34 | auto errmsg = json_object_get(root, "error"); 35 | logFile << " Error with response: " 36 | << json_string_value(errmsg) << '\n'; 37 | } 38 | 39 | auto result = json_object_get(root, "return"); 40 | json_incref(result); 41 | return result; 42 | } 43 | 44 | quote_t getQuote(Parameters& params) 45 | { 46 | auto &exchange = queryHandle(params); 47 | unique_json root { exchange.getRequest("/api/3/ticker/btc_usd") }; 48 | 49 | double bidValue = json_number_value(json_object_get(json_object_get(root.get(), "btc_usd"), "sell")); 50 | double askValue = json_number_value(json_object_get(json_object_get(root.get(), "btc_usd"), "buy")); 51 | 52 | return std::make_pair(bidValue, askValue); 53 | } 54 | 55 | double getAvail(Parameters ¶ms, std::string currency) 56 | { 57 | unique_json root { authRequest(params, "getInfo") }; 58 | auto funds = json_object_get(json_object_get(root.get(), "funds"), currency.c_str()); 59 | return json_number_value(funds); 60 | } 61 | 62 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price) 63 | { 64 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 65 | << std::fixed 66 | << std::setprecision(6) << quantity << "@$" 67 | << std::setprecision(2) << price << "...\n"; 68 | std::ostringstream options; 69 | options << "pair=btc_usd" 70 | << "&type=" << direction 71 | << "&amount=" << std::fixed << quantity; 72 | // WEX's 'Trade' method requires rate to be limited to 3 decimals 73 | // otherwise it'll barf an error message about incorrect fields 74 | options << "&rate=" << std::setprecision(3) << price; 75 | unique_json root { authRequest(params, "Trade", options.str()) }; 76 | 77 | auto orderid = json_integer_value(json_object_get(root.get(), "order_id")); 78 | *params.logFile << " Done (order ID: " << orderid << ")\n" << std::endl; 79 | return std::to_string(orderid); 80 | } 81 | 82 | bool isOrderComplete(Parameters& params, std::string orderId) 83 | { 84 | if (orderId == "0") return true; 85 | unique_json root { authRequest(params, "ActiveOrders", "pair=btc_usd") }; 86 | 87 | return json_object_get(root.get(), orderId.c_str()) == nullptr; 88 | } 89 | 90 | double getActivePos(Parameters& params) 91 | { 92 | // TODO: 93 | // this implementation is more of a placeholder copied from other exchanges; 94 | // may not be reliable. 95 | return getAvail(params, "btc"); 96 | } 97 | 98 | double getLimitPrice(Parameters& params, double volume, bool isBid) 99 | { 100 | auto &exchange = queryHandle(params); 101 | unique_json root { exchange.getRequest("/api/3/depth/btc_usd") }; 102 | auto bidask = json_object_get(json_object_get(root.get(), "btc_usd"), isBid ? "bids" : "asks"); 103 | double price = 0.0, sumvol = 0.0; 104 | for (size_t i = 0, n = json_array_size(bidask); i < n; ++i) 105 | { 106 | auto currnode = json_array_get(bidask, i); 107 | price = json_number_value(json_array_get(currnode, 0)); 108 | sumvol += json_number_value(json_array_get(currnode, 1)); 109 | *params.logFile << " order book: " 110 | << std::setprecision(6) << sumvol << "@$" 111 | << std::setprecision(2) << price << std::endl; 112 | if (sumvol >= std::fabs(volume) * params.orderBookFactor) break; 113 | } 114 | return price; 115 | } 116 | 117 | /* 118 | * This is here to handle annoying inconsistences in wex's api. 119 | * For example, if there are no open orders, the 'ActiveOrders' 120 | * method returns an *error* instead of an empty object/array. 121 | * This function turns that error into an empty object for sake 122 | * of regularity. 123 | */ 124 | json_t* adjustResponse(json_t *root) 125 | { 126 | auto errmsg = json_object_get(root, "error"); 127 | if (!errmsg) return root; 128 | 129 | if (json_string_value(errmsg) == std::string("no orders")) 130 | { 131 | int err = 0; 132 | err += json_integer_set(json_object_get(root, "success"), 1); 133 | err += json_object_set_new(root, "return", json_object()); 134 | err += json_object_del(root, "error"); 135 | assert (err == 0); 136 | } 137 | 138 | return root; 139 | } 140 | 141 | json_t* authRequest(Parameters ¶ms, const char *request, const std::string &options) 142 | { 143 | using namespace std; 144 | // WEX requires nonce to be [1, 2^32 - 1) 145 | constexpr auto MAXCALLS_PER_SEC = 3ull; 146 | static auto nonce = static_cast (time(nullptr) * MAXCALLS_PER_SEC); 147 | string post_body = "nonce=" + to_string(++nonce) + 148 | "&method=" + request; 149 | if (!options.empty()) 150 | { 151 | post_body += '&'; 152 | post_body += options; 153 | } 154 | 155 | uint8_t *sign = HMAC (EVP_sha512(), 156 | params.wexSecret.data(), params.wexSecret.size(), 157 | reinterpret_cast(post_body.data()), post_body.size(), 158 | nullptr, nullptr); 159 | auto &exchange = queryHandle(params); 160 | array headers 161 | { 162 | "Key:" + params.wexApi, 163 | "Sign:" + hex_str(sign, sign + SHA512_DIGEST_LENGTH), 164 | }; 165 | auto result = exchange.postRequest ("/tapi", 166 | make_slist(begin(headers), end(headers)), 167 | post_body); 168 | return checkResponse(*params.logFile, adjustResponse(result)); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/exchanges/poloniex.cpp: -------------------------------------------------------------------------------- 1 | #include "poloniex.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "unique_json.hpp" 5 | #include "hex_str.hpp" 6 | 7 | #include "openssl/sha.h" 8 | #include "openssl/hmac.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Poloniex { 16 | 17 | static json_t* authRequest(Parameters &, const char *, const std::string & = ""); 18 | 19 | static RestApi& queryHandle(Parameters ¶ms) 20 | { 21 | static RestApi query ("https://poloniex.com", 22 | params.cacert.c_str(), *params.logFile); 23 | return query; 24 | } 25 | 26 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 27 | { 28 | auto errmsg = json_object_get(root, "error"); 29 | if (errmsg) 30 | logFile << " Error with response: " 31 | << json_string_value(errmsg) << '\n'; 32 | 33 | return root; 34 | } 35 | 36 | 37 | // We use ETH/BTC as there is no USD on Poloniex 38 | // TODO We could show BTC/USDT 39 | quote_t getQuote(Parameters ¶ms) 40 | { 41 | auto &exchange = queryHandle(params); 42 | unique_json root { exchange.getRequest("/public?command=returnTicker") }; 43 | 44 | const char *quote = json_string_value(json_object_get(json_object_get(root.get(), "USDT_BTC"), "highestBid")); 45 | auto bidValue = quote ? std::stod(quote) : 0.0; 46 | 47 | quote = json_string_value(json_object_get(json_object_get(root.get(), "USDT_BTC"), "lowestAsk")); 48 | auto askValue = quote ? std::stod(quote) : 0.0; 49 | 50 | return std::make_pair(bidValue, askValue); 51 | } 52 | 53 | double getAvail(Parameters ¶ms, std::string currency) 54 | { 55 | std::transform(begin(currency), end(currency), begin(currency), ::toupper); 56 | std::string options = "account=exchange"; 57 | if (currency.compare("USD")==0) { 58 | currency += "T"; 59 | } 60 | unique_json root { authRequest(params, "returnAvailableAccountBalances", options) }; 61 | auto funds = json_string_value(json_object_get(json_object_get(root.get(), "exchange"), currency.c_str())); 62 | return funds ? std::stod(funds) : 0.0; 63 | } 64 | 65 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) { 66 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) { 67 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 68 | return "0"; 69 | } 70 | //TODO: Real currency string 71 | std::string options = "currencyPair=USDT_BTC&rate="; 72 | std::string volume = std::to_string(quantity); 73 | std::string pricelimit = std::to_string(price); 74 | options += pricelimit + "&amount=" + volume; 75 | unique_json root { authRequest(params, direction.c_str(), options) }; 76 | std::string txid = json_string_value(json_object_get(root.get(),"orderNumber")); 77 | return txid; 78 | } 79 | 80 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price) { 81 | // TODO 82 | return "0"; 83 | } 84 | 85 | bool isOrderComplete(Parameters& params, std::string orderId) 86 | { 87 | unique_json root { authRequest(params, "returnOpenOrders", "currencyPair=USDT_BTC") }; 88 | auto n = json_array_size(root.get()); 89 | while (n --> 0) 90 | { 91 | auto item = json_object_get(json_array_get(root.get(), n), "orderNumber"); 92 | if (orderId == json_string_value(item)) 93 | return false; 94 | } 95 | return true; 96 | } 97 | 98 | double getActivePos(Parameters& params) { 99 | // TODO: When we add the new getActivePos style uncomment this 100 | // double activeSize = 0.0; 101 | // if (!orderId.empty()){ 102 | // std::string options = "orderNumber="; 103 | // options += orderId; 104 | // unique_json root {authRequest(params, "returnOrderTrades", options) }; 105 | // size_t arraySize = json_array_size(root.get()); 106 | // double tmpFee = 0.0; 107 | // double tmpAmount = 0.0; 108 | // //polo returns weird things - we must loop through all the executed trades (for partial fills) 109 | // //the actual active size is then SUM(amount*(1-tmpFee)) fee seems to be expressed as a percent 110 | // for (size_t i = 0; i < arraySize; i++) { 111 | // tmpFee = atof(json_string_value(json_object_get(json_array_get(root.get(),i),"fee"))); 112 | // tmpAmount = atof(json_string_value(json_object_get(json_array_get(root.get(),i),"amount"))); 113 | // activeSize += tmpAmount*(1-tmpFee); 114 | // } 115 | // } 116 | // return activeSize; 117 | 118 | return getAvail(params, "BTC"); 119 | } 120 | 121 | double getLimitPrice(Parameters& params, double volume, bool isBid) { 122 | auto &exchange = queryHandle(params); 123 | // TODO: build real curr string 124 | //std::string uri = "/public?command=returnOrderBook¤cyPair="; 125 | unique_json root { exchange.getRequest("/public?command=returnOrderBook¤cyPair=USDT_BTC") }; 126 | auto bidask = json_object_get(root.get(),isBid ? "bids" : "asks"); 127 | *params.logFile << " Looking for a limit price to fill " 128 | << std::setprecision(8) << fabs(volume) << " Legx...\n"; 129 | double tmpVol = 0.0; 130 | double p = 0.0; 131 | double v; 132 | int i = 0; 133 | while (tmpVol < fabs(volume) * params.orderBookFactor) 134 | { 135 | p = atof(json_string_value(json_array_get(json_array_get(bidask,i),0))); 136 | v = json_number_value(json_array_get(json_array_get(bidask,i),1)); 137 | *params.logFile << " order book: " 138 | << std::setprecision(8) << v << " @$" 139 | << std::setprecision(8) << p << std::endl; 140 | tmpVol += v; 141 | i++; 142 | } 143 | return p; 144 | } 145 | 146 | json_t* authRequest(Parameters ¶ms, const char *request, const std::string &options) 147 | { 148 | using namespace std; 149 | static uint64_t nonce = time(nullptr) * 4; 150 | string post_body = "nonce=" + to_string(++nonce) + 151 | "&command=" + request; 152 | if (!options.empty()) 153 | { 154 | post_body += '&'; 155 | post_body += options; 156 | } 157 | 158 | uint8_t *sign = HMAC (EVP_sha512(), 159 | params.poloniexSecret.data(), params.poloniexSecret.size(), 160 | reinterpret_cast(post_body.data()), post_body.size(), 161 | nullptr, nullptr); 162 | auto &exchange = queryHandle(params); 163 | array headers 164 | { 165 | "Key:" + params.poloniexApi, 166 | "Sign:" + hex_str(sign, sign + SHA512_DIGEST_LENGTH), 167 | }; 168 | return checkResponse (*params.logFile, 169 | exchange.postRequest ("/tradingApi", 170 | make_slist(begin(headers), end(headers)), 171 | post_body)); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex.cpp: -------------------------------------------------------------------------------- 1 | #include "bitfinex.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "hex_str.hpp" 6 | #include "unique_json.hpp" 7 | 8 | #include "openssl/sha.h" 9 | #include "openssl/hmac.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Bitfinex { 17 | 18 | static RestApi& queryHandle(Parameters ¶ms) 19 | { 20 | static RestApi query ("https://api.bitfinex.com", 21 | params.cacert.c_str(), *params.logFile); 22 | return query; 23 | } 24 | 25 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 26 | { 27 | auto msg = json_object_get(root, "message"); 28 | if (!msg) msg = json_object_get(root, "error"); 29 | 30 | if (msg) 31 | logFile << " Error with response: " 32 | << json_string_value(msg) << '\n'; 33 | 34 | return root; 35 | } 36 | 37 | quote_t getQuote(Parameters ¶ms) 38 | { 39 | auto &exchange = queryHandle(params); 40 | 41 | std::string url; 42 | url = "/v1/ticker/btcusd"; 43 | 44 | unique_json root { exchange.getRequest(url) }; 45 | 46 | const char *quote = json_string_value(json_object_get(root.get(), "bid")); 47 | double bidValue = quote ? std::stod(quote) : 0.0; 48 | 49 | quote = json_string_value(json_object_get(root.get(), "ask")); 50 | double askValue = quote ? std::stod(quote) : 0.0; 51 | 52 | return std::make_pair(bidValue, askValue); 53 | } 54 | 55 | double getAvail(Parameters& params, std::string currency) 56 | { 57 | unique_json root { authRequest(params, "/v1/balances", "") }; 58 | 59 | double availability = 0.0; 60 | for (size_t i = json_array_size(root.get()); i--;) 61 | { 62 | const char *each_type, *each_currency, *each_amount; 63 | json_error_t err; 64 | int unpack_fail = json_unpack_ex (json_array_get(root.get(), i), 65 | &err, 0, 66 | "{s:s, s:s, s:s}", 67 | "type", &each_type, 68 | "currency", &each_currency, 69 | "amount", &each_amount); 70 | if (unpack_fail) 71 | { 72 | *params.logFile << " Error with JSON: " 73 | << err.text << std::endl; 74 | } 75 | else if (each_type == std::string("trading") && each_currency == currency) 76 | { 77 | availability = std::stod(each_amount); 78 | break; 79 | } 80 | } 81 | return availability; 82 | } 83 | 84 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) 85 | { 86 | return sendOrder(params, direction, quantity, price); 87 | } 88 | 89 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price) 90 | { 91 | return sendOrder(params, direction, quantity, price); 92 | } 93 | 94 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price) 95 | { 96 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 97 | << std::setprecision(6) << quantity << "@$" 98 | << std::setprecision(2) << price << "...\n"; 99 | std::ostringstream oss; 100 | oss << "\"symbol\":\"btcusd\", \"amount\":\"" << quantity << "\", \"price\":\"" << price << "\", \"exchange\":\"bitfinex\", \"side\":\"" << direction << "\", \"type\":\"limit\""; 101 | std::string options = oss.str(); 102 | unique_json root { authRequest(params, "/v1/order/new", options) }; 103 | auto orderId = std::to_string(json_integer_value(json_object_get(root.get(), "order_id"))); 104 | *params.logFile << " Done (order ID: " << orderId << ")\n" << std::endl; 105 | return orderId; 106 | } 107 | 108 | bool isOrderComplete(Parameters& params, std::string orderId) 109 | { 110 | if (orderId == "0") return true; 111 | 112 | auto options = "\"order_id\":" + orderId; 113 | unique_json root { authRequest(params, "/v1/order/status", options) }; 114 | return json_is_false(json_object_get(root.get(), "is_live")); 115 | } 116 | 117 | double getActivePos(Parameters& params) 118 | { 119 | unique_json root { authRequest(params, "/v1/positions", "") }; 120 | double position; 121 | if (json_array_size(root.get()) == 0) 122 | { 123 | *params.logFile << " WARNING: BTC position not available, return 0.0" << std::endl; 124 | position = 0.0; 125 | } 126 | else 127 | { 128 | position = atof(json_string_value(json_object_get(json_array_get(root.get(), 0), "amount"))); 129 | } 130 | return position; 131 | } 132 | 133 | double getLimitPrice(Parameters& params, double volume, bool isBid) 134 | { 135 | auto &exchange = queryHandle(params); 136 | unique_json root { exchange.getRequest("/v1/book/btcusd") }; 137 | json_t *bidask = json_object_get(root.get(), isBid ? "bids" : "asks"); 138 | 139 | *params.logFile << " Looking for a limit price to fill " 140 | << std::setprecision(6) << fabs(volume) << " BTC...\n"; 141 | double tmpVol = 0.0; 142 | double p = 0.0; 143 | double v; 144 | 145 | // loop on volume 146 | for (int i = 0, n = json_array_size(bidask); i < n; ++i) 147 | { 148 | p = atof(json_string_value(json_object_get(json_array_get(bidask, i), "price"))); 149 | v = atof(json_string_value(json_object_get(json_array_get(bidask, i), "amount"))); 150 | *params.logFile << " order book: " 151 | << std::setprecision(6) << v << "@$" 152 | << std::setprecision(2) << p << std::endl; 153 | tmpVol += v; 154 | if (tmpVol >= fabs(volume) * params.orderBookFactor) break; 155 | } 156 | 157 | return p; 158 | } 159 | 160 | json_t* authRequest(Parameters ¶ms, std::string request, std::string options) 161 | { 162 | using namespace std; 163 | 164 | static uint64_t nonce = time(nullptr) * 4; 165 | 166 | string payload = "{\"request\":\"" + request + 167 | "\",\"nonce\":\"" + to_string(++nonce); 168 | if (options.empty()) 169 | { 170 | payload += "\"}"; 171 | } 172 | else 173 | { 174 | payload += "\", " + options + "}"; 175 | } 176 | 177 | payload = base64_encode(reinterpret_cast(payload.c_str()), payload.length()); 178 | 179 | // signature 180 | uint8_t *digest = HMAC (EVP_sha384(), 181 | params.bitfinexSecret.c_str(), params.bitfinexSecret.length(), 182 | reinterpret_cast (payload.data()), payload.size(), 183 | NULL, NULL); 184 | 185 | array headers 186 | { 187 | "X-BFX-APIKEY:" + params.bitfinexApi, 188 | "X-BFX-SIGNATURE:" + hex_str(digest, digest + SHA384_DIGEST_LENGTH), 189 | "X-BFX-PAYLOAD:" + payload, 190 | }; 191 | auto &exchange = queryHandle(params); 192 | auto root = exchange.postRequest (request, 193 | make_slist(begin(headers), end(headers))); 194 | return checkResponse(*params.logFile, root); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/exchanges/exmo.cpp: -------------------------------------------------------------------------------- 1 | #include "exmo.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "unique_json.hpp" 6 | #include "hex_str.hpp" 7 | #include "utils/hmac_sha512.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Exmo { 15 | // Forward declarations 16 | static json_t* authRequest(Parameters &, const char* URL_Request, std::string URL_Options = ""); 17 | static std::string getSignature(Parameters &, std::string); 18 | 19 | static RestApi& queryHandle(Parameters ¶ms) 20 | { 21 | static RestApi query ("https://api.exmo.com/v1", 22 | params.cacert.c_str(), *params.logFile); 23 | return query; 24 | } 25 | 26 | quote_t getQuote(Parameters ¶ms) 27 | { 28 | auto &exchange = queryHandle(params); 29 | auto root = unique_json(exchange.getRequest("/order_book/?pair=BTC_USD")); 30 | 31 | auto quote = json_string_value(json_object_get(json_object_get(root.get(), "BTC_USD"), "bid_top")); 32 | auto bidValue = quote ? std::stod(quote) : 0.0; 33 | 34 | quote = json_string_value(json_object_get(json_object_get(root.get(), "BTC_USD"), "ask_top")); 35 | auto askValue = quote ? std::stod(quote) : 0.0; 36 | 37 | return std::make_pair(bidValue, askValue); 38 | } 39 | 40 | 41 | double getAvail(Parameters& params, std::string currency) 42 | { 43 | double available = 0.0; 44 | transform(currency.begin(), currency.end(), currency.begin(), ::toupper); 45 | const char * curr_ = currency.c_str(); 46 | 47 | unique_json root { authRequest(params, "/user_info") }; 48 | const char * avail_str = json_string_value(json_object_get(json_object_get(root.get(), "balances"), curr_)); 49 | available = avail_str ? atof(avail_str) : 0.0; 50 | return available; 51 | } 52 | 53 | // TODO multi currency support 54 | //std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price, std::string pair) { 55 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) { 56 | using namespace std; 57 | string pair = "btc_usd"; // TODO remove when multi currency support 58 | *params.logFile << " Trying to send a " << pair << " " << direction << " limit order: " << quantity << "@" << price << endl; 59 | transform(pair.begin(), pair.end(), pair.begin(), ::toupper); 60 | 61 | string options; 62 | options = "pair=" + pair; 63 | options += "&quantity=" + to_string(quantity); 64 | options += "&price=" + to_string(price); 65 | options += "&type=" + direction; 66 | 67 | unique_json root { authRequest(params, "/order_create", options) }; 68 | string orderId = to_string(json_integer_value(json_object_get(root.get(), "order_id"))); 69 | if (orderId == "0") { 70 | auto dump = json_dumps(root.get(), 0); 71 | *params.logFile << " Failed, Message: " << dump << endl; 72 | free(dump); 73 | } 74 | else { 75 | *params.logFile << " Done, order ID: " << orderId << endl; 76 | } 77 | return orderId; 78 | } 79 | 80 | 81 | // TODO multi currency support 82 | //bool isOrderComplete(Parameters& params, std::string orderId, std::string pair) 83 | bool isOrderComplete(Parameters& params, std::string orderId) { 84 | using namespace std; 85 | string pair = "btc_usd"; // TODO remove when multi currency support 86 | transform(pair.begin(), pair.end(), pair.begin(), ::toupper); 87 | 88 | unique_json rootOrd { authRequest(params, "/user_open_orders") }; 89 | 90 | int orders = json_array_size(json_object_get(rootOrd.get(), pair.c_str())); 91 | string order_id; 92 | 93 | for (int i=0; i Failed, Server Return Message: " << dump << endl; 110 | free(dump); 111 | return false; 112 | } 113 | } 114 | 115 | 116 | double getActivePos(Parameters& params) { 117 | return getAvail(params, "btc"); 118 | } 119 | 120 | 121 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 122 | { 123 | auto &exchange = queryHandle(params); 124 | auto root = unique_json(exchange.getRequest("/order_book?pair=BTC_USD")); 125 | auto branch = json_object_get(json_object_get(root.get(), "BTC_USD"), isBid ? "bid" : "ask"); 126 | 127 | // loop on volume 128 | double totVol = 0.0; 129 | double currPrice = 0.0; 130 | double currVol = 0.0; 131 | unsigned int i = 0; 132 | // [[, ], [, ], ...] 133 | for(i = 0; i < (json_array_size(branch)); i++) 134 | { 135 | // volumes are added up until the requested volume is reached 136 | currVol = atof(json_string_value(json_array_get(json_array_get(branch, i), 1))); 137 | currPrice = atof(json_string_value(json_array_get(json_array_get(branch, i), 0))); 138 | totVol += currVol; 139 | if(totVol >= volume * params.orderBookFactor){ 140 | break; 141 | } 142 | } 143 | 144 | return currPrice; 145 | } 146 | 147 | 148 | json_t* authRequest(Parameters ¶ms, const char *request, std::string options) { 149 | using namespace std; 150 | static unsigned long nonce = time(nullptr); 151 | nonce++; 152 | 153 | string req = request; 154 | req += "?nonce=" + to_string(nonce); 155 | if (!options.empty()) 156 | req += "&" + options; 157 | 158 | // FIXME against the API definition exmo actualy seems don't using options in signature 159 | string opt = ""; 160 | 161 | array headers { 162 | "Content-Type:application/x-www-form-urlencoded", 163 | "Key:" + params.exmoApi, 164 | "Sign:" + getSignature(params, opt), 165 | }; 166 | 167 | // cURL request 168 | auto &exchange = queryHandle(params); 169 | auto ret = exchange.postRequest(req, 170 | make_slist(begin(headers), end(headers))); 171 | // debug 172 | //auto dump = json_dumps(ret, 0); 173 | //*params.logFile << " Debug, Server Return Message: " << dump << std::endl << std::endl; 174 | //free(dump); 175 | 176 | return ret; 177 | } 178 | 179 | std::string getSignature(Parameters& params, std::string msg) { 180 | 181 | HMAC_SHA512 hmac_sha512(params.exmoSecret.c_str(), msg); 182 | return hmac_sha512.hex_digest(); 183 | } 184 | 185 | 186 | void testExmo() { 187 | 188 | using namespace std; 189 | Parameters params("blackbird.conf"); 190 | //params.exmoSecret = ""; 191 | //params.exmoClientId = ""; 192 | //params.exmoApi = ""; 193 | params.logFile = new ofstream("./test.log" , ofstream::trunc); 194 | 195 | string orderId; 196 | 197 | cout << "Current value BTC_USD bid: " << getQuote(params).bid() << endl; 198 | cout << "Current value BTC_USD ask: " << getQuote(params).ask() << endl; 199 | cout << "Current balance BTC: " << getAvail(params, "btc") << endl; 200 | cout << "Current balance USD: " << getAvail(params, "usd") << endl; 201 | cout << "Current balance XMR: " << getAvail(params, "xmr")<< endl; 202 | cout << "Current balance EUR: " << getAvail(params, "eur")<< endl; 203 | cout << "Current bid limit price for 10 units: " << getLimitPrice(params, 10.0, true) << endl; 204 | cout << "Current ask limit price for 10 units: " << getLimitPrice(params, 10.0, false) << endl; 205 | 206 | //cout << "Sending buy order - TXID: " ; 207 | //orderId = sendLongOrder(params, "buy", 0.005, 1000); 208 | //cout << orderId << endl; 209 | //cout << "Buy order is complete: " << isOrderComplete(params, orderId) << endl; 210 | 211 | //cout << "Sending sell order - TXID: " ; 212 | //orderId = sendLongOrder(params, "sell", 0.5, 338); 213 | //if (orderId == "0") { 214 | // cout << "failed" << endl; 215 | //} 216 | //else { 217 | // cout << orderId << endl; 218 | cout << "Sell order is complete: " << isOrderComplete(params, "404591373") << endl; 219 | //} 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/exchanges/gemini.cpp: -------------------------------------------------------------------------------- 1 | #include "gemini.h" 2 | #include "parameters.h" 3 | #include "curl_fun.h" 4 | #include "utils/restapi.h" 5 | #include "utils/base64.h" 6 | #include "unique_json.hpp" 7 | 8 | #include "openssl/sha.h" 9 | #include "openssl/hmac.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Gemini { 18 | 19 | static RestApi& queryHandle(Parameters ¶ms) 20 | { 21 | static RestApi query ("https://api.gemini.com", 22 | params.cacert.c_str(), *params.logFile); 23 | return query; 24 | } 25 | 26 | quote_t getQuote(Parameters ¶ms) 27 | { 28 | auto &exchange = queryHandle(params); 29 | std::string url; 30 | url = "/v1/book/BTCUSD"; 31 | 32 | unique_json root { exchange.getRequest(url) }; 33 | const char *quote = json_string_value(json_object_get(json_array_get(json_object_get(root.get(), "bids"), 0), "price")); 34 | auto bidValue = quote ? std::stod(quote) : 0.0; 35 | 36 | quote = json_string_value(json_object_get(json_array_get(json_object_get(root.get(), "asks"), 0), "price")); 37 | auto askValue = quote ? std::stod(quote) : 0.0; 38 | 39 | return std::make_pair(bidValue, askValue); 40 | } 41 | 42 | double getAvail(Parameters& params, std::string currency) { 43 | unique_json root { authRequest(params, "https://api.gemini.com/v1/balances", "balances", "") }; 44 | while (json_object_get(root.get(), "message") != NULL) { 45 | std::this_thread::sleep_for(std::chrono::seconds(1)); 46 | auto dump = json_dumps(root.get(), 0); 47 | *params.logFile << " Error with JSON: " << dump << ". Retrying..." << std::endl; 48 | free(dump); 49 | root.reset(authRequest(params, "https://api.gemini.com/v1/balances", "balances", "")); 50 | } 51 | // go through the list 52 | size_t arraySize = json_array_size(root.get()); 53 | double availability = 0.0; 54 | const char* returnedText; 55 | std::string currencyAllCaps; 56 | if (currency.compare("btc") == 0) { 57 | currencyAllCaps = "BTC"; 58 | } else if (currency.compare("usd") == 0) { 59 | currencyAllCaps = "USD"; 60 | } 61 | for (size_t i = 0; i < arraySize; i++) { 62 | std::string tmpCurrency = json_string_value(json_object_get(json_array_get(root.get(), i), "currency")); 63 | if (tmpCurrency.compare(currencyAllCaps.c_str()) == 0) { 64 | returnedText = json_string_value(json_object_get(json_array_get(root.get(), i), "amount")); 65 | if (returnedText != NULL) { 66 | availability = atof(returnedText); 67 | } else { 68 | *params.logFile << " Error with the credentials." << std::endl; 69 | availability = 0.0; 70 | } 71 | } 72 | } 73 | 74 | return availability; 75 | } 76 | 77 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) { 78 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 79 | << std::setprecision(6) << quantity << "@$" 80 | << std::setprecision(2) << price << "...\n"; 81 | std::ostringstream oss; 82 | oss << "\"symbol\":\"BTCUSD\", \"amount\":\"" << quantity << "\", \"price\":\"" << price << "\", \"side\":\"" << direction << "\", \"type\":\"exchange limit\""; 83 | std::string options = oss.str(); 84 | unique_json root { authRequest(params, "https://api.gemini.com/v1/order/new", "order/new", options) }; 85 | std::string orderId = json_string_value(json_object_get(root.get(), "order_id")); 86 | *params.logFile << " Done (order ID: " << orderId << ")\n" << std::endl; 87 | return orderId; 88 | } 89 | 90 | bool isOrderComplete(Parameters& params, std::string orderId) { 91 | if (orderId == "0") return true; 92 | 93 | auto options = "\"order_id\":" + orderId; 94 | unique_json root { authRequest(params, "https://api.gemini.com/v1/order/status", "order/status", options) }; 95 | return json_is_false(json_object_get(root.get(), "is_live")); 96 | } 97 | 98 | double getActivePos(Parameters& params) { 99 | return getAvail(params, "btc"); 100 | } 101 | 102 | double getLimitPrice(Parameters& params, double volume, bool isBid) 103 | { 104 | auto &exchange = queryHandle(params); 105 | unique_json root { exchange.getRequest("/v1/book/btcusd") }; 106 | auto bidask = json_object_get(root.get(), isBid ? "bids" : "asks"); 107 | 108 | // loop on volume 109 | *params.logFile << " Looking for a limit price to fill " 110 | << std::setprecision(6) << fabs(volume) << " BTC...\n"; 111 | double tmpVol = 0.0; 112 | double p = 0.0; 113 | double v; 114 | int i = 0; 115 | while (tmpVol < fabs(volume) * params.orderBookFactor) 116 | { 117 | p = atof(json_string_value(json_object_get(json_array_get(bidask, i), "price"))); 118 | v = atof(json_string_value(json_object_get(json_array_get(bidask, i), "amount"))); 119 | *params.logFile << " order book: " 120 | << std::setprecision(6) << v << "@$" 121 | << std::setprecision(2) << p << std::endl; 122 | tmpVol += v; 123 | i++; 124 | } 125 | 126 | return p; 127 | } 128 | 129 | json_t* authRequest(Parameters& params, std::string url, std::string request, std::string options) { 130 | static uint64_t nonce = time(nullptr) * 4; 131 | ++nonce; 132 | // check if options parameter is empty 133 | std::ostringstream oss; 134 | if (options.empty()) { 135 | oss << "{\"request\":\"/v1/" << request << "\",\"nonce\":\"" << nonce << "\"}"; 136 | } else { 137 | oss << "{\"request\":\"/v1/" << request << "\",\"nonce\":\"" << nonce << "\", " << options << "}"; 138 | } 139 | std::string tmpPayload = base64_encode(reinterpret_cast(oss.str().c_str()), oss.str().length()); 140 | oss.clear(); 141 | oss.str(""); 142 | oss << "X-GEMINI-PAYLOAD:" << tmpPayload; 143 | std::string payload; 144 | payload = oss.str(); 145 | oss.clear(); 146 | oss.str(""); 147 | // build the signature 148 | uint8_t *digest = HMAC(EVP_sha384(), params.geminiSecret.c_str(), params.geminiSecret.size(), (uint8_t*)tmpPayload.c_str(), tmpPayload.size(), NULL, NULL); 149 | char mdString[SHA384_DIGEST_LENGTH+100]; // FIXME +100 150 | for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) { 151 | sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]); 152 | } 153 | oss.clear(); 154 | oss.str(""); 155 | oss << "X-GEMINI-SIGNATURE:" << mdString; 156 | struct curl_slist *headers = NULL; 157 | std::string api = "X-GEMINI-APIKEY:" + std::string(params.geminiApi); 158 | headers = curl_slist_append(headers, api.c_str()); 159 | headers = curl_slist_append(headers, payload.c_str()); 160 | headers = curl_slist_append(headers, oss.str().c_str()); 161 | CURLcode resCurl; 162 | if (params.curl) { 163 | std::string readBuffer; 164 | curl_easy_setopt(params.curl, CURLOPT_POST, 1L); 165 | curl_easy_setopt(params.curl, CURLOPT_HTTPHEADER, headers); 166 | curl_easy_setopt(params.curl, CURLOPT_POSTFIELDS, ""); 167 | curl_easy_setopt(params.curl, CURLOPT_SSL_VERIFYPEER, 0L); 168 | curl_easy_setopt(params.curl, CURLOPT_WRITEFUNCTION, WriteCallback); 169 | curl_easy_setopt(params.curl, CURLOPT_WRITEDATA, &readBuffer); 170 | curl_easy_setopt(params.curl, CURLOPT_URL, url.c_str()); 171 | curl_easy_setopt(params.curl, CURLOPT_CONNECTTIMEOUT, 10L); 172 | resCurl = curl_easy_perform(params.curl); 173 | json_t *root; 174 | json_error_t error; 175 | using std::this_thread::sleep_for; 176 | using secs = std::chrono::seconds; 177 | while (resCurl != CURLE_OK) { 178 | *params.logFile << " Error with cURL. Retry in 2 sec..." << std::endl; 179 | sleep_for(secs(2)); 180 | readBuffer = ""; 181 | resCurl = curl_easy_perform(params.curl); 182 | } 183 | root = json_loads(readBuffer.c_str(), 0, &error); 184 | while (!root) { 185 | *params.logFile << " Error with JSON:\n" << error.text << std::endl; 186 | *params.logFile << " Buffer:\n" << readBuffer.c_str() << std::endl; 187 | *params.logFile << " Retrying..." << std::endl; 188 | sleep_for(secs(2)); 189 | readBuffer = ""; 190 | resCurl = curl_easy_perform(params.curl); 191 | while (resCurl != CURLE_OK) { 192 | *params.logFile << " Error with cURL. Retry in 2 sec..." << std::endl; 193 | sleep_for(secs(2)); 194 | readBuffer = ""; 195 | resCurl = curl_easy_perform(params.curl); 196 | } 197 | root = json_loads(readBuffer.c_str(), 0, &error); 198 | } 199 | curl_slist_free_all(headers); 200 | curl_easy_reset(params.curl); 201 | return root; 202 | } else { 203 | *params.logFile << " Error with cURL init." << std::endl; 204 | return NULL; 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/check_entry_exit.cpp: -------------------------------------------------------------------------------- 1 | #include "check_entry_exit.h" 2 | #include "bitcoin.h" 3 | #include "result.h" 4 | #include "parameters.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | // Not sure to understand what's going on here ;-) 14 | template 15 | static typename std::iterator_traits::value_type compute_sd(T first, const T &last) { 16 | using namespace std; 17 | typedef typename iterator_traits::value_type value_type; 18 | 19 | auto n = distance(first, last); 20 | auto mu = accumulate(first, last, value_type()) / n; 21 | auto squareSum = inner_product(first, last, first, value_type()); 22 | return sqrt(squareSum / n - mu * mu); 23 | } 24 | 25 | // Returns a double as a string '##.##%' 26 | std::string percToStr(double perc) { 27 | std::ostringstream s; 28 | if (perc >= 0.0) s << " "; 29 | s << std::fixed << std::setprecision(2) << perc * 100.0 << "%"; 30 | return s.str(); 31 | } 32 | 33 | bool checkEntry(Bitcoin* btcLong, Bitcoin* btcShort, Result& res, Parameters& params) { 34 | 35 | if (!btcShort->getHasShort()) return false; 36 | 37 | // Gets the prices and computes the spread 38 | double priceLong = btcLong->getAsk(); 39 | double priceShort = btcShort->getBid(); 40 | // If the prices are null we return a null spread 41 | // to avoid false opportunities 42 | if (priceLong > 0.0 && priceShort > 0.0) { 43 | res.spreadIn = (priceShort - priceLong) / priceLong; 44 | } else { 45 | res.spreadIn = 0.0; 46 | } 47 | int longId = btcLong->getId(); 48 | int shortId = btcShort->getId(); 49 | 50 | // We update the max and min spread if necessary 51 | res.maxSpread[longId][shortId] = std::max(res.spreadIn, res.maxSpread[longId][shortId]); 52 | res.minSpread[longId][shortId] = std::min(res.spreadIn, res.minSpread[longId][shortId]); 53 | 54 | if (params.verbose) { 55 | params.logFile->precision(2); 56 | *params.logFile << " " << btcLong->getExchName() << "/" << btcShort->getExchName() << ":\t" << percToStr(res.spreadIn); 57 | *params.logFile << " [target " << percToStr(params.spreadEntry) << ", min " << percToStr(res.minSpread[longId][shortId]) << ", max " << percToStr(res.maxSpread[longId][shortId]) << "]"; 58 | // The short-term volatility is computed and 59 | // displayed. No other action with it for 60 | // the moment. 61 | if (params.useVolatility) { 62 | if (res.volatility[longId][shortId].size() >= params.volatilityPeriod) { 63 | auto stdev = compute_sd(begin(res.volatility[longId][shortId]), end(res.volatility[longId][shortId])); 64 | *params.logFile << " volat. " << stdev * 100.0 << "%"; 65 | } else { 66 | *params.logFile << " volat. n/a " << res.volatility[longId][shortId].size() << "<" << params.volatilityPeriod << " "; 67 | } 68 | } 69 | // Updates the trailing spread 70 | // TODO: explain what a trailing spread is. 71 | // See #12 on GitHub for the moment 72 | if (res.trailing[longId][shortId] != -1.0) { 73 | *params.logFile << " trailing " << percToStr(res.trailing[longId][shortId]) << " " << res.trailingWaitCount[longId][shortId] << "/" << params.trailingCount; 74 | } 75 | // If one of the exchanges (or both) hasn't been implemented, 76 | // we mention in the log file that this spread is for info only. 77 | if ((!btcLong->getIsImplemented() || !btcShort->getIsImplemented()) && !params.isDemoMode) 78 | *params.logFile << " info only"; 79 | 80 | *params.logFile << std::endl; 81 | } 82 | // We need both exchanges to be implemented, 83 | // otherwise we return False regardless of 84 | // the opportunity found. 85 | if (!btcLong->getIsImplemented() || 86 | !btcShort->getIsImplemented() || 87 | res.spreadIn == 0.0) 88 | return false; 89 | 90 | // the trailing spread is reset for this pair, 91 | // because once the spread is *below* 92 | // SpreadEndtry. Again, see #12 on GitHub for 93 | // more details. 94 | if (res.spreadIn < params.spreadEntry) { 95 | res.trailing[longId][shortId] = -1.0; 96 | res.trailingWaitCount[longId][shortId] = 0; 97 | return false; 98 | } 99 | 100 | // Updates the trailingSpread with the new value 101 | double newTrailValue = res.spreadIn - params.trailingLim; 102 | if (res.trailing[longId][shortId] == -1.0) { 103 | res.trailing[longId][shortId] = std::max(newTrailValue, params.spreadEntry); 104 | return false; 105 | } 106 | 107 | if (newTrailValue >= res.trailing[longId][shortId]) { 108 | res.trailing[longId][shortId] = newTrailValue; 109 | res.trailingWaitCount[longId][shortId] = 0; 110 | } 111 | if (res.spreadIn >= res.trailing[longId][shortId]) { 112 | res.trailingWaitCount[longId][shortId] = 0; 113 | return false; 114 | } 115 | 116 | if (res.trailingWaitCount[longId][shortId] < params.trailingCount) { 117 | res.trailingWaitCount[longId][shortId]++; 118 | return false; 119 | } 120 | 121 | // Updates the Result structure with the information about 122 | // the two trades and return True (meaning an opportunity 123 | // was found). 124 | res.idExchLong = longId; 125 | res.idExchShort = shortId; 126 | res.feesLong = btcLong->getFees(); 127 | res.feesShort = btcShort->getFees(); 128 | res.exchNameLong = btcLong->getExchName(); 129 | res.exchNameShort = btcShort->getExchName(); 130 | res.priceLongIn = priceLong; 131 | res.priceShortIn = priceShort; 132 | res.exitTarget = res.spreadIn - params.spreadTarget - 2.0*(res.feesLong + res.feesShort); 133 | res.trailingWaitCount[longId][shortId] = 0; 134 | return true; 135 | } 136 | 137 | bool checkExit(Bitcoin* btcLong, Bitcoin* btcShort, Result& res, Parameters& params, time_t period) { 138 | double priceLong = btcLong->getBid(); 139 | double priceShort = btcShort->getAsk(); 140 | if (priceLong > 0.0 && priceShort > 0.0) { 141 | res.spreadOut = (priceShort - priceLong) / priceLong; 142 | } else { 143 | res.spreadOut = 0.0; 144 | } 145 | int longId = btcLong->getId(); 146 | int shortId = btcShort->getId(); 147 | 148 | res.maxSpread[longId][shortId] = std::max(res.spreadOut, res.maxSpread[longId][shortId]); 149 | res.minSpread[longId][shortId] = std::min(res.spreadOut, res.minSpread[longId][shortId]); 150 | 151 | if (params.verbose) { 152 | params.logFile->precision(2); 153 | *params.logFile << " " << btcLong->getExchName() << "/" << btcShort->getExchName() << ":\t" << percToStr(res.spreadOut); 154 | *params.logFile << " [target " << percToStr(res.exitTarget) << ", min " << percToStr(res.minSpread[longId][shortId]) << ", max " << percToStr(res.maxSpread[longId][shortId]) << "]"; 155 | // The short-term volatility is computed and 156 | // displayed. No other action with it for 157 | // the moment. 158 | if (params.useVolatility) { 159 | if (res.volatility[longId][shortId].size() >= params.volatilityPeriod) { 160 | auto stdev = compute_sd(begin(res.volatility[longId][shortId]), end(res.volatility[longId][shortId])); 161 | *params.logFile << " volat. " << stdev * 100.0 << "%"; 162 | } else { 163 | *params.logFile << " volat. n/a " << res.volatility[longId][shortId].size() << "<" << params.volatilityPeriod << " "; 164 | } 165 | } 166 | if (res.trailing[longId][shortId] != 1.0) { 167 | *params.logFile << " trailing " << percToStr(res.trailing[longId][shortId]) << " " << res.trailingWaitCount[longId][shortId] << "/" << params.trailingCount; 168 | } 169 | } 170 | *params.logFile << std::endl; 171 | if (period - res.entryTime >= int(params.maxLength)) { 172 | res.priceLongOut = priceLong; 173 | res.priceShortOut = priceShort; 174 | return true; 175 | } 176 | if (res.spreadOut == 0.0) return false; 177 | if (res.spreadOut > res.exitTarget) { 178 | res.trailing[longId][shortId] = 1.0; 179 | res.trailingWaitCount[longId][shortId] = 0; 180 | return false; 181 | } 182 | 183 | double newTrailValue = res.spreadOut + params.trailingLim; 184 | if (res.trailing[longId][shortId] == 1.0) { 185 | res.trailing[longId][shortId] = std::min(newTrailValue, res.exitTarget); 186 | return false; 187 | } 188 | if (newTrailValue <= res.trailing[longId][shortId]) { 189 | res.trailing[longId][shortId] = newTrailValue; 190 | res.trailingWaitCount[longId][shortId] = 0; 191 | } 192 | if (res.spreadOut <= res.trailing[longId][shortId]) { 193 | res.trailingWaitCount[longId][shortId] = 0; 194 | return false; 195 | } 196 | if (res.trailingWaitCount[longId][shortId] < params.trailingCount) { 197 | res.trailingWaitCount[longId][shortId]++; 198 | return false; 199 | } 200 | 201 | res.priceLongOut = priceLong; 202 | res.priceShortOut = priceShort; 203 | res.trailingWaitCount[longId][shortId] = 0; 204 | return true; 205 | } 206 | -------------------------------------------------------------------------------- /src/parameters.cpp: -------------------------------------------------------------------------------- 1 | #include "parameters.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | static std::string findConfigFile(std::string fileName) { 9 | // local directory 10 | { 11 | std::ifstream configFile(fileName); 12 | 13 | // Keep the first match 14 | if (configFile.good()) { 15 | return fileName; 16 | } 17 | } 18 | 19 | // Unix user settings directory 20 | { 21 | char *home = getenv("HOME"); 22 | 23 | if (home) { 24 | std::string prefix = std::string(home) + "/.config"; 25 | std::string fullpath = prefix + "/" + fileName; 26 | std::ifstream configFile(fullpath); 27 | 28 | // Keep the first match 29 | if (configFile.good()) { 30 | return fullpath; 31 | } 32 | } 33 | } 34 | 35 | // Windows user settings directory 36 | { 37 | char *appdata = getenv("APPDATA"); 38 | 39 | if (appdata) { 40 | std::string prefix = std::string(appdata); 41 | std::string fullpath = prefix + "/" + fileName; 42 | std::ifstream configFile(fullpath); 43 | 44 | // Keep the first match 45 | if (configFile.good()) { 46 | return fullpath; 47 | } 48 | } 49 | } 50 | 51 | // Unix system settings directory 52 | { 53 | std::string fullpath = "/etc/" + fileName; 54 | std::ifstream configFile(fullpath); 55 | 56 | // Keep the first match 57 | if (configFile.good()) { 58 | return fullpath; 59 | } 60 | } 61 | 62 | // We have to return something, even though we already know this will 63 | // fail 64 | return fileName; 65 | } 66 | 67 | Parameters::Parameters(std::string fileName) { 68 | std::ifstream configFile(findConfigFile(fileName)); 69 | if (!configFile.is_open()) { 70 | std::cout << "ERROR: " << fileName << " cannot be open.\n"; 71 | exit(EXIT_FAILURE); 72 | } 73 | 74 | spreadEntry = getDouble(getParameter("SpreadEntry", configFile)); 75 | spreadTarget = getDouble(getParameter("SpreadTarget", configFile)); 76 | maxLength = getUnsigned(getParameter("MaxLength", configFile)); 77 | priceDeltaLim = getDouble(getParameter("PriceDeltaLimit", configFile)); 78 | trailingLim = getDouble(getParameter("TrailingSpreadLim", configFile)); 79 | trailingCount = getUnsigned(getParameter("TrailingSpreadCount", configFile)); 80 | orderBookFactor = getDouble(getParameter("OrderBookFactor", configFile)); 81 | isDemoMode = getBool(getParameter("DemoMode", configFile)); 82 | leg1 = getParameter("Leg1", configFile); 83 | leg2 = getParameter("Leg2", configFile); 84 | verbose = getBool(getParameter("Verbose", configFile)); 85 | interval = getUnsigned(getParameter("Interval", configFile)); 86 | debugMaxIteration = getUnsigned(getParameter("DebugMaxIteration", configFile)); 87 | useFullExposure = getBool(getParameter("UseFullExposure", configFile)); 88 | testedExposure = getDouble(getParameter("TestedExposure", configFile)); 89 | maxExposure = getDouble(getParameter("MaxExposure", configFile)); 90 | useVolatility = getBool(getParameter("UseVolatility", configFile)); 91 | volatilityPeriod = getUnsigned(getParameter("VolatilityPeriod", configFile)); 92 | cacert = getParameter("CACert", configFile); 93 | 94 | bitfinexApi = getParameter("BitfinexApiKey", configFile); 95 | bitfinexSecret = getParameter("BitfinexSecretKey", configFile); 96 | bitfinexFees = getDouble(getParameter("BitfinexFees", configFile)); 97 | bitfinexEnable = getBool(getParameter("BitfinexEnable", configFile)); 98 | okcoinApi = getParameter("OkCoinApiKey", configFile); 99 | okcoinSecret = getParameter("OkCoinSecretKey", configFile); 100 | okcoinFees = getDouble(getParameter("OkCoinFees", configFile)); 101 | okcoinEnable = getBool(getParameter("OkCoinEnable", configFile)); 102 | bitstampClientId = getParameter("BitstampClientId", configFile); 103 | bitstampApi = getParameter("BitstampApiKey", configFile); 104 | bitstampSecret = getParameter("BitstampSecretKey", configFile); 105 | bitstampFees = getDouble(getParameter("BitstampFees", configFile)); 106 | bitstampEnable = getBool(getParameter("BitstampEnable", configFile)); 107 | geminiApi = getParameter("GeminiApiKey", configFile); 108 | geminiSecret = getParameter("GeminiSecretKey", configFile); 109 | geminiFees = getDouble(getParameter("GeminiFees", configFile)); 110 | geminiEnable = getBool(getParameter("GeminiEnable", configFile)); 111 | krakenApi = getParameter("KrakenApiKey", configFile); 112 | krakenSecret = getParameter("KrakenSecretKey", configFile); 113 | krakenFees = getDouble(getParameter("KrakenFees", configFile)); 114 | krakenEnable = getBool(getParameter("KrakenEnable", configFile)); 115 | itbitApi = getParameter("ItBitApiKey", configFile); 116 | itbitSecret = getParameter("ItBitSecretKey", configFile); 117 | itbitFees = getDouble(getParameter("ItBitFees", configFile)); 118 | itbitEnable = getBool(getParameter("ItBitEnable", configFile)); 119 | wexApi = getParameter("WEXApiKey", configFile); 120 | wexSecret = getParameter("WEXSecretKey", configFile); 121 | wexFees = getDouble(getParameter("WEXFees", configFile)); 122 | wexEnable = getBool(getParameter("WEXEnable", configFile)); 123 | poloniexApi = getParameter("PoloniexApiKey", configFile); 124 | poloniexSecret = getParameter("PoloniexSecretKey", configFile); 125 | poloniexFees = getDouble(getParameter("PoloniexFees", configFile)); 126 | poloniexEnable = getBool(getParameter("PoloniexEnable", configFile)); 127 | gdaxApi = getParameter("GDAXApiKey", configFile); 128 | gdaxSecret = getParameter("GDAXSecretKey", configFile); 129 | gdaxPhrase = getParameter("GDAXPhrase", configFile); 130 | gdaxFees = getDouble(getParameter("GDAXFees", configFile)); 131 | gdaxEnable = getBool(getParameter("GDAXEnable", configFile)); 132 | quadrigaApi = getParameter("QuadrigaApiKey", configFile); 133 | quadrigaSecret = getParameter("QuadrigaSecretKey", configFile); 134 | quadrigaFees = getDouble(getParameter("QuadrigaFees", configFile)); 135 | quadrigaClientId = getParameter("QuadrigaClientId", configFile); 136 | quadrigaEnable = getBool(getParameter("QuadrigaEnable", configFile)); 137 | exmoApi = getParameter("ExmoApiKey", configFile); 138 | exmoSecret = getParameter("ExmoSecretKey", configFile); 139 | exmoFees = getDouble(getParameter("ExmoFees", configFile)); 140 | exmoEnable = getBool(getParameter("ExmoEnable", configFile)); 141 | cexioClientId = getParameter("CexioClientId", configFile); 142 | cexioApi = getParameter("CexioApiKey", configFile); 143 | cexioSecret = getParameter("CexioSecretKey", configFile); 144 | cexioFees = getDouble(getParameter("CexioFees", configFile)); 145 | cexioEnable = getBool(getParameter("CexioEnable", configFile)); 146 | bittrexApi = getParameter("BittrexApiKey", configFile); 147 | bittrexSecret = getParameter("BittrexSecretKey", configFile); 148 | bittrexFees = getDouble(getParameter("BittrexFees", configFile)); 149 | bittrexEnable = getBool(getParameter("BittrexEnable", configFile)); 150 | binanceApi = getParameter("BinanceApiKey", configFile); 151 | binanceSecret = getParameter("BinanceSecretKey", configFile); 152 | binanceFees = getDouble(getParameter("BinanceFees", configFile)); 153 | binanceEnable = getBool(getParameter("BinanceEnable", configFile)); 154 | 155 | sendEmail = getBool(getParameter("SendEmail", configFile)); 156 | senderAddress = getParameter("SenderAddress", configFile); 157 | senderUsername = getParameter("SenderUsername", configFile); 158 | senderPassword = getParameter("SenderPassword", configFile); 159 | smtpServerAddress = getParameter("SmtpServerAddress", configFile); 160 | receiverAddress = getParameter("ReceiverAddress", configFile); 161 | 162 | dbFile = getParameter("DBFile", configFile); 163 | } 164 | 165 | void Parameters::addExchange(std::string n, double f, bool h, bool m) { 166 | exchName.push_back(n); 167 | fees.push_back(f); 168 | canShort.push_back(h); 169 | isImplemented.push_back(m); 170 | } 171 | 172 | int Parameters::nbExch() const { 173 | return exchName.size(); 174 | } 175 | 176 | std::string getParameter(std::string parameter, std::ifstream& configFile) { 177 | assert (configFile); 178 | std::string line; 179 | configFile.clear(); 180 | configFile.seekg(0); 181 | 182 | while (getline(configFile, line)) { 183 | if (line.length() > 0 && line.at(0) != '#') { 184 | std::string key = line.substr(0, line.find('=')); 185 | std::string value = line.substr(line.find('=') + 1, line.length()); 186 | if (key == parameter) { 187 | return value; 188 | } 189 | } 190 | } 191 | std::cout << "ERROR: parameter '" << parameter << "' not found. Your configuration file might be too old.\n" << std::endl; 192 | exit(EXIT_FAILURE); 193 | } 194 | 195 | bool getBool(std::string value) { 196 | return value == "true"; 197 | } 198 | 199 | double getDouble(std::string value) { 200 | return atof(value.c_str()); 201 | } 202 | 203 | unsigned getUnsigned(std::string value) { 204 | return atoi(value.c_str()); 205 | } 206 | -------------------------------------------------------------------------------- /src/exchanges/gdax.cpp: -------------------------------------------------------------------------------- 1 | #include "gdax.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "unique_json.hpp" 5 | #include "utils/base64.h" 6 | #include "openssl/sha.h" 7 | #include "openssl/hmac.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace GDAX 15 | { 16 | 17 | static RestApi &queryHandle(Parameters ¶ms) 18 | { 19 | static RestApi query("https://api.gdax.com", 20 | params.cacert.c_str(), *params.logFile); 21 | return query; 22 | } 23 | 24 | quote_t getQuote(Parameters ¶ms) 25 | { 26 | auto &exchange = queryHandle(params); 27 | std::string pair; 28 | pair = "/products/"; 29 | pair += params.leg1.c_str(); 30 | pair += "-"; 31 | pair += params.leg2.c_str(); 32 | pair += "/ticker"; 33 | unique_json root{exchange.getRequest(pair)}; 34 | const char *bid, *ask; 35 | int unpack_fail = json_unpack(root.get(), "{s:s, s:s}", "bid", &bid, "ask", &ask); 36 | if (unpack_fail) 37 | { 38 | bid = "0"; 39 | ask = "0"; 40 | } 41 | 42 | return std::make_pair(std::stod(bid), std::stod(ask)); 43 | } 44 | 45 | double getAvail(Parameters ¶ms, std::string currency) 46 | { 47 | unique_json root{authRequest(params, "GET", "/accounts", "")}; 48 | size_t arraySize = json_array_size(root.get()); 49 | double available = 0.0; 50 | const char *currstr; 51 | for (size_t i = 0; i < arraySize; i++) 52 | { 53 | std::string tmpCurrency = json_string_value(json_object_get(json_array_get(root.get(), i), "currency")); 54 | if (tmpCurrency.compare(currency.c_str()) == 0) 55 | { 56 | currstr = json_string_value(json_object_get(json_array_get(root.get(), i), "available")); 57 | if (currstr != NULL) 58 | { 59 | available = atof(currstr); 60 | } 61 | else 62 | { 63 | *params.logFile << " Error with currency string" << std::endl; 64 | available = 0.0; 65 | } 66 | } 67 | } 68 | return available; 69 | } 70 | 71 | double getActivePos(Parameters ¶ms) 72 | { 73 | // TODO: this is not really a good way to get active positions 74 | return getAvail(params, "BTC"); 75 | } 76 | 77 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 78 | { 79 | auto &exchange = queryHandle(params); 80 | // TODO: Build a real URL with leg1 leg2 and auth post it 81 | // FIXME: using level 2 order book - has aggregated data but should be sufficient for now. 82 | unique_json root{exchange.getRequest("/products/BTC-USD/book?level=2")}; 83 | auto bidask = json_object_get(root.get(), isBid ? "bids" : "asks"); 84 | *params.logFile << " Looking for a limit price to fill " 85 | << std::setprecision(8) << fabs(volume) << " Legx...\n"; 86 | double tmpVol = 0.0; 87 | double p = 0.0; 88 | double v; 89 | int i = 0; 90 | while (tmpVol < fabs(volume) * params.orderBookFactor) 91 | { 92 | p = atof(json_string_value(json_array_get(json_array_get(bidask, i), 0))); 93 | v = atof(json_string_value(json_array_get(json_array_get(bidask, i), 1))); 94 | *params.logFile << " order book: " 95 | << std::setprecision(8) << v << " @$" 96 | << std::setprecision(8) << p << std::endl; 97 | tmpVol += v; 98 | i++; 99 | } 100 | return p; 101 | } 102 | 103 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price) 104 | { 105 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) 106 | { 107 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 108 | return "0"; 109 | } 110 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 111 | << std::setprecision(8) << quantity << " @ $" 112 | << std::setprecision(8) << price << "...\n"; 113 | std::string pair = "BTC-USD"; 114 | std::string type = direction; 115 | char buff[300]; 116 | snprintf(buff, 300, "{\"size\":\"%.8f\",\"price\":\"%.8f\",\"side\":\"%s\",\"product_id\": \"%s\"}", quantity, price, type.c_str(), pair.c_str()); 117 | unique_json root{authRequest(params, "POST", "/orders", buff)}; 118 | auto txid = json_string_value(json_object_get(root.get(), "id")); 119 | 120 | *params.logFile << " Done (transaction ID: " << txid << ")\n" 121 | << std::endl; 122 | return txid; 123 | } 124 | 125 | bool isOrderComplete(Parameters ¶ms, std::string orderId) 126 | { 127 | 128 | unique_json root{authRequest(params, "GET", "/orders", "")}; 129 | size_t arraySize = json_array_size(root.get()); 130 | bool complete = true; 131 | const char *idstr; 132 | for (size_t i = 0; i < arraySize; i++) 133 | { 134 | std::string tmpId = json_string_value(json_object_get(json_array_get(root.get(), i), "id")); 135 | if (tmpId.compare(orderId.c_str()) == 0) 136 | { 137 | idstr = json_string_value(json_object_get(json_array_get(root.get(), i), "status")); 138 | *params.logFile << " Order still open (Status:" << idstr << ")" << std::endl; 139 | complete = false; 140 | } 141 | } 142 | return complete; 143 | } 144 | 145 | json_t *authRequest(Parameters ¶ms, std::string method, std::string request, const std::string &options) 146 | { 147 | // create timestamp 148 | 149 | //static uint64_t nonce = time(nullptr); 150 | std::string nonce = gettime(); 151 | // create data string 152 | 153 | std::string post_data = nonce + method + request + options; 154 | 155 | //if (!options.empty()) 156 | // post_data += options; 157 | 158 | // create decoded key 159 | 160 | std::string decoded_key = base64_decode(params.gdaxSecret); 161 | 162 | // Message Signature using HMAC-SHA256 of (NONCE+ METHOD??? + PATH + body) 163 | 164 | uint8_t *hmac_digest = HMAC(EVP_sha256(), 165 | decoded_key.c_str(), decoded_key.length(), 166 | reinterpret_cast(post_data.data()), post_data.size(), NULL, NULL); 167 | 168 | // encode the HMAC to base64 169 | std::string api_sign_header = base64_encode(hmac_digest, SHA256_DIGEST_LENGTH); 170 | 171 | // cURL header 172 | 173 | std::array headers{ 174 | "CB-ACCESS-KEY:" + params.gdaxApi, 175 | "CB-ACCESS-SIGN:" + api_sign_header, 176 | "CB-ACCESS-TIMESTAMP:" + nonce, 177 | "CB-ACCESS-PASSPHRASE:" + params.gdaxPhrase, 178 | "Content-Type: application/json; charset=utf-8", 179 | }; 180 | 181 | // cURL request 182 | auto &exchange = queryHandle(params); 183 | 184 | //TODO: this sucks please do something better 185 | if (method.compare("GET") == 0) 186 | { 187 | return exchange.getRequest(request, make_slist(std::begin(headers), std::end(headers))); 188 | } 189 | else if (method.compare("POST") == 0) 190 | { 191 | return exchange.postRequest(request, make_slist(std::begin(headers), std::end(headers)), options); 192 | } 193 | else 194 | { 195 | std::cout << "Error With Auth method. Exiting with code 0" << std::endl; 196 | exit(0); 197 | } 198 | } 199 | std::string gettime() 200 | { 201 | 202 | timeval curTime; 203 | gettimeofday(&curTime, NULL); 204 | int milli = curTime.tv_usec / 1000; 205 | char buffer[80]; 206 | strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S", gmtime(&curTime.tv_sec)); 207 | char time_buffer2[200]; 208 | snprintf(time_buffer2, 200, "%s.%d000Z", buffer, milli); 209 | snprintf(time_buffer2, 200, "%sZ", buffer); 210 | 211 | //return time_buffer2; 212 | 213 | time_t result = time(NULL); 214 | long long result2 = result; 215 | char buff4[40]; 216 | snprintf(buff4, 40, "%lld", result2); 217 | 218 | char buff5[40]; 219 | snprintf(buff5, 40, "%lld.%d000", result2, milli); 220 | return buff5; 221 | } 222 | void testGDAX() 223 | { 224 | 225 | Parameters params("bird.conf"); 226 | params.logFile = new std::ofstream("./test.log", std::ofstream::trunc); 227 | 228 | std::string orderId; 229 | 230 | //std::cout << "Current value LEG1_LEG2 bid: " << getQuote(params).bid() << std::endl; 231 | //std::cout << "Current value LEG1_LEG2 ask: " << getQuote(params).ask() << std::endl; 232 | //std::cout << "Current balance BTC: " << getAvail(params, "BTC") << std::endl; 233 | //std::cout << "Current balance USD: " << getAvail(params, "USD")<< std::endl; 234 | //std::cout << "Current balance ETH: " << getAvail(params, "ETH")<< std::endl; 235 | //std::cout << "Current balance BTC: " << getAvail(params, "BCH")<< std::endl; 236 | //std::cout << "current bid limit price for 10 units: " << getLimitPrice(params, 10 , true) << std::endl; 237 | //std::cout << "Current ask limit price for .09 units: " << getLimitPrice(params, 0.09, false) << std::endl; 238 | //std::cout << "Sending buy order for 0.005 BTC @ ASK! USD - TXID: " << std::endl; 239 | //orderId = sendLongOrder(params, "buy", 0.005, getLimitPrice(params,.005,false)); 240 | //std::cout << orderId << std::endl; 241 | //std::cout << "Buy Order is complete: " << isOrderComplete(params, orderId) << std::endl; 242 | 243 | //std::cout << "Sending sell order for 0.02 BTC @ 10000 USD - TXID: " << std::endl; 244 | //orderId = sendLongOrder(params, "sell", 0.02, 10000); 245 | //std::cout << orderId << std::endl; 246 | //std::cout << "Sell order is complete: " << isOrderComplete(params, orderId) << std::endl; 247 | //std::cout << "Active Position: " << getActivePos(params); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/exchanges/quadrigacx.cpp: -------------------------------------------------------------------------------- 1 | #include "quadrigacx.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "unique_json.hpp" 6 | #include "hex_str.hpp" 7 | 8 | #include "openssl/sha.h" 9 | #include "openssl/hmac.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace QuadrigaCX { 16 | 17 | //forward declarations 18 | static std::string getSignature(Parameters& params, const uint64_t nonce); 19 | static json_t* authRequest(Parameters& params, std::string request, json_t * options = nullptr); 20 | 21 | static RestApi& queryHandle(Parameters ¶ms) 22 | { 23 | static RestApi query ("https://api.quadrigacx.com", 24 | params.cacert.c_str(), *params.logFile); 25 | return query; 26 | } 27 | 28 | quote_t getQuote(Parameters ¶ms) 29 | { 30 | auto &exchange = queryHandle(params); 31 | auto root = unique_json(exchange.getRequest("/v2/ticker?book=btc_usd")); 32 | 33 | auto quote = json_string_value(json_object_get(root.get(), "bid")); 34 | auto bidValue = quote ? std::stod(quote) : 0.0; 35 | 36 | quote = json_string_value(json_object_get(root.get(), "ask")); 37 | auto askValue = quote ? std::stod(quote) : 0.0; 38 | 39 | return std::make_pair(bidValue, askValue); 40 | } 41 | 42 | 43 | double getAvail(Parameters& params, std::string currency) 44 | { 45 | unique_json root { authRequest(params, "/v2/balance") }; 46 | 47 | double available = 0.0; 48 | const char * key = nullptr; 49 | if (currency.compare("usd") == 0) { 50 | key = "usd_available"; 51 | } else if (currency.compare("btc") == 0) { 52 | key = "btc_available"; 53 | } else if (currency.compare("eth") == 0) { 54 | key = "eth_available"; 55 | } else if (currency.compare("cad") == 0) { 56 | key = "cad_available"; 57 | } else { 58 | *params.logFile << " Currency " << currency << " not supported" << std::endl; 59 | } 60 | const char * avail_str = json_string_value(json_object_get(root.get(), key)); 61 | available = avail_str ? atof(avail_str) : 0.0; 62 | return available; 63 | } 64 | 65 | 66 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) 67 | { 68 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) { 69 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 70 | return "0"; 71 | } 72 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 73 | << std::setprecision(8) << quantity << "@$" 74 | << std::setprecision(2) << price << "...\n"; 75 | 76 | std::ostringstream oss; 77 | // Quadriga don't accept amount longer that 8 digits after decimal point 78 | // Its a workaround, would be better to trim only digits after decimal point. 79 | oss << std::fixed << std::setprecision(8) << quantity; 80 | std::string amount = oss.str(); 81 | 82 | unique_json options {json_object()}; 83 | json_object_set_new(options.get(), "book", json_string("btc_usd")); 84 | json_object_set_new(options.get(), "amount", json_string(amount.c_str())); 85 | json_object_set_new(options.get(), "price", json_real(price)); 86 | 87 | unique_json root { authRequest(params, ("/v2/" + direction), options.get()) }; 88 | std::string orderId = json_string_value(json_object_get(root.get(), "id")); 89 | if (orderId.empty()) { 90 | auto dump = json_dumps(root.get(), 0); 91 | *params.logFile << " Failed, Message: " << dump << std::endl; 92 | free(dump); 93 | return "0"; 94 | } 95 | else { 96 | *params.logFile << " Done, order ID: " << orderId << std::endl; 97 | return orderId; 98 | } 99 | } 100 | 101 | 102 | bool isOrderComplete(Parameters& params, std::string orderId) 103 | { 104 | auto ret = false; 105 | unique_json options {json_object()}; 106 | json_object_set_new(options.get(), "id", json_string(orderId.c_str())); 107 | 108 | unique_json root { authRequest(params, "/v2/lookup_order", options.get()) }; 109 | 110 | auto res = json_object_get(json_array_get(root.get(),0), "status"); 111 | if( json_is_string(res) ){ 112 | auto value = std::string(json_string_value(res)); 113 | if(value.compare("2") == 0){ 114 | ret = true; 115 | } 116 | } 117 | else{ 118 | auto dump = json_dumps(root.get(), 0); 119 | *params.logFile << " Error: failed to get order status for id: " << orderId 120 | << ":" << dump << std::endl; 121 | free(dump); 122 | } 123 | 124 | return ret; 125 | 126 | } 127 | 128 | double getActivePos(Parameters& params) { 129 | return getAvail(params, "btc"); 130 | } 131 | 132 | 133 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 134 | { 135 | auto &exchange = queryHandle(params); 136 | auto root = unique_json(exchange.getRequest("/v2/order_book?book=btc_usd")); 137 | auto branch = json_object_get(root.get(), isBid ? "bids" : "asks"); 138 | 139 | // loop on volume 140 | double totVol = 0.0; 141 | double currPrice = 0.0; 142 | double currVol = 0.0; 143 | unsigned int i = 0; 144 | // [[, ], [, ], ...] 145 | for(i = 0; i < (json_array_size(branch)); i++) 146 | { 147 | // volumes are added up until the requested volume is reached 148 | currVol = atof(json_string_value(json_array_get(json_array_get(branch, i), 1))); 149 | currPrice = atof(json_string_value(json_array_get(json_array_get(branch, i), 0))); 150 | totVol += currVol; 151 | if(totVol >= volume * params.orderBookFactor){ 152 | break; 153 | } 154 | } 155 | 156 | return currPrice; 157 | } 158 | 159 | 160 | static json_t* authRequest(Parameters& params, std::string request, json_t * options) 161 | { 162 | json_int_t nonce = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 163 | 164 | //post data is json 165 | unique_json payload {json_object()}; 166 | auto root = payload.get(); 167 | json_object_set_new(root, "key", json_string(params.quadrigaApi.c_str())); 168 | json_object_set_new(root, "nonce", json_integer(nonce)); 169 | json_object_set_new(root, "signature", json_string(getSignature(params, nonce).c_str())); 170 | 171 | if(options != nullptr){ 172 | const char * key; 173 | json_t * value; 174 | 175 | json_object_foreach(options, key, value){ 176 | if(value != nullptr && key != nullptr) 177 | json_object_set_new(root, key, value); 178 | } 179 | } 180 | 181 | auto payload_string = json_dumps(root, 0); 182 | std::string post_data(payload_string); 183 | free(payload_string); 184 | 185 | std::string headers[1] = { 186 | "Content-Type: application/json; charset=utf-8", 187 | }; 188 | 189 | // cURL request 190 | auto &exchange = queryHandle(params); 191 | auto ret = exchange.postRequest(request, 192 | make_slist(std::begin(headers), std::end(headers)), 193 | post_data); 194 | return ret; 195 | } 196 | 197 | static std::string getSignature(Parameters& params, const uint64_t nonce) 198 | { 199 | std::string sig_data_str = std::to_string(nonce) + params.quadrigaClientId + params.quadrigaApi; 200 | auto data_len = sig_data_str.length(); 201 | std::vector sig_data(data_len); 202 | 203 | copy(sig_data_str.begin(), sig_data_str.end(), sig_data.begin()); 204 | 205 | uint8_t * hmac_digest = HMAC(EVP_sha256(), 206 | params.quadrigaSecret.c_str(), 207 | params.quadrigaSecret.length(), 208 | sig_data.data(), 209 | data_len, 210 | NULL, NULL); 211 | 212 | return hex_str(hmac_digest, hmac_digest + SHA256_DIGEST_LENGTH); 213 | } 214 | 215 | void testQuadriga(){ 216 | 217 | Parameters params("blackbird.conf"); 218 | params.quadrigaSecret = ""; 219 | params.quadrigaClientId = ""; 220 | params.quadrigaApi = ""; 221 | params.logFile = new std::ofstream("./test.log" , std::ofstream::trunc); 222 | 223 | std::string orderId; 224 | 225 | std::cout << "Current value BTC_USD bid: " << getQuote(params).bid() << std::endl; 226 | std::cout << "Current value BTC_USD ask: " << getQuote(params).ask() << std::endl; 227 | std::cout << "Current balance BTC: " << getAvail(params, "btc") << std::endl; 228 | std::cout << "Current balance USD: " << getAvail(params, "usd")<< std::endl; 229 | std::cout << "Current balance ETH: " << getAvail(params, "eth")<< std::endl; 230 | std::cout << "Current balance CAD: " << getAvail(params, "cad")<< std::endl; 231 | std::cout << "current bid limit price for 10 units: " << getLimitPrice(params, 10.0, true) << std::endl; 232 | std::cout << "Current ask limit price for 10 units: " << getLimitPrice(params, 10.0, false) << std::endl; 233 | 234 | std::cout << "Sending buy order for 0.005 BTC @ 1000 USD - TXID: " ; 235 | orderId = sendLongOrder(params, "buy", 0.005, 1000); 236 | std:: cout << orderId << std::endl; 237 | std::cout << "Buy order is complete: " << isOrderComplete(params, orderId) << std::endl; 238 | 239 | std::cout << "Sending sell order for 0.005 BTC @ 5000 USD - TXID: " ; 240 | orderId = sendLongOrder(params, "sell", 0.005, 5000); 241 | std:: cout << orderId << std::endl; 242 | std::cout << "Sell order is complete: " << isOrderComplete(params, orderId) << std::endl; 243 | 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /src/exchanges/binance.cpp: -------------------------------------------------------------------------------- 1 | #include "binance.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "unique_json.hpp" 5 | #include "hex_str.hpp" 6 | #include "time_fun.h" 7 | #include "openssl/sha.h" 8 | #include "openssl/hmac.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Binance 17 | { 18 | 19 | static json_t *authRequest(Parameters &, std::string, std::string, std::string); 20 | 21 | static std::string getSignature(Parameters ¶ms, std::string payload); 22 | 23 | static RestApi &queryHandle(Parameters ¶ms) 24 | { 25 | static RestApi query("https://api.binance.com", 26 | params.cacert.c_str(), *params.logFile); 27 | return query; 28 | } 29 | 30 | quote_t getQuote(Parameters ¶ms) 31 | { 32 | auto &exchange = queryHandle(params); 33 | std::string x; 34 | //TODO: build real currency string 35 | x += "/api/v3/ticker/bookTicker?symbol="; 36 | x += "BTCUSDT"; 37 | //params.leg2.c_str(); 38 | unique_json root{exchange.getRequest(x)}; 39 | double quote = atof(json_string_value(json_object_get(root.get(), "bidPrice"))); 40 | auto bidValue = quote ? quote : 0.0; 41 | quote = atof(json_string_value(json_object_get(root.get(), "askPrice"))); 42 | auto askValue = quote ? quote : 0.0; 43 | 44 | return std::make_pair(bidValue, askValue); 45 | } 46 | 47 | double getAvail(Parameters ¶ms, std::string currency) 48 | { 49 | std::string cur_str; 50 | //cur_str += "symbol=BTCUSDT"; 51 | if (currency.compare("USD") == 0) 52 | { 53 | cur_str += "USDT"; 54 | } 55 | else 56 | { 57 | cur_str += currency.c_str(); 58 | } 59 | 60 | unique_json root{authRequest(params, "GET", "/api/v3/account", "")}; 61 | size_t arraySize = json_array_size(json_object_get(root.get(), "balances")); 62 | double available = 0.0; 63 | const char *currstr; 64 | auto balances = json_object_get(root.get(), "balances"); 65 | for (size_t i = 0; i < arraySize; i++) 66 | { 67 | std::string tmpCurrency = json_string_value(json_object_get(json_array_get(balances, i), "asset")); 68 | if (tmpCurrency.compare(cur_str.c_str()) == 0) 69 | { 70 | currstr = json_string_value(json_object_get(json_array_get(balances, i), "free")); 71 | if (currstr != NULL) 72 | { 73 | available = atof(currstr); 74 | } 75 | else 76 | { 77 | *params.logFile << " Error with currency string" << std::endl; 78 | available = 0.0; 79 | } 80 | } 81 | } 82 | return available; 83 | } 84 | //TODO: Currency String here 85 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price) 86 | { 87 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) 88 | { 89 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 90 | return "0"; 91 | } 92 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 93 | << std::setprecision(8) << quantity << " @ $" 94 | << std::setprecision(8) << price << "...\n"; 95 | std::string symbol = "BTCUSDT"; 96 | std::transform(direction.begin(), direction.end(), direction.begin(), toupper); 97 | std::string type = "LIMIT"; 98 | std::string tif = "GTC"; 99 | std::string pricelimit = std::to_string(price); 100 | std::string volume = std::to_string(quantity); 101 | std::string options = "symbol=" + symbol + "&side=" + direction + "&type=" + type + "&timeInForce=" + tif + "&price=" + pricelimit + "&quantity=" + volume; 102 | unique_json root{authRequest(params, "POST", "/api/v3/order", options)}; 103 | long txid = json_integer_value(json_object_get(root.get(), "orderId")); 104 | std::string order = std::to_string(txid); 105 | *params.logFile << " Done (transaction ID: " << order << ")\n" 106 | << std::endl; 107 | return order; 108 | } 109 | 110 | //TODO: probably not necessary 111 | std::string sendShortOrder(Parameters ¶ms, std::string direction, double quantity, double price) 112 | { 113 | return "0"; 114 | } 115 | 116 | bool isOrderComplete(Parameters ¶ms, std::string orderId) 117 | { 118 | unique_json root{authRequest(params, "GET", "/api/v3/openOrders", "")}; 119 | size_t arraySize = json_array_size(root.get()); 120 | bool complete = true; 121 | const char *idstr; 122 | for (size_t i = 0; i < arraySize; i++) 123 | { 124 | //SUGGEST: this is sort of messy 125 | long tmpInt = json_integer_value(json_object_get(json_array_get(root.get(), i), "orderId")); 126 | std::string tmpId = std::to_string(tmpInt); 127 | if (tmpId.compare(orderId.c_str()) == 0) 128 | { 129 | idstr = json_string_value(json_object_get(json_array_get(root.get(), i), "status")); 130 | *params.logFile << " Order still open (Status:" << idstr << ")" << std::endl; 131 | complete = false; 132 | } 133 | } 134 | return complete; 135 | } 136 | //TODO: Currency 137 | double getActivePos(Parameters ¶ms) 138 | { 139 | return getAvail(params, "BTC"); 140 | } 141 | 142 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 143 | { 144 | auto &exchange = queryHandle(params); 145 | //TODO build a real URI string here 146 | unique_json root{exchange.getRequest("/api/v1/depth?symbol=BTCUSDT")}; 147 | auto bidask = json_object_get(root.get(), isBid ? "bids" : "asks"); 148 | *params.logFile << " order book: " 159 | << std::setprecision(8) << v << "@$" 160 | << std::setprecision(8) << p << std::endl; 161 | tmpVol += v; 162 | i++; 163 | } 164 | return p; 165 | } 166 | 167 | json_t *authRequest(Parameters ¶ms, std::string method, std::string request, std::string options) 168 | { 169 | //create timestamp Binance is annoying and requires their servertime 170 | auto &exchange = queryHandle(params); 171 | unique_json stamper{exchange.getRequest("/api/v1/time")}; 172 | long stamp = json_integer_value(json_object_get(stamper.get(), "serverTime")); 173 | std::string timestamp = std::to_string(stamp); 174 | // create empty payload 175 | std::string payload; 176 | std::string uri; 177 | std::string sig; 178 | //our headers, might want to edit later to go into options check 179 | std::array headers{ 180 | "X-MBX-APIKEY:" + params.binanceApi, 181 | }; 182 | if (method.compare("POST") == 0) 183 | { 184 | payload += options + "×tamp=" + timestamp; 185 | sig += getSignature(params, payload); 186 | uri += request + "?" + options + "×tamp=" + timestamp + "&signature=" + sig; 187 | return exchange.postRequest(uri, make_slist(std::begin(headers), std::end(headers))); 188 | } 189 | else 190 | { 191 | if (options.empty()) 192 | { 193 | payload += "timestamp=" + timestamp; 194 | sig += getSignature(params, payload); 195 | uri += request + "?timestamp=" + timestamp + "&signature=" + sig; 196 | } 197 | else 198 | { 199 | payload += options + "×tamp=" + timestamp; 200 | sig += getSignature(params, payload); 201 | uri += request + "?" + options + "×tamp=" + timestamp + "&signature=" + sig; 202 | } 203 | return exchange.getRequest(uri, make_slist(std::begin(headers), std::end(headers))); 204 | } 205 | } 206 | 207 | static std::string getSignature(Parameters ¶ms, std::string payload) 208 | { 209 | uint8_t *hmac_digest = HMAC(EVP_sha256(), 210 | params.binanceSecret.c_str(), params.binanceSecret.size(), 211 | reinterpret_cast(payload.data()), payload.size(), 212 | NULL, NULL); 213 | std::string api_sign_header = hex_str(hmac_digest, hmac_digest + SHA256_DIGEST_LENGTH); 214 | return api_sign_header; 215 | } 216 | 217 | void testBinance() 218 | { 219 | 220 | Parameters params("blackbird.conf"); 221 | params.logFile = new std::ofstream("./test.log", std::ofstream::trunc); 222 | 223 | std::string orderId; 224 | 225 | //std::cout << "Current value LEG1_LEG2 bid: " << getQuote(params).bid() << std::endl; 226 | //std::cout << "Current value LEG1_LEG2 ask: " << getQuote(params).ask() << std::endl; 227 | //std::cout << "Current balance BTC: " << getAvail(params, "BTC") << std::endl; 228 | //std::cout << "Current balance USD: " << getAvail(params, "USD")<< std::endl; 229 | //std::cout << "Current balance ETH: " << getAvail(params, "ETH")<< std::endl; 230 | //std::cout << "Current balance BNB: " << getAvail(params, "BNB")<< std::endl; 231 | //std::cout << "current bid limit price for .04 units: " << getLimitPrice(params, 0.04, true) << std::endl; 232 | //std::cout << "Current ask limit price for 10 units: " << getLimitPrice(params, 10.0, false) << std::endl; 233 | //std::cout << "Sending buy order for 0.003603 BTC @ BID! - TXID: " << std::endl; 234 | //orderId = sendLongOrder(params, "buy", 0.003603, getLimitPrice(params,0.003603,true)); 235 | //std::cout << orderId << std::endl; 236 | //std::cout << "Buy Order is complete: " << isOrderComplete(params, orderId) << std::endl; 237 | 238 | //std::cout << "Sending sell order for 0.003603 BTC @ 10000 USD - TXID: " << std::endl; 239 | //orderId = sendLongOrder(params, "sell", 0.003603, 10000); 240 | //std::cout << orderId << std::endl; 241 | //std::cout << "Sell order is complete: " << isOrderComplete(params, orderId) << std::endl; 242 | //std::cout << "Active Position: " << getActivePos(params,orderId.c_str()); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/exchanges/okcoin.cpp: -------------------------------------------------------------------------------- 1 | #include "okcoin.h" 2 | #include "parameters.h" 3 | #include "curl_fun.h" 4 | #include "utils/restapi.h" 5 | #include "hex_str.hpp" 6 | #include "unique_json.hpp" 7 | 8 | #include "openssl/md5.h" 9 | #include 10 | #include 11 | #include 12 | #include // sleep_for 13 | #include // fabs 14 | 15 | namespace OKCoin { 16 | 17 | static RestApi& queryHandle(Parameters ¶ms) 18 | { 19 | static RestApi query ("https://www.okcoin.com", 20 | params.cacert.c_str(), *params.logFile); 21 | return query; 22 | } 23 | 24 | quote_t getQuote(Parameters ¶ms) 25 | { 26 | auto &exchange = queryHandle(params); 27 | unique_json root { exchange.getRequest("/api/v1/ticker.do?symbol=btc_usd") }; 28 | const char *quote = json_string_value(json_object_get(json_object_get(root.get(), "ticker"), "buy")); 29 | auto bidValue = quote ? std::stod(quote) : 0.0; 30 | 31 | quote = json_string_value(json_object_get(json_object_get(root.get(), "ticker"), "sell")); 32 | auto askValue = quote ? std::stod(quote) : 0.0; 33 | 34 | return std::make_pair(bidValue, askValue); 35 | } 36 | 37 | double getAvail(Parameters& params, std::string currency) 38 | { 39 | std::ostringstream oss; 40 | oss << "api_key=" << params.okcoinApi << "&secret_key=" << params.okcoinSecret; 41 | std::string signature(oss.str()); 42 | oss.clear(); 43 | oss.str(""); 44 | oss << "api_key=" << params.okcoinApi; 45 | std::string content(oss.str()); 46 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/userinfo.do", signature, content) }; 47 | double availability = 0.0; 48 | const char* returnedText; 49 | if (currency == "usd") 50 | { 51 | returnedText = json_string_value(json_object_get(json_object_get(json_object_get(json_object_get(root.get(), "info"), "funds"), "free"), "usd")); 52 | } 53 | else if (currency == "btc") 54 | { 55 | returnedText = json_string_value(json_object_get(json_object_get(json_object_get(json_object_get(root.get(), "info"), "funds"), "free"), "btc")); 56 | } 57 | else returnedText = "0.0"; 58 | 59 | if (returnedText != NULL) { 60 | availability = atof(returnedText); 61 | } else { 62 | *params.logFile << " Error with the credentials." << std::endl; 63 | availability = 0.0; 64 | } 65 | return availability; 66 | } 67 | 68 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) 69 | { 70 | // signature 71 | std::ostringstream oss; 72 | oss << "amount=" << quantity << "&api_key=" << params.okcoinApi << "&price=" << price << "&symbol=btc_usd&type=" << direction << "&secret_key=" << params.okcoinSecret; 73 | std::string signature = oss.str(); 74 | oss.clear(); 75 | oss.str(""); 76 | // content 77 | oss << "amount=" << quantity << "&api_key=" << params.okcoinApi << "&price=" << price << "&symbol=btc_usd&type=" << direction; 78 | std::string content = oss.str(); 79 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 80 | << std::setprecision(6) << quantity << "@$" 81 | << std::setprecision(2) << price << "...\n"; 82 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/trade.do", signature, content) }; 83 | auto orderId = std::to_string(json_integer_value(json_object_get(root.get(), "order_id"))); 84 | *params.logFile << " Done (order ID: " << orderId << ")\n" << std::endl; 85 | return orderId; 86 | } 87 | 88 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price) { 89 | // TODO 90 | // Unlike Bitfinex and Poloniex, on OKCoin the borrowing phase has to be done 91 | // as a separated step before being able to short sell. 92 | // Here are the steps: 93 | // Step | Function 94 | // -----------------------------------------|---------------------- 95 | // 1. ask to borrow bitcoins | borrowBtc(amount) FIXME bug "10007: Signature does not match" 96 | // 2. sell the bitcoins on the market | sendShortOrder("sell") 97 | // 3. | 98 | // 4. buy back the bitcoins on the market | sendShortOrder("buy") 99 | // 5. repay the bitcoins to the lender | repayBtc(borrowId) 100 | return "0"; 101 | } 102 | 103 | bool isOrderComplete(Parameters& params, std::string orderId) 104 | { 105 | if (orderId == "0") return true; 106 | 107 | // signature 108 | std::ostringstream oss; 109 | oss << "api_key=" << params.okcoinApi << "&order_id=" << orderId << "&symbol=btc_usd" << "&secret_key=" << params.okcoinSecret; 110 | std::string signature = oss.str(); 111 | oss.clear(); 112 | oss.str(""); 113 | // content 114 | oss << "api_key=" << params.okcoinApi << "&order_id=" << orderId << "&symbol=btc_usd"; 115 | std::string content = oss.str(); 116 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/order_info.do", signature, content) }; 117 | auto status = json_integer_value(json_object_get(json_array_get(json_object_get(root.get(), "orders"), 0), "status")); 118 | 119 | return status == 2; 120 | } 121 | 122 | double getActivePos(Parameters& params) { return getAvail(params, "btc"); } 123 | 124 | double getLimitPrice(Parameters& params, double volume, bool isBid) 125 | { 126 | auto &exchange = queryHandle(params); 127 | unique_json root { exchange.getRequest("/api/v1/depth.do?symbol=btc_usd") }; 128 | auto bidask = json_object_get(root.get(), isBid ? "bids" : "asks"); 129 | 130 | // loop on volume 131 | *params.logFile << " Looking for a limit price to fill " 132 | << std::setprecision(6) << fabs(volume) << " BTC...\n"; 133 | double tmpVol = 0.0; 134 | double p = 0.0; 135 | double v; 136 | size_t i = isBid ? 0 : json_array_size(bidask) - 1; 137 | size_t step = isBid ? 1 : -1; 138 | while (tmpVol < fabs(volume) * params.orderBookFactor) 139 | { 140 | p = json_number_value(json_array_get(json_array_get(bidask, i), 0)); 141 | v = json_number_value(json_array_get(json_array_get(bidask, i), 1)); 142 | *params.logFile << " order book: " 143 | << std::setprecision(6) << v << "@$" 144 | << std::setprecision(2) << p << std::endl; 145 | tmpVol += v; 146 | i += step; 147 | } 148 | 149 | return p; 150 | } 151 | 152 | json_t* authRequest(Parameters& params, std::string url, std::string signature, std::string content) 153 | { 154 | uint8_t digest[MD5_DIGEST_LENGTH]; 155 | MD5((uint8_t *)signature.data(), signature.length(), (uint8_t *)&digest); 156 | 157 | std::ostringstream oss; 158 | oss << content << "&sign=" << hex_str(digest, digest + MD5_DIGEST_LENGTH); 159 | std::string postParameters = oss.str().c_str(); 160 | curl_slist *headers = curl_slist_append(nullptr, "contentType: application/x-www-form-urlencoded"); 161 | CURLcode resCurl; 162 | if (params.curl) { 163 | curl_easy_setopt(params.curl, CURLOPT_POST,1L); 164 | curl_easy_setopt(params.curl, CURLOPT_HTTPHEADER, headers); 165 | curl_easy_setopt(params.curl, CURLOPT_POSTFIELDS, postParameters.c_str()); 166 | curl_easy_setopt(params.curl, CURLOPT_SSL_VERIFYPEER, 0L); 167 | curl_easy_setopt(params.curl, CURLOPT_WRITEFUNCTION, WriteCallback); 168 | std::string readBuffer; 169 | curl_easy_setopt(params.curl, CURLOPT_WRITEDATA, &readBuffer); 170 | curl_easy_setopt(params.curl, CURLOPT_URL, url.c_str()); 171 | curl_easy_setopt(params.curl, CURLOPT_CONNECTTIMEOUT, 10L); 172 | resCurl = curl_easy_perform(params.curl); 173 | 174 | using std::this_thread::sleep_for; 175 | using secs = std::chrono::seconds; 176 | while (resCurl != CURLE_OK) { 177 | *params.logFile << " Error with cURL. Retry in 2 sec..." << std::endl; 178 | sleep_for(secs(4)); 179 | resCurl = curl_easy_perform(params.curl); 180 | } 181 | json_error_t error; 182 | json_t *root = json_loads(readBuffer.c_str(), 0, &error); 183 | while (!root) { 184 | *params.logFile << " Error with JSON:\n" << error.text << std::endl; 185 | *params.logFile << " Buffer:\n" << readBuffer.c_str() << std::endl; 186 | *params.logFile << " Retrying..." << std::endl; 187 | sleep_for(secs(4)); 188 | readBuffer = ""; 189 | resCurl = curl_easy_perform(params.curl); 190 | while (resCurl != CURLE_OK) { 191 | *params.logFile << " Error with cURL. Retry in 2 sec..." << std::endl; 192 | sleep_for(secs(4)); 193 | readBuffer = ""; 194 | resCurl = curl_easy_perform(params.curl); 195 | } 196 | root = json_loads(readBuffer.c_str(), 0, &error); 197 | } 198 | curl_slist_free_all(headers); 199 | curl_easy_reset(params.curl); 200 | return root; 201 | } else { 202 | *params.logFile << " Error with cURL init." << std::endl; 203 | return NULL; 204 | } 205 | } 206 | 207 | void getBorrowInfo(Parameters& params) 208 | { 209 | std::ostringstream oss; 210 | oss << "api_key=" << params.okcoinApi << "&symbol=btc_usd&secret_key=" << params.okcoinSecret; 211 | std::string signature = oss.str(); 212 | oss.clear(); 213 | oss.str(""); 214 | oss << "api_key=" << params.okcoinApi << "&symbol=btc_usd"; 215 | std::string content = oss.str(); 216 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/borrows_info.do", signature, content) }; 217 | auto dump = json_dumps(root.get(), 0); 218 | *params.logFile << " Borrow info:\n" << dump << std::endl; 219 | free(dump); 220 | } 221 | 222 | int borrowBtc(Parameters& params, double amount) 223 | { 224 | std::ostringstream oss; 225 | oss << "api_key=" << params.okcoinApi << "&symbol=btc_usd&days=fifteen&amount=" << 1 << "&rate=0.0001&secret_key=" << params.okcoinSecret; 226 | std::string signature = oss.str(); 227 | oss.clear(); 228 | oss.str(""); 229 | oss << "api_key=" << params.okcoinApi << "&symbol=btc_usd&days=fifteen&amount=" << 1 << "&rate=0.0001"; 230 | std::string content = oss.str(); 231 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/borrow_money.do", signature, content) }; 232 | auto dump = json_dumps(root.get(), 0); 233 | *params.logFile << " Borrow " 234 | << std::setprecision(6) << amount 235 | << " BTC:\n" << dump << std::endl; 236 | free(dump); 237 | bool isBorrowAccepted = json_is_true(json_object_get(root.get(), "result")); 238 | return isBorrowAccepted ? 239 | json_integer_value(json_object_get(root.get(), "borrow_id")) : 240 | 0; 241 | } 242 | 243 | void repayBtc(Parameters& params, int borrowId) 244 | { 245 | std::ostringstream oss; 246 | oss << "api_key=" << params.okcoinApi << "&borrow_id=" << borrowId << "&secret_key=" << params.okcoinSecret; 247 | std::string signature = oss.str(); 248 | oss.clear(); 249 | oss.str(""); 250 | oss << "api_key=" << params.okcoinApi << "&borrow_id=" << borrowId; 251 | std::string content = oss.str(); 252 | unique_json root { authRequest(params, "https://www.okcoin.com/api/v1/repayment.do", signature, content) }; 253 | auto dump = json_dumps(root.get(), 0); 254 | *params.logFile << " Repay borrowed BTC:\n" << dump << std::endl; 255 | free(dump); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/exchanges/kraken.cpp: -------------------------------------------------------------------------------- 1 | #include "kraken.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "unique_json.hpp" 6 | 7 | #include "openssl/sha.h" 8 | #include "openssl/hmac.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Kraken 15 | { 16 | 17 | // Initialise internal variables 18 | static unique_json krakenTicker = nullptr; 19 | static bool krakenGotTicker = false; 20 | 21 | 22 | static RestApi &queryHandle(Parameters ¶ms) 23 | { 24 | static RestApi query("https://api.kraken.com", 25 | params.cacert.c_str(), *params.logFile); 26 | return query; 27 | } 28 | 29 | quote_t getQuote(Parameters ¶ms) 30 | { 31 | if (krakenGotTicker) 32 | { 33 | krakenGotTicker = false; 34 | } 35 | else 36 | { 37 | auto &exchange = queryHandle(params); 38 | krakenTicker.reset(exchange.getRequest("/0/public/Ticker?pair=XXBTZUSD")); 39 | krakenGotTicker = true; 40 | } 41 | json_t *root = krakenTicker.get(); 42 | const char *quote = json_string_value(json_array_get(json_object_get(json_object_get(json_object_get(root, "result"), "XXBTZUSD"), "b"), 0)); 43 | auto bidValue = quote ? std::stod(quote) : 0.0; 44 | 45 | quote = json_string_value(json_array_get(json_object_get(json_object_get(json_object_get(root, "result"), "XXBTZUSD"), "a"), 0)); 46 | auto askValue = quote ? std::stod(quote) : 0.0; 47 | 48 | return std::make_pair(bidValue, askValue); 49 | } 50 | 51 | double getAvail(Parameters ¶ms, std::string currency) 52 | { 53 | unique_json root{authRequest(params, "/0/private/Balance")}; 54 | json_t *result = json_object_get(root.get(), "result"); 55 | if (json_object_size(result) == 0) 56 | { 57 | return 0.0; 58 | } 59 | double available = 0.0; 60 | if (currency.compare("usd") == 0) 61 | { 62 | const char *avail_str = json_string_value(json_object_get(result, "ZUSD")); 63 | available = avail_str ? atof(avail_str) : 0.0; 64 | } 65 | else if (currency.compare("btc") == 0) 66 | { 67 | const char *avail_str = json_string_value(json_object_get(result, "XXBT")); 68 | available = avail_str ? atof(avail_str) : 0.0; 69 | } 70 | else 71 | { 72 | *params.logFile << " Currency not supported" << std::endl; 73 | } 74 | return available; 75 | } 76 | 77 | std::string sendLongOrder(Parameters ¶ms, std::string direction, double quantity, double price) 78 | { 79 | return sendOrder(params, direction, quantity, price); 80 | } 81 | 82 | std::string sendOrder(Parameters ¶ms, std::string direction, double quantity, double price) 83 | { 84 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) 85 | { 86 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 87 | return "0"; 88 | } 89 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 90 | << std::setprecision(6) << quantity << " @ $" 91 | << std::setprecision(2) << price << "...\n"; 92 | std::string pair = "XXBTZUSD"; 93 | std::string type = direction; 94 | std::string ordertype = "limit"; 95 | std::string pricelimit = std::to_string(price); 96 | std::string volume = std::to_string(quantity); 97 | std::string options = "pair=" + pair + "&type=" + type + "&ordertype=" + ordertype + "&price=" + pricelimit + "&volume=" + volume + "&trading_agreement=agree"; 98 | unique_json root{authRequest(params, "/0/private/AddOrder", options)}; 99 | json_t *res = json_object_get(root.get(), "result"); 100 | if (json_is_object(res) == 0) 101 | { 102 | *params.logFile << json_dumps(root.get(), 0) << std::endl; 103 | exit(0); 104 | } 105 | std::string txid = json_string_value(json_array_get(json_object_get(res, "txid"), 0)); 106 | *params.logFile << " Done (transaction ID: " << txid << ")\n" 107 | << std::endl; 108 | return txid; 109 | } 110 | 111 | std::string sendShortOrder(Parameters ¶ms, std::string direction, double quantity, double price) 112 | { 113 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) 114 | { 115 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 116 | return "0"; 117 | } 118 | *params.logFile << " Trying to send a short \"" << direction << "\" limit order: " 119 | << std::setprecision(6) << quantity << " @ $" 120 | << std::setprecision(2) << price << "...\n"; 121 | std::string pair = "XXBTZUSD"; 122 | std::string type = direction; 123 | std::string ordertype; 124 | std::string options; 125 | std::string pricelimit = std::to_string(price); 126 | std::string volume = std::to_string(quantity); 127 | std::string leverage = "2"; 128 | ordertype = "limit"; 129 | options = "pair=" + pair + "&type=" + type + "&ordertype=" + ordertype + "&price=" + pricelimit + "&volume=" + volume + "&leverage=" + leverage + "&trading_agreement=agree"; 130 | unique_json root{authRequest(params, "/0/private/AddOrder", options)}; 131 | json_t *res = json_object_get(root.get(), "result"); 132 | if (json_is_object(res) == 0) 133 | { 134 | *params.logFile << json_dumps(root.get(), 0) << std::endl; 135 | exit(0); 136 | } 137 | std::string txid = json_string_value(json_array_get(json_object_get(res, "txid"), 0)); 138 | *params.logFile << " Done (transaction ID: " << txid << ")\n" 139 | << std::endl; 140 | return txid; 141 | } 142 | 143 | bool isOrderComplete(Parameters ¶ms, std::string orderId) 144 | { 145 | unique_json root{authRequest(params, "/0/private/OpenOrders")}; 146 | // no open order: return true 147 | auto res = json_object_get(json_object_get(root.get(), "result"), "open"); 148 | if (json_object_size(res) == 0) 149 | { 150 | *params.logFile << " No order exists" << std::endl; 151 | return true; 152 | } 153 | res = json_object_get(res, orderId.c_str()); 154 | // open orders exist but specific order not found: return true 155 | if (json_object_size(res) == 0) 156 | { 157 | *params.logFile << " Order " << orderId << " does not exist" << std::endl; 158 | return true; 159 | // open orders exist and specific order was found: return false 160 | } 161 | else 162 | { 163 | *params.logFile << " Order " << orderId << " still exists!" << std::endl; 164 | return false; 165 | } 166 | } 167 | 168 | double getActivePos(Parameters ¶ms) 169 | { 170 | return getAvail(params, "btc"); 171 | } 172 | 173 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 174 | { 175 | auto &exchange = queryHandle(params); 176 | unique_json root { exchange.getRequest("/0/public/Depth?pair=XXBTZUSD") }; 177 | auto branch = json_object_get(json_object_get(root.get(), "result"), "XXBTZUSD"); 178 | branch = json_object_get(branch, isBid ? "bids" : "asks"); 179 | 180 | // loop on volume 181 | double totVol = 0.0; 182 | double currPrice = 0; 183 | double currVol = 0; 184 | unsigned int i; 185 | // [[, , ], [, , ], ...] 186 | for (i = 0; i < json_array_size(branch); i++) 187 | { 188 | // volumes are added up until the requested volume is reached 189 | currVol = atof(json_string_value(json_array_get(json_array_get(branch, i), 1))); 190 | currPrice = atof(json_string_value(json_array_get(json_array_get(branch, i), 0))); 191 | totVol += currVol; 192 | if (totVol >= volume * params.orderBookFactor) 193 | break; 194 | } 195 | 196 | return currPrice; 197 | } 198 | 199 | json_t *authRequest(Parameters ¶ms, std::string request, std::string options) 200 | { 201 | // create nonce and POST data 202 | static uint64_t nonce = time(nullptr) * 4; 203 | std::string post_data = "nonce=" + std::to_string(++nonce); 204 | if (!options.empty()) 205 | post_data += "&" + options; 206 | 207 | // Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) 208 | // and base64 decoded secret API key 209 | auto sig_size = request.size() + SHA256_DIGEST_LENGTH; 210 | std::vector sig_data(sig_size); 211 | copy(std::begin(request), std::end(request), std::begin(sig_data)); 212 | 213 | std::string payload_for_signature = std::to_string(nonce) + post_data; 214 | SHA256((uint8_t *)payload_for_signature.c_str(), payload_for_signature.size(), 215 | &sig_data[sig_size - SHA256_DIGEST_LENGTH]); 216 | 217 | std::string decoded_key = base64_decode(params.krakenSecret); 218 | uint8_t *hmac_digest = HMAC(EVP_sha512(), 219 | decoded_key.c_str(), decoded_key.length(), 220 | sig_data.data(), sig_data.size(), NULL, NULL); 221 | std::string api_sign_header = base64_encode(hmac_digest, SHA512_DIGEST_LENGTH); 222 | // cURL header 223 | std::array headers{ 224 | "API-KEY:" + params.krakenApi, 225 | "API-Sign:" + api_sign_header, 226 | }; 227 | 228 | // cURL request 229 | auto &exchange = queryHandle(params); 230 | return exchange.postRequest(request, 231 | make_slist(std::begin(headers), std::end(headers)), 232 | post_data); 233 | } 234 | 235 | void testKraken() 236 | { 237 | 238 | Parameters params("bird.conf"); 239 | params.logFile = new std::ofstream("./test.log", std::ofstream::trunc); 240 | 241 | std::string orderId; 242 | 243 | std::cout << "Current value LEG1_LEG2 bid: " << getQuote(params).bid() << std::endl; 244 | std::cout << "Current value LEG1_LEG2 ask: " << getQuote(params).ask() << std::endl; 245 | std::cout << "Current balance BTC: " << getAvail(params, "btc") << std::endl; 246 | std::cout << "Current balance USD: " << getAvail(params, "usd") << std::endl; 247 | std::cout << "Current balance ETH: " << getAvail(params, "eth") << std::endl; 248 | std::cout << "Current balance XMR: " << getAvail(params, "xmr") << std::endl; 249 | std::cout << "current bid limit price for .09 units: " << getLimitPrice(params, 0.09, true) << std::endl; 250 | std::cout << "Current ask limit price for .09 units: " << getLimitPrice(params, 0.09, false) << std::endl; 251 | //std::cout << "Sending buy order for 0.01 XMR @ $100 USD - TXID: " << std::endl; 252 | //orderId = sendLongOrder(params, "buy", 0.01, 100); 253 | //std::cout << orderId << std::endl; 254 | ///// if you don't wait bittrex won't recognize order for iscomplete 255 | //sleep(5); 256 | //std::cout << "Buy Order is complete: " << isOrderComplete(params, orderId) << std::endl; 257 | 258 | //std::cout << "Sending Short XMR order for 0.177 XMR @BID! USD: "; 259 | //orderId = sendShortOrder(params,"sell",0.133, getLimitPrice(params,0.133,true)); 260 | //std::cout << orderId << std::endl; 261 | //std::cout << "Closing Short XMR order for .09 - TXID: "; 262 | //orderId = sendShortOrder(params, "buy", 0.046, getLimitPrice(params,0.046, false)); 263 | //std::cout << orderId << std::endl; 264 | 265 | //vanilla sell orders below 266 | //std::cout << "Buy order is complete: " << isOrderComplete(params, orderId) << std::endl; 267 | //std::cout << "Sending sell order for 0.01 XMR @ 5000 USD - TXID: " << std::endl ; 268 | //orderId = sendLongOrder(params, "sell", 0.01, 5000); 269 | //std:: cout << orderId << std::endl; 270 | //std::cout << "Sell order is complete: " << isOrderComplete(params, orderId) << std::endl; 271 | //std::cout << "Active Position: " << getActivePos(params); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/exchanges/bittrex.cpp: -------------------------------------------------------------------------------- 1 | #include "bittrex.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "unique_json.hpp" 5 | #include "hex_str.hpp" 6 | 7 | #include "openssl/sha.h" 8 | #include "openssl/hmac.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Bittrex { 17 | static json_t* authRequest(Parameters &, std::string, std::string); 18 | static RestApi& queryHandle(Parameters ¶ms) 19 | { 20 | static RestApi query ("https://bittrex.com", 21 | params.cacert.c_str(), *params.logFile); 22 | return query; 23 | } 24 | 25 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 26 | { 27 | auto errmsg = json_object_get(root, "error"); 28 | if (errmsg) 29 | logFile << " Error with response: " 30 | << json_string_value(errmsg) << '\n'; 31 | 32 | return root; 33 | } 34 | 35 | quote_t getQuote(Parameters ¶ms) 36 | { 37 | auto &exchange = queryHandle(params); 38 | std::string x; 39 | 40 | x += "/api/v1.1/public/getticker?market="; 41 | x += "USDT-BTC"; 42 | //params.leg2.c_str(); 43 | 44 | unique_json root {exchange.getRequest(x)}; 45 | 46 | double quote = json_number_value(json_object_get(json_object_get(root.get(), "result"), "Bid")); 47 | auto bidValue = quote ? quote : 0.0; 48 | 49 | quote = json_number_value(json_object_get(json_object_get(root.get(), "result"), "Ask")); 50 | auto askValue = quote ? quote : 0.0; 51 | 52 | return std::make_pair(bidValue, askValue); 53 | } 54 | 55 | double getAvail(Parameters ¶ms, std::string currency) 56 | { 57 | std::string cur_str; 58 | cur_str += "currency="; 59 | if (currency.compare("USD")==0){ 60 | cur_str += "USDT"; 61 | } 62 | else { 63 | cur_str += currency.c_str(); 64 | } 65 | unique_json root { authRequest(params, "/api/v1.1/account/getbalance", cur_str)}; 66 | 67 | double available = json_number_value(json_object_get(json_object_get(root.get(), "result"),"Available")); 68 | return available; 69 | } 70 | // this function name is misleading it is not a "long" order but a non margin order. 71 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) { 72 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) { 73 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 74 | return "0"; 75 | } 76 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 77 | << std::setprecision(8) << quantity << " @ $" 78 | << std::setprecision(8) << price << "...\n"; 79 | std::string pair = "USDT-BTC"; 80 | std::string type = direction; 81 | std::string pricelimit = std::to_string(price); 82 | std::string volume = std::to_string(quantity); 83 | std::string options = "market=" + pair + "&quantity=" + volume + "&rate=" + pricelimit; 84 | std::string url = "/api/v1.1/market/" + direction + "limit"; 85 | unique_json root { authRequest(params, url, options) }; 86 | // theres some problem here that can produce a seg fault. 87 | auto txid = json_string_value(json_object_get(json_object_get(root.get(), "result"), "uuid")); 88 | 89 | *params.logFile << " Done (transaction ID: " << txid << ")\n" << std::endl; 90 | return txid; 91 | } 92 | //SUGGEST: probably not necessary 93 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price) { 94 | if (direction.compare("buy") != 0 && direction.compare("sell") != 0) { 95 | *params.logFile << " Error: Neither \"buy\" nor \"sell\" selected" << std::endl; 96 | return "0"; 97 | } 98 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 99 | << std::setprecision(8) << quantity << " @ $" 100 | << std::setprecision(8) << price << "...\n"; 101 | std::string pair = "USDT-BTC"; 102 | std::string pricelimit = std::to_string(price); 103 | std::string volume = std::to_string(quantity); 104 | std::string options = "market=" + pair + "&quantity=" + volume + "&rate=" + pricelimit; 105 | unique_json root { authRequest(params, "/api/v1.1/market/selllimit", options) }; 106 | //theres so 107 | auto txid = json_string_value(json_object_get(json_object_get(root.get(), "result"), "uuid")); 108 | 109 | *params.logFile << " Done (transaction ID: " << txid << ")\n" << std::endl; 110 | return txid; 111 | } 112 | //This is not used at the moment, but could pull out send long/short order. Leaving as is for now 113 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price) 114 | { 115 | *params.logFile << " Trying to send a \"" << direction << "\" limit order: " 116 | << std::setprecision(6) << quantity << "@$" 117 | << std::setprecision(2) << price << "...\n"; 118 | std::ostringstream oss; 119 | oss << "\"symbol\":\"btcusd\", \"amount\":\"" << quantity << "\", \"price\":\"" << price << "\", \"exchange\":\"bitfinex\", \"side\":\"" << direction << "\", \"type\":\"limit\""; 120 | std::string options = oss.str(); 121 | unique_json root { authRequest(params, "/v1/order/new", options) }; 122 | 123 | auto orderId = std::to_string(json_integer_value(json_object_get(root.get(), "order_id"))); 124 | *params.logFile << " Done (order ID: " << orderId << ")\n" << std::endl; 125 | return orderId; 126 | } 127 | 128 | bool isOrderComplete(Parameters& params, std::string orderId) 129 | { 130 | //TODO Build a real currency string for options here (or outside?) 131 | unique_json root { authRequest(params, "/api/v1.1/market/getopenorders","market=USDT-BTC")}; 132 | auto res = json_object_get(root.get(), "result"); 133 | // loop through the array to check orders 134 | std::string uuid; 135 | int size = json_array_size (res); 136 | if (json_array_size(res) == 0) { 137 | *params.logFile << " No orders exist" << std::endl; 138 | return true; 139 | } 140 | bool isOrderStillOpen = true; 141 | for(int i = 0; i < size; i++){ 142 | uuid = json_string_value(json_object_get(json_array_get(res,i),"OrderUuid")); 143 | if (uuid.compare(orderId.c_str())==0){ 144 | *params.logFile << " Order " << orderId << " still exists" << std::endl; 145 | isOrderStillOpen = false; 146 | } 147 | } 148 | if (isOrderStillOpen){ 149 | *params.logFile << " Order " << orderId << " does not exist" << std::endl; 150 | } 151 | return isOrderStillOpen; 152 | } 153 | 154 | double getActivePos(Parameters& params) { 155 | return getAvail(params, "BTC"); 156 | } 157 | 158 | double getLimitPrice(Parameters& params, double volume, bool isBid) { 159 | // takes a quantity we want and if its a bid or not 160 | auto &exchange = queryHandle(params); 161 | //TODO build a real URI string here 162 | unique_json root { exchange.getRequest("/api/v1.1/public/getorderbook?market=USDT-BTC&type=both") }; 163 | auto bidask = json_object_get(json_object_get(root.get(),"result"),isBid ? "buy" : "sell"); 164 | // loop on volume 165 | *params.logFile << " Looking for a limit price to fill " 166 | << std::setprecision(8) << fabs(volume) << " Legx...\n"; 167 | double tmpVol = 0.0; 168 | double p = 0.0; 169 | double v; 170 | int i = 0; 171 | while (tmpVol < fabs(volume) * params.orderBookFactor) 172 | { 173 | p = json_number_value(json_object_get(json_array_get(bidask, i), "Rate")); 174 | v = json_number_value(json_object_get(json_array_get(bidask, i), "Quantity")); 175 | *params.logFile << " order book: " 176 | << std::setprecision(8) << v << "@$" 177 | << std::setprecision(8) << p << std::endl; 178 | tmpVol += v; 179 | i++; 180 | } 181 | 182 | return p; 183 | } 184 | 185 | // build auth request - needs to append apikey, nonce, and calculate HMAC 512 hash and include it under api sign header 186 | json_t* authRequest(Parameters ¶ms, std::string request, std:: string options) 187 | { 188 | //TODO: create nonce NOT respected right now so not calculating but could be added with std::to_string(++nonce) 189 | //static uint64_t nonce = time(nullptr) * 4; 190 | // this message is the full uri for sig signing. 191 | auto msg = "https://bittrex.com" + request +"?apikey=" + params.bittrexApi.c_str() +"&nonce=0"; 192 | //append options to full uri and partial URI 193 | std::string uri = request + "?apikey=" + params.bittrexApi + "&nonce=0"; 194 | if (!options.empty()){ 195 | msg += "&"; 196 | msg += options; 197 | uri += "&"; 198 | uri += options; 199 | } 200 | // SHA512 of URI and API SECRET 201 | // this function grabs the HMAC hash (using sha512) of the secret and the full URI 202 | uint8_t *hmac_digest = HMAC(EVP_sha512(), 203 | params.bittrexSecret.c_str(), params.bittrexSecret.size(), 204 | reinterpret_cast(msg.data()),msg.size(), 205 | NULL, NULL); 206 | // creates a hex string of the hmac hash 207 | std::string api_sign_header = hex_str(hmac_digest, hmac_digest + SHA512_DIGEST_LENGTH); 208 | // create a base for appending the initial request domain 209 | std::string postParams = "?apikey=" + params.bittrexApi + 210 | "&nonce=0"; 211 | //once again append the extra options 212 | if (!options.empty()) 213 | { 214 | postParams += "&"; 215 | postParams += options; 216 | } 217 | std::array headers 218 | { 219 | "apisign:" + api_sign_header 220 | }; 221 | //curl the request 222 | auto &exchange = queryHandle(params); 223 | return checkResponse(*params.logFile, exchange.postRequest(uri, make_slist(std::begin(headers), std::end(headers)), 224 | postParams)); 225 | } 226 | 227 | 228 | 229 | void testBittrex(){ 230 | 231 | Parameters params("blackbird.conf"); 232 | params.logFile = new std::ofstream("./test.log" , std::ofstream::trunc); 233 | 234 | std::string orderId; 235 | 236 | //std::cout << "Current value LEG1_LEG2 bid: " << getQuote(params).bid() << std::endl; 237 | //std::cout << "Current value LEG1_LEG2 ask: " << getQuote(params).ask() << std::endl; 238 | //std::cout << "Current balance XMR: " << getAvail(params, "XMR") << std::endl; 239 | //std::cout << "Current balance USD: " << getAvail(params, "USD")<< std::endl; 240 | //std::cout << "Current balance ETH: " << getAvail(params, "ETH")<< std::endl; 241 | //std::cout << "Current balance BTC: " << getAvail(params, "BTC")<< std::endl; 242 | //std::cout << "current bid limit price for 10 units: " << getLimitPrice(params, 10.0, true) << std::endl; 243 | //std::cout << "Current ask limit price for 10 units: " << getLimitPrice(params, 10.0, false) << std::endl; 244 | //std::cout << "Sending buy order for 0.01 XMR @ $100 USD - TXID: " << std::endl; 245 | //orderId = sendLongOrder(params, "buy", 0.01, 100); 246 | //std::cout << orderId << std::endl; 247 | ///// if you don't wait bittrex won't recognize order for iscomplete 248 | //sleep(5); 249 | //std::cout << "Buy Order is complete: " << isOrderComplete(params, orderId) << std::endl; 250 | 251 | // TODO: Test sell orders, really should work though. 252 | //std::cout << orderId << std::endl; 253 | //std::cout << "Buy order is complete: " << isOrderComplete(params, orderId) << std::endl; 254 | //std::cout << "Sending sell order for 0.01 XMR @ 5000 USD - TXID: " << std::endl ; 255 | //orderId = sendLongOrder(params, "sell", 0.01, 5000); 256 | //std:: cout << orderId << std::endl; 257 | //std::cout << "Sell order is complete: " << isOrderComplete(params, orderId) << std::endl; 258 | //std::cout << "Active Position: " << getActivePos(params, orderId); 259 | } 260 | } -------------------------------------------------------------------------------- /src/exchanges/cexio.cpp: -------------------------------------------------------------------------------- 1 | #include "cexio.h" 2 | #include "parameters.h" 3 | #include "utils/restapi.h" 4 | #include "utils/base64.h" 5 | #include "unique_json.hpp" 6 | #include "hex_str.hpp" 7 | 8 | #include "openssl/sha.h" 9 | #include "openssl/hmac.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Cexio { 18 | 19 | static json_t* authRequest(Parameters &, std::string, std::string); 20 | 21 | static bool g_bShort= false; 22 | static std::string g_strOpenId = "0"; 23 | 24 | 25 | static RestApi& queryHandle(Parameters ¶ms) 26 | { 27 | static RestApi query ("https://cex.io/api", 28 | params.cacert.c_str(), *params.logFile); 29 | return query; 30 | } 31 | 32 | static json_t* checkResponse(std::ostream &logFile, json_t *root) 33 | { 34 | auto errstatus = json_object_get(root, "error"); 35 | 36 | if (errstatus) 37 | { 38 | auto errmsg = json_dumps(errstatus, JSON_ENCODE_ANY); 39 | logFile << " Error with response: " 40 | << errmsg << '\n'; 41 | free(errmsg); 42 | } 43 | 44 | return root; 45 | } 46 | 47 | quote_t getQuote(Parameters ¶ms) 48 | { 49 | auto &exchange = queryHandle(params); 50 | unique_json root { exchange.getRequest("/ticker/BTC/USD") }; 51 | 52 | double bidValue = json_number_value(json_object_get(root.get(), "bid")); 53 | double askValue = json_number_value(json_object_get(root.get(), "ask")); 54 | 55 | return std::make_pair(bidValue, askValue); 56 | } 57 | 58 | double getAvail(Parameters& params, std::string currency) 59 | { 60 | double available = 0.0; 61 | std::transform(currency.begin(), currency.end(), currency.begin(), ::toupper); 62 | const char * curr_ = currency.c_str(); 63 | 64 | unique_json root { authRequest(params, "/balance/","") }; 65 | 66 | const char * avail_str = json_string_value(json_object_get(json_object_get(root.get(), curr_), "available")); 67 | available = avail_str ? atof(avail_str) : 0.0; 68 | 69 | return available; 70 | } 71 | 72 | std::string sendLongOrder(Parameters& params, std::string direction, double quantity, double price) 73 | { 74 | g_bShort = false; 75 | return sendOrder(params, direction, quantity, price); 76 | 77 | } 78 | 79 | std::string sendShortOrder(Parameters& params, std::string direction, double quantity, double price) 80 | { 81 | //return sendOrder(params, direction, quantity, price); 82 | 83 | g_bShort = true; 84 | 85 | if(direction.compare("sell") == 0) 86 | { 87 | return openPosition(params, direction, quantity, price); 88 | 89 | }else if(direction.compare("buy")== 0){ 90 | 91 | return closePosition(params); 92 | 93 | } 94 | 95 | return "0"; 96 | } 97 | 98 | 99 | std::string openPosition(Parameters& params, std::string direction, double quantity, double price) 100 | { 101 | using namespace std; 102 | string pair = "btc_usd"; 103 | string orderId = ""; 104 | *params.logFile << " Trying to open a " << pair << " " << direction << " position: " << quantity << "@" << price << endl; 105 | 106 | ostringstream oss; 107 | string ptype = ""; 108 | double stopLossPrice = price ; 109 | 110 | if(direction.compare("sell") == 0) 111 | { 112 | ptype = "short"; 113 | stopLossPrice += 500; 114 | 115 | }else if(direction.compare("buy")== 0){ 116 | ptype = "long"; 117 | stopLossPrice -= 500; 118 | } 119 | 120 | oss << "symbol=" << "BTC" << "&amount=" << setprecision(8) << quantity << "&msymbol=" << "USD" << "&leverage=" << 3 << "&ptype=" << ptype << "&anySlippage=" << "true" << "&eoprice=" << setprecision(8) << price << "&stopLossPrice=" << stopLossPrice; 121 | 122 | string options = oss.str(); 123 | 124 | unique_json root {authRequest(params,"/open_position/BTC/USD/",options)}; 125 | auto error = json_string_value(json_object_get(root.get(),"error")); 126 | 127 | ostringstream oss1; 128 | 129 | if(error){ 130 | 131 | orderId = "0"; 132 | 133 | }else{ 134 | 135 | oss1 << json_number_value(json_object_get(json_object_get(root.get(),"data"),"id")); 136 | orderId = oss1.str(); 137 | 138 | *params.logFile << "Open Position Done (positon ID): " << orderId << ")\n" << endl; 139 | 140 | } 141 | 142 | g_strOpenId = orderId; 143 | 144 | return orderId; 145 | } 146 | 147 | std::string closePosition(Parameters& params) 148 | { 149 | 150 | 151 | if(g_strOpenId == "0") return "0"; 152 | 153 | using namespace std; 154 | string orderId = "0"; 155 | std::string tmpId = g_strOpenId; 156 | ostringstream oss; 157 | oss << "id=" << tmpId; 158 | string options = oss.str(); 159 | 160 | unique_json root {authRequest(params,"/close_position/BTC/USD/",options)}; 161 | auto error = json_string_value(json_object_get(root.get(),"error")); 162 | 163 | ostringstream oss1; 164 | 165 | if(error){ 166 | orderId ="0"; 167 | 168 | }else{ 169 | 170 | oss1 << json_number_value(json_object_get(json_object_get(root.get(),"data"),"id")); 171 | orderId = oss1.str(); 172 | 173 | } 174 | 175 | return orderId; 176 | 177 | /* 178 | 179 | using namespace std; 180 | string orderId = ""; 181 | unique_json root {authRequest(params,"/open_positions/BTC/USD/","")}; 182 | size_t arraySize = json_array_size(json_object_get(root.get(),"data")); 183 | auto data = json_object_get(root.get(),"data"); 184 | for(size_t i=0; i< arraySize;i++){ 185 | 186 | std::string tmpId = json_string_value(json_object_get(json_array_get(data,i),"id")); 187 | ostringstream oss; 188 | oss << "id=" << tmpId; 189 | string options = oss.str(); 190 | 191 | unique_json root1 {authRequest(params,"close_position/BTC/USD/",options)}; 192 | auto error = json_string_value(json_object_get(root1.get(),"error")); 193 | if(error){ 194 | orderId ="0"; 195 | 196 | }else{ 197 | 198 | orderId = json_string_value(json_object_get(root1.get(),"id")); 199 | 200 | } 201 | 202 | } 203 | 204 | 205 | return orderId; 206 | */ 207 | 208 | } 209 | 210 | 211 | 212 | std::string sendOrder(Parameters& params, std::string direction, double quantity, double price) 213 | { 214 | using namespace std; 215 | string pair = "btc_usd"; 216 | string orderId = ""; 217 | *params.logFile << " Trying to send a " << pair << " " << direction << " limit order: " << quantity << "@" << price << endl; 218 | 219 | ostringstream oss; 220 | oss << "type=" << direction << "&amount=" << quantity << "&price=" << fixed << setprecision(2) << price; 221 | string options = oss.str(); 222 | 223 | unique_json root { authRequest(params, "/place_order/BTC/USD/", options) }; 224 | auto error = json_string_value(json_object_get(root.get(), "error")); 225 | if (error){ 226 | // auto dump = json_dumps(root.get(), 0); 227 | // *params.logFile << " Error placing order: " << dump << ")\n" << endl; 228 | // free(dump); 229 | orderId = "0"; 230 | } else { 231 | orderId = json_string_value(json_object_get(root.get(), "id")); 232 | *params.logFile << " Done (order ID): " << orderId << ")\n" << endl; 233 | } 234 | return orderId; 235 | } 236 | 237 | bool isOrderComplete(Parameters& params, std::string orderId) 238 | { 239 | 240 | 241 | using namespace std; 242 | if (orderId == "0") return false; 243 | 244 | 245 | auto oId = stol(orderId); 246 | ostringstream oss; 247 | oss << "id=" << oId; 248 | string options = oss.str(); 249 | 250 | if(g_bShort) 251 | { 252 | unique_json root { authRequest(params, "/get_position/", options) }; 253 | 254 | string status = json_string_value(json_object_get(json_object_get(root.get(),"data"), "status")); 255 | if (status.compare("a") == 0) { 256 | return true; 257 | } else { 258 | // auto dump = json_dumps(root.get(), 0); 259 | // *params.logFile << " Position Order Not Complete: " << dump << ")\n" << endl; 260 | // free(dump); 261 | // cout << "REMAINS:" << remains << endl; 262 | return false; 263 | } 264 | 265 | }else{ 266 | 267 | unique_json root { authRequest(params, "/get_order/", options) }; 268 | auto remains = atof(json_string_value(json_object_get(root.get(), "remains"))); 269 | if (remains==0){ 270 | return true; 271 | } else { 272 | auto dump = json_dumps(root.get(), 0); 273 | *params.logFile << " Order Not Complete: " << dump << ")\n" << endl; 274 | free(dump); 275 | // cout << "REMAINS:" << remains << endl; 276 | return false; 277 | } 278 | 279 | } 280 | 281 | } 282 | 283 | double getActivePos(Parameters& params) { return getAvail(params, "btc"); } 284 | 285 | double getLimitPrice(Parameters ¶ms, double volume, bool isBid) 286 | { 287 | auto &exchange = queryHandle(params); 288 | auto root = unique_json(exchange.getRequest("/order_book/BTC/USD/")); 289 | auto branch = json_object_get(root.get(), isBid ? "bids" : "asks"); 290 | 291 | // loop on volume 292 | double totVol = 0.0; 293 | double currPrice = 0.0; 294 | double currVol = 0.0; 295 | unsigned int i = 0; 296 | // // [[, ], [, ], ...] 297 | for(i = 0; i < (json_array_size(branch)); i++) 298 | { 299 | // volumes are added up until the requested volume is reached 300 | currVol = json_number_value(json_array_get(json_array_get(branch, i), 1)); 301 | currPrice = json_number_value(json_array_get(json_array_get(branch, i), 0)); 302 | totVol += currVol; 303 | if(totVol >= volume * params.orderBookFactor){ 304 | break; 305 | } 306 | } 307 | 308 | return currPrice; 309 | } 310 | 311 | json_t* authRequest(Parameters ¶ms, std::string request, std::string options) 312 | { 313 | using namespace std; 314 | static uint64_t nonce = time(nullptr) * 4; 315 | auto msg = to_string(++nonce) + params.cexioClientId + params.cexioApi; 316 | uint8_t *digest = HMAC (EVP_sha256(), 317 | params.cexioSecret.c_str(), params.cexioSecret.size(), 318 | reinterpret_cast(msg.data()), msg.size(), 319 | nullptr, nullptr); 320 | 321 | string postParams = "key=" + params.cexioApi + 322 | "&signature=" + hex_str(digest, digest + SHA256_DIGEST_LENGTH) + 323 | "&nonce=" + to_string(nonce); 324 | 325 | if (!options.empty()) 326 | { 327 | postParams += "&"; 328 | postParams += options; 329 | } 330 | 331 | auto &exchange = queryHandle(params); 332 | return checkResponse(*params.logFile, exchange.postRequest(request, postParams)); 333 | } 334 | 335 | void testCexio() { 336 | using namespace std; 337 | Parameters params("blackbird.conf"); 338 | params.logFile = new ofstream("./test.log" , ofstream::trunc); 339 | 340 | string orderId; 341 | 342 | cout << "Current value BTC_USD bid: " << getQuote(params).bid() << endl; 343 | cout << "Current value BTC_USD ask: " << getQuote(params).ask() << endl; 344 | cout << "Current balance BTC: " << getAvail(params, "BTC") << endl; 345 | cout << "Current balance BCH: " << getAvail(params, "BCH") << endl; 346 | cout << "Current balance ETH: " << getAvail(params, "ETH") << endl; 347 | cout << "Current balance LTC: " << getAvail(params, "LTC") << endl; 348 | cout << "Current balance DASH: " << getAvail(params, "DASH") << endl; 349 | cout << "Current balance ZEC: " << getAvail(params, "ZEC") << endl; 350 | cout << "Current balance USD: " << getAvail(params, "USD") << endl; 351 | cout << "Current balance EUR: " << getAvail(params, "EUR")<< endl; 352 | cout << "Current balance GBP: " << getAvail(params, "GBP") << endl; 353 | cout << "Current balance RUB: " << getAvail(params, "RUB") << endl; 354 | cout << "Current balance GHS: " << getAvail(params, "GHS") << endl; 355 | cout << "Current bid limit price for 10 units: " << getLimitPrice(params, 10.0, true) << endl; 356 | cout << "Current ask limit price for 10 units: " << getLimitPrice(params, 10.0, false) << endl; 357 | 358 | // cout << "Sending buy order - TXID: " ; 359 | // orderId = sendLongOrder(params, "buy", 0.002, 9510); 360 | // cout << orderId << endl; 361 | // cout << "Buy order is complete: " << isOrderComplete(params, orderId) << endl; 362 | // cout << "Active positions (BTC): " << getActivePos(params) << endl; 363 | 364 | // cout << "Sending sell order - TXID: " ; 365 | // orderId = sendLongOrder(params, "sell", 0.002, 8800); 366 | // cout << orderId << endl; 367 | // cout << "Sell order is complete: " << isOrderComplete(params, orderId) << endl; 368 | 369 | // cout << "Active positions (BTC): " << getActivePos(params) << endl; 370 | 371 | // cout << "Sending short order - TXID: " ; 372 | // orderId = sendShortOrder(params, "sell", 0.002, 8800); 373 | // cout << orderId << endl; 374 | // cout << "Sell order is complete: " << isOrderComplete(params, orderId) << endl; 375 | 376 | // cout << "Active positions: " << getActivePos(params) << endl; 377 | 378 | /******* Cancel order functionality - for testing *******/ 379 | // long oId = 0; //Add orderId here 380 | // ostringstream oss; 381 | // oss << "id=" << oId; 382 | // string options = oss.str(); 383 | 384 | // unique_json root { authRequest(params, "/cancel_order/", options) }; 385 | // auto dump = json_dumps(root.get(), 0); 386 | // *params.logFile << " Error canceling order: " << dump << ")\n" << endl; 387 | // free(dump); 388 | /*********************************************************/ 389 | } 390 | 391 | } 392 | --------------------------------------------------------------------------------