• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ctubio / Krypto-trading-bot / 10945627037

19 Sep 2024 05:05PM UTC coverage: 41.429% (+1.2%) from 40.241%
10945627037

push

github

ctubio
Fixed gcov version mismatch.

████ ███  To request new features or in case this commit breaks something for you,
████ ███  please, create a new github issue with all possible information for me,
▓███▀█▄   but never share your API Keys!
▒▓██ ███
░▒▓█ ███  Signed-off-by: Carles Tubio <ctubio@users.noreply.github.com>
 _________________________________________
/ Hello, WORLD!                           \
|                                         |
\ pssst.. 1.00000000 BTC = 57114.74 EUR.  /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

1861 of 4492 relevant lines covered (41.43%)

12.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.83
/src/lib/Krypto.ninja-apis.h
1
//! \file
2
//! \brief Exchange API integrations.
3

4
namespace â‚¿ {
5
  enum class Connectivity: unsigned int { Disconnected, Connected };
6
  enum class       Status: unsigned int { Waiting, Working, Terminated };
7
  enum class         Side: unsigned int { Bid, Ask };
8
  enum class  TimeInForce: unsigned int { GTC, IOC, FOK };
9
  enum class    OrderType: unsigned int { Limit, Market };
10
  enum class       Future: unsigned int { Spot, Inverse, Linear };
11

12
  struct Level {
29✔
13
     Price price = 0;
14
    Amount size  = 0;
15
  };
16
  static void to_json(json &j, const Level &k) {
45✔
17
    j = {
18
      {"price", k.price}
45✔
19
    };
180✔
20
    if (k.size) j["size"] = k.size;
43✔
21
  };
223✔
22
  struct Levels {
23
    vector<Level> bids,
24
                  asks;
25
    static Levels reduce(const size_t &max, Levels levels) {
26
      if (max) {
27
        if (levels.bids.size() > max)
28
          levels.bids.erase(levels.bids.begin() + max, levels.bids.end());
29
        if (levels.asks.size() > max)
30
          levels.asks.erase(levels.asks.begin() + max, levels.asks.end());
31
      }
32
      return levels;
33
    };
34
    static void update(const Side &side, const Price &price, const Amount &size, Levels *const levels) {
35
      vector<Level> *const level = side == Side::Bid
36
                                 ? &levels->bids
37
                                 : &levels->asks;
38
      auto it = find_if(
39
        level->begin(), level->end(),
40
        [&](const Level &it) { return price == it.price; }
41
      );
42
      if (it == level->end()) {
43
        if (size)
44
          level->insert(
45
            side == Side::Bid
46
              ? find_if(
47
                  level->begin(), level->end(),
48
                  [&](const Level &it) { return price > it.price; }
49
                )
50
              : find_if(
51
                  level->begin(), level->end(),
52
                  [&](const Level &it) { return price < it.price; }
53
                ),
54
            {price, size}
55
          );
56
      } else if (size)
57
        it->size = size;
58
      else level->erase(it);
59
    };
60
  };
61
  static void __attribute__ ((unused)) to_json(json &j, const Levels &k) {
10✔
62
    j = {
63
      {"bids", k.bids},
10✔
64
      {"asks", k.asks}
10✔
65
    };
70✔
66
  };
70✔
67

68
  struct Ticker {
69
    string base   = "";
70
    string quote  = "";
71
     Price price  = 0,
72
           spread = 0;
73
    double open   = 0;
74
    Amount volume = 0;
75
  };
76
  static void __attribute__ ((unused)) to_json(json &j, const Ticker &k) {
77
    j = {
78
      {  "base", k.base  },
79
      { "quote", k.quote },
80
      { "price", k.price },
81
      {"spread", k.spread},
82
      {  "open", k.open  },
83
      {"volume", k.volume}
84
    };
85
  };
86

87
  struct Wallet {
83✔
88
    Amount amount   = 0,
89
           held     = 0;
90
    string currency = "";
91
    Amount total    = 0,
92
           value    = 0;
93
    double profit   = 0;
94
    Wallet &operator=(const Wallet &raw) {
95
      total = (amount = raw.amount)
54✔
96
            + (held   = raw.held);
54✔
97
      currency = raw.currency;
54✔
98
      return *this;
54✔
99
    };
100
  };
101
  static void __attribute__ ((unused)) to_json(json &j, const Wallet &k) {
14✔
102
    j = {
103
      {  "amount", k.amount  },
14✔
104
      {    "held", k.held    },
14✔
105
      {"currency", k.currency},
14✔
106
      {   "value", k.value   },
14✔
107
      {  "profit", k.profit  }
14✔
108
    };
224✔
109
  };
224✔
110

111
  struct Trade {
112
      Side side     = (Side)0;
113
     Price price    = 0;
114
    Amount quantity = 0;
115
     Clock time     = 0;
116
  };
117
  static void __attribute__ ((unused)) to_json(json &j, const Trade &k) {
×
118
    j = {
119
      {    "side", k.side    },
×
120
      {   "price", k.price   },
×
121
      {"quantity", k.quantity},
×
122
      {    "time", k.time    }
×
123
    };
×
124
  };
×
125

126
  struct Order {
166✔
127
           Side side        = (Side)0;
128
          Price price       = 0;
129
         Amount quantity    = 0;
130
          Clock time        = 0;
131
           bool isPong      = false;
132
         string orderId     = "",
133
                exchangeId  = "";
134
         Status status      = (Status)0;
135
         Amount qtyFilled   = 0;
136
      OrderType type        = (OrderType)0;
137
    TimeInForce timeInForce = (TimeInForce)0;
138
           bool manual      = false;
139
          Clock latency     = 0;
140
    static Order *update(const Order &raw, Order *const order) {
57✔
141
      if (order) {
57✔
142
        if (Status::Working == (     order->status     = raw.status
52✔
143
        ) and !order->latency)       order->latency    = raw.time - order->time;
52✔
144
        order->time         = raw.time;
52✔
145
        if (!raw.exchangeId.empty()) order->exchangeId = raw.exchangeId;
52✔
146
        if (raw.price)               order->price      = raw.price;
52✔
147
        if (raw.quantity)            order->quantity   = raw.quantity;
52✔
148
        if (raw.qtyFilled)           order->qtyFilled  = raw.qtyFilled;
52✔
149
      }
150
      return order;
57✔
151
    };
152
    static bool replace(const Price &price, const bool &isPong, Order *const order) {
153
      if (!order
154
        or order->exchangeId.empty()
×
155
      ) return false;
156
      order->price  = price;
×
157
      order->isPong = isPong;
×
158
      order->time   = Tstamp;
×
159
      return true;
160
    };
161
    static bool cancel(Order *const order) {
×
162
      if (!order
163
        or order->exchangeId.empty()
×
164
        or order->status == Status::Waiting
×
165
      ) return false;
166
      order->status = Status::Waiting;
×
167
      order->time   = Tstamp;
×
168
      return true;
×
169
    };
170
  };
171
  static void __attribute__ ((unused)) to_json(json &j, const Order &k) {
35✔
172
    j = {
173
      {    "orderId", k.orderId    },
35✔
174
      { "exchangeId", k.exchangeId },
35✔
175
      {       "side", k.side       },
35✔
176
      {   "quantity", k.quantity   },
35✔
177
      {       "type", k.type       },
35✔
178
      {     "isPong", k.isPong     },
35✔
179
      {      "price", k.price      },
35✔
180
      {"timeInForce", k.timeInForce},
35✔
181
      {     "status", k.status     },
35✔
182
      {       "time", k.time       },
35✔
183
      {    "latency", k.latency    }
35✔
184
    };
1,190✔
185
  };
1,190✔
186
  static void __attribute__ ((unused)) from_json(const json &j, Order &k) {
×
187
    k.orderId     = j.value("orderId", "");
×
188
    k.price       = j.value("price", 0.0);
×
189
    k.quantity    = j.value("quantity", 0.0);
×
190
    k.time        = j.value("time", Tstamp);
×
191
    k.side        = j.value("side", k.side);
×
192
    k.type        = j.value("type", k.type);
×
193
    k.timeInForce = j.value("timeInForce", k.timeInForce);
×
194
    k.manual      = j.value("manual", false);
×
195
  };
×
196

197
  class GwExchangeData {
198
    public_friend:
199
      using DataEvent = variant<
200
        function<void(const Connectivity&)>,
201
        function<void(const Ticker&)>,
202
        function<void(const Wallet&)>,
203
        function<void(const Levels&)>,
204
        function<void(const Order&)>,
205
        function<void(const Trade&)>
206
      >;
207
    public:
208
      mutex *guard = nullptr;
209
      curl_socket_t loopfd = 0;
210
      struct {
211
        Decimal funds,
212
                price,
213
                amount,
214
                percent;
215
      } decimal;
216
      bool askForReplace = false;
217
      bool askForBalance = false;
218
      string (*randId)() = nullptr;
219
    protected:
220
      struct {
221
        Loop::Async::Event<Connectivity> connectivity;
222
        Loop::Async::Event<Ticker>       ticker;
223
        Loop::Async::Event<Wallet>       wallet;
224
        Loop::Async::Event<Levels>       levels;
225
        Loop::Async::Event<Order>        orders;
226
        Loop::Async::Event<Trade>        trades;
227
      } async;
228
    public:
229
      virtual void ask_for_data(const unsigned int &tick) = 0;
230
      virtual void wait_for_data(Loop *const loop) = 0;
231
      void data(const DataEvent &ev) {
×
232
        if (holds_alternative            <function<void(const Connectivity&)>>(ev))
×
233
          async.connectivity.callback(get<function<void(const Connectivity&)>>(ev));
×
234
        else if (holds_alternative <function<void(const Ticker&)>>(ev))
235
          async.ticker.callback(get<function<void(const Ticker&)>>(ev));
×
236
        else if (holds_alternative <function<void(const Wallet&)>>(ev))
237
          async.wallet.callback(get<function<void(const Wallet&)>>(ev));
×
238
        else if (holds_alternative <function<void(const Levels&)>>(ev))
239
          async.levels.callback(get<function<void(const Levels&)>>(ev));
×
240
        else if (holds_alternative <function<void(const Order&)>>(ev))
241
          async.orders.callback(get<function<void(const Order&)>>(ev));
×
242
        else if (holds_alternative <function<void(const Trade&)>>(ev))
243
          async.trades.callback(get<function<void(const Trade&)>>(ev));
×
244
      };
×
245
      void place(const Order *const order) {
×
246
        place(
×
247
          order->orderId,
248
          order->side,
×
249
          decimal.price.str(order->price),
×
250
          decimal.amount.str(order->quantity),
×
251
          order->type,
×
252
          order->timeInForce
×
253
        );
254
      };
×
255
      void replace(const Order *const order) {
×
256
        replace(
×
257
          order->exchangeId,
258
          decimal.price.str(order->price)
×
259
        );
260
      };
×
261
      void cancel(const Order *const order) {
×
262
        cancel(
×
263
          order->orderId,
264
          order->exchangeId
265
        );
266
      };
×
267
      void balance() {
×
268
        askForBalance = false;
×
269
        if (!async_wallet())
×
270
          async.wallet.ask_for();
×
271
      };
×
272
//BO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions below)...
273
/**/  virtual void   place(string, Side, string, string, OrderType, TimeInForce) = 0;// call async order  data from exchange.
274
/**/  virtual void replace(string, string) {};                                 // call price async order  data from exchange.
×
275
/**/  virtual void  cancel(string, string) = 0;                              // call by uuid async order  data from exchange.
276
/**/  virtual void  cancel() = 0;                                          // call by symbol async orders data from exchange.
277
/**/protected:
278
/**/  virtual           bool async_wallet() { return false; };         // call               async wallet data from exchange.
×
279
/**/  virtual vector<Wallet>  sync_wallet() { return {}; };          // call                  sync wallet data from exchange.
×
280
//EO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions above)...
281
      virtual void online(const Connectivity &connectivity) {
×
282
        async.connectivity.try_write(connectivity);
283
        if (!(bool)connectivity)
×
284
          async.levels.try_write({});
×
285
      };
×
286
      void wait_for_never_async_data(Loop *const loop) {
×
287
        async.wallet.wait_for(loop, [&]() { return sync_wallet(); });
×
288
      };
×
289
      void ask_for_never_async_data(const unsigned int &tick) {
×
290
        if (async.wallet.waiting() and (
×
291
          askForBalance or !(tick % 15)
×
292
        )) balance();
×
293
      };
×
294
  };
295

296
  class GwExchange: public GwExchangeData {
297
    public:
298
      using Report = vector<pair<string, string>>;
299
      string exchange, apikey, secret, apikeyid,
300
             base,     quote,  symbol,
301
             http,     ws,
302
             unlock;
303
       Price tickPrice = 0;
304
      Amount tickSize  = 0,
305
             minSize   = 0,
306
             minValue  = 0,
307
             makeFee   = 0,
308
             takeFee   = 0;
309
         int maxLevel  = 0;
310
      double leverage  = 0;
311
      Future margin    = (Future)0;
312
        bool debug     = false;
313
      Connectivity adminAgreement = Connectivity::Disconnected;
314
      json handshake(const bool &nocache) {
×
315
        json reply;
×
316
        const string cache = (K_HOME "/cache/handshake")
317
          + ('.' + exchange)
×
318
          +  '.' + base
×
319
          +  '.' + quote
×
320
          +  '.' + "json";
×
321
        fstream file;
×
322
        struct stat st;
323
        if (!nocache
×
324
          and access(cache.data(), R_OK) != -1
×
325
          and !stat(cache.data(), &st)
×
326
          and Tstamp - 25200e+3 < st.st_mtime * 1e+3
×
327
        ) {
328
          file.open(cache, fstream::in);
×
329
          reply = json::parse(file);
×
330
        } else
331
          reply = handshake();
×
332
        base      = reply.value("base",      base);
×
333
        quote     = reply.value("quote",     quote);
×
334
        symbol    = reply.value("symbol",    symbol);
×
335
        margin    = reply.value("margin",    margin);
×
336
        tickPrice = reply.value("tickPrice", 0.0);
×
337
        tickSize  = reply.value("tickSize",  0.0);
×
338
        minValue  = reply.value("minValue",  0.0);
×
339
        if (!minSize) minSize = reply.value("minSize", 0.0);
×
340
        if (!makeFee) makeFee = reply.value("makeFee", 0.0);
×
341
        if (!takeFee) takeFee = reply.value("takeFee", 0.0);
×
342
        decimal.funds.precision(1e-8);
×
343
        decimal.price.precision(tickPrice);
×
344
        decimal.amount.precision(tickSize);
×
345
        decimal.percent.precision(1e-2);
×
346
        if (!file.is_open()
347
          and tickPrice and tickSize and minSize
×
348
          and !base.empty() and !quote.empty()
×
349
        ) {
350
          file.open(cache, fstream::out | fstream::trunc);
×
351
          file << reply.dump();
×
352
        }
353
        if (file.is_open()) file.close();
×
354
        return reply.value("reply", json::object());
×
355
      };
×
356
      void end() {
357
        online(Connectivity::Disconnected);
×
358
        disconnect();
×
359
      };
360
      void report(Report notes, const bool &nocache) {
×
361
        for (const auto &it : (Report){
×
362
          {"symbols", (margin == Future::Linear
×
363
                        ? symbol             + " (" + decimal.funds.str(decimal.funds.step)
×
364
                        : base + "/" + quote + " (" + decimal.amount.str(tickSize)
×
365
                      ) + "/"
×
366
                        + decimal.price.str(tickPrice) + ")"                                  },
×
367
          {"minSize", decimal.amount.str(minSize) + " " + (
×
368
                        margin == Future::Spot
×
369
                          ? base
×
370
                          : "Contract" + string(minSize != 1, 's')
×
371
                      ) + (minValue ? " or " + decimal.price.str(minValue) + " " + quote : "")},
×
372
          {"makeFee", decimal.percent.str(makeFee * 1e+2) + "%"                               },
×
373
          {"takeFee", decimal.percent.str(takeFee * 1e+2) + "%"                               }
×
374
        }) notes.push_back(it);
×
375
        string note = "handshake:";
×
376
        for (const auto &it : notes)
×
377
          if (!it.second.empty())
×
378
            note += ANSI_NEW_LINE "- " + it.first + ": " + it.second;
×
379
        print((nocache ? "" : "cached ") + note);
×
380
      };
×
381
      string latency(const function<void()> &fn) {
×
382
        print("latency check", "start");
×
383
        const Clock Tstart = Tstamp;
×
384
        fn();
385
        const Clock Tstop  = Tstamp;
×
386
        print("latency check", "stop");
×
387
        const unsigned int Tdiff = Tstop - Tstart;
×
388
        print("HTTP read/write handshake took", to_string(Tdiff) + "ms of your time");
×
389
        string result = "This result is ";
×
390
        if      (Tdiff < 2e+2) result += "very good; most traders don't enjoy such speed!";
×
391
        else if (Tdiff < 5e+2) result += "good; most traders get the same result";
×
392
        else if (Tdiff < 7e+2) result += "a bit bad; most traders get better results";
×
393
        else if (Tdiff < 1e+3) result += "bad; consider moving to another server/network";
×
394
        else                   result += "very bad; move to another server/network";
395
        print(result);
×
396
        return "--latency of 1 HTTP call done (consider to repeat a few times this check)";
×
397
      };
398
      string pairs() const {
×
399
        string report;
400
        pairs(report);
×
401
        print("allows " + to_string(count(report.begin(), report.end(), '\n')) + " currency pairs");
×
402
        cout << report;
403
        return "--list done (to find a symbol use grep)";
×
404
      };
405
      virtual string web(const string&, const string&) const = 0;
406
      string web(const bool &orders = false) const {
×
407
        return orders ? webOrders : web(base, quote);
×
408
      };
409
      void disclaimer() const {
×
410
        if (unlock.empty()) return;
×
411
        print("was slowdown 121 seconds (--free-version argument was implicitly set):"
×
412
          ANSI_NEW_LINE ANSI_NEW_LINE "Current apikey: " + apikey.substr(0, apikey.length() / 2)
×
413
                                       + string(apikey.length() / 2, '#') +
×
414
          ANSI_NEW_LINE ANSI_NEW_LINE "To unlock it anonymously and to collaborate with"
415
          ANSI_NEW_LINE                "the development, make an acceptable Pull Request"
416
          ANSI_NEW_LINE                "on github.. or send 0.00121000 BTC (or more) to:"
417
          ANSI_NEW_LINE ANSI_NEW_LINE "  " + unlock +
×
418
          ANSI_NEW_LINE ANSI_NEW_LINE "Before restart, wait for zero (0) confirmations:"
419
          ANSI_NEW_LINE ANSI_NEW_LINE "https://live.blockcypher.com/btc/address/" + unlock +
×
420
          ANSI_NEW_LINE ANSI_NEW_LINE OBLIGATORY_analpaper_SOFTWARE_LICENSE
421
          ANSI_NEW_LINE ANSI_NEW_LINE "                     Signed-off-by: Carles Tubio"
422
          ANSI_NEW_LINE                "see: github.com/ctubio/Krypto-trading-bot#unlock"
423
          ANSI_NEW_LINE                "or just use --free-version to hide this message"
424
        );
425
      };
426
      function<void(const string&, const string&, const string&)> printer;
427
      void print(const string &reason, const string &highlight = "") const {
×
428
        if (printer) printer(
×
429
          string(reason.find(">>>") != reason.find("<<<")
×
430
            ? "DEBUG "
431
            : "GW "
432
          ) + exchange,
×
433
          reason,
434
          highlight
435
        );
436
      };
×
437
    protected:
438
      string webMarket,
439
             webOrders;
440
      virtual   void disconnect()   = 0;
441
      virtual   bool connected()    const = 0;
442
      virtual   json handshake()    const = 0;
443
      virtual   void pairs(string&) const = 0;
444
      virtual string nonce()        const = 0;
445
      void online(const Connectivity &connectivity) override {
×
446
        print("network state changed to", string((bool)connectivity
×
447
          ? ""
448
          : "DIS"
449
        ) + "CONNECTED");
×
450
        GwExchangeData::online(connectivity);
×
451
      };
×
452
  };
453

454
  class Gw: public GwExchange {
455
    public:
456
//BO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions below).
457
/**/  static Gw* new_Gw(const string&); // may return too a nullptr instead of a child gateway class, if string is unknown.
458
//EO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions above).
459
  };
460

461
  class GwApiWs: public Gw,
462
                 public Curl::WebSocket {
463
    public:
464
      GwApiWs()
465
        : WebSocket(guard)
466
      {};
467
    private:
468
       unsigned int countdown    = 1;
469
               bool subscription = false;
470
    public:
471
      void ask_for_data(const unsigned int &tick) override {
×
472
        if (countdown and !--countdown)
×
473
          connect();
×
474
        if (subscribed())
×
475
          ask_for_never_async_data(tick);
×
476
      };
×
477
      void wait_for_data(Loop *const loop) override {
×
478
        wait_for_never_async_data(loop);
×
479
      };
×
480
    protected:
481
//BO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions below).
482
/**/  virtual string subscribe() = 0;               // send subcription messages to remote server and return channel names.
483
/**/  virtual void consume(json) = 0;               // read message one by one from remote server and call async observers.
484
//EO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions above).
485
      bool connected() const override {
×
486
        return WebSocket::connected();
×
487
      };
488
      virtual void connect() {
×
489
        CURLcode rc;
490
        if (CURLE_OK == (rc = WebSocket::connect(ws)))
×
491
          WebSocket::start(GwExchangeData::loopfd, [&]() {
×
492
            wait_for_async_data();
×
493
          });
494
        else reconnect(string("CURL connect Error: ") + curl_easy_strerror(rc));
×
495
      };
×
496
      void emit(const string &msg) {
497
        CURLcode rc;
498
        if (CURLE_OK != (rc = WebSocket::emit(msg, 0x01)))
499
          print(string("CURL send Error: ") + curl_easy_strerror(rc));
500
      };
501
      void disconnect() override {
×
502
        WebSocket::emit("", 0x08);
×
503
        WebSocket::cleanup();
×
504
      };
×
505
      void reconnect(const string &reason) {
×
506
        disconnect();
×
507
        countdown = 7;
×
508
        print("WS " + reason + ", reconnecting in " + to_string(countdown) + "s.");
×
509
      };
×
510
      bool subscribed() {
×
511
        if (subscription != connected()) {
×
512
          subscription = !subscription;
×
513
          if (subscription) print("WS streaming [" + subscribe() + "]");
×
514
          else reconnect("Disconnected");
×
515
          online((Connectivity)subscription);
×
516
        }
517
        return subscription;
×
518
      };
519
      bool accept_msg(const string &msg) {
×
520
        const bool next = !msg.empty();
×
521
        if (next) {
×
522
          if (json::accept(msg))
×
523
            consume(json::parse(msg));
×
524
          else print("WS Error: Unsupported data format");
×
525
        }
526
        return next;
×
527
      };
528
    private:
529
      void wait_for_async_data() {
×
530
        CURLcode rc;
531
        if (CURLE_OK != (rc = WebSocket::send_recv()))
×
532
          print(string("CURL recv Error: ") + curl_easy_strerror(rc));
×
533
        while (accept_msg(WebSocket::unframe()));
×
534
      };
×
535
  };
536
  class GwApiWsWs: public GwApiWs,
537
                   public Curl::WebSocketTwin {
538
    public:
539
      GwApiWsWs()
540
        : WebSocketTwin(guard)
541
      {};
542
    protected:
543
      bool connected() const override {
×
544
        return GwApiWs::connected()
545
           and WebSocketTwin::connected();
×
546
      };
547
      void connect() override {
×
548
        GwApiWs::connect();
×
549
        if (GwApiWs::connected()) {
×
550
          CURLcode rc;
551
          if (CURLE_OK == (rc = WebSocketTwin::connect(twin(ws))))
×
552
            WebSocketTwin::start(GwExchangeData::loopfd, [&]() {
×
553
              wait_for_async_data();
×
554
            });
555
          else reconnect(string("CURL connect Error: ") + curl_easy_strerror(rc));
×
556
        }
557
      };
×
558
      void disconnect() override {
×
559
        WebSocketTwin::emit("", 0x08);
×
560
        WebSocketTwin::cleanup();
×
561
        GwApiWs::disconnect();
×
562
      };
×
563
      void emit(const string &msg) {
564
        GwApiWs::emit(msg);
565
      };
566
      void beam(const string &msg) {
567
        CURLcode rc;
568
        if (CURLE_OK != (rc = WebSocketTwin::emit(msg, 0x01)))
569
          print(string("CURL send Error: ") + curl_easy_strerror(rc));
570
      };
571
    private:
572
      void wait_for_async_data() {
×
573
        CURLcode rc;
574
        if (CURLE_OK != (rc = WebSocketTwin::send_recv()))
×
575
          print(string("CURL recv Error: ") + curl_easy_strerror(rc));
×
576
        while (accept_msg(WebSocketTwin::unframe()));
×
577
      };
×
578
  };
579

580
class GwApiWsFix: public GwApiWs,
581
                  public Curl::FixSocket {
582
    public:
583
      GwApiWsFix(const string &t)
584
        : FixSocket(t, apikey, guard)
585
      {};
586
    private:
587
       string fix;
588
    protected:
589
      bool connected() const override {
×
590
        return GwApiWs::connected()
591
           and FixSocket::connected();
×
592
      };
593
//BO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions below).
594
/**/  virtual string logon() = 0;                                                                  // return logon message.
595
//EO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions above).
596
      void connect() override {
×
597
        GwApiWs::connect();
×
598
        if (GwApiWs::connected()) {
×
599
          CURLcode rc;
600
          if (CURLE_OK == (rc = FixSocket::connect(fix, logon()))) {
×
601
            FixSocket::start(GwExchangeData::loopfd, [&]() {
×
602
              wait_for_async_data();
×
603
            });
604
            print("FIX streaming [orders]");
×
605
          } else reconnect(string("CURL connect FIX Error: ") + curl_easy_strerror(rc));
×
606
        }
607
      };
×
608
      void disconnect() override {
×
609
        if (FixSocket::connected()) print("FIX Logout");
×
610
        FixSocket::emit("", "5");
×
611
        FixSocket::cleanup();
×
612
        GwApiWs::disconnect();
×
613
      };
×
614
      void beam(const string &msg, const string &type) {
615
        CURLcode rc;
616
        if (CURLE_OK != (rc = FixSocket::emit(msg, type)))
617
          print(string("CURL send FIX Error: ") + curl_easy_strerror(rc));
618
      };
619
    private:
620
      void wait_for_async_data() {
×
621
        CURLcode rc;
622
        if (CURLE_OK != (rc = FixSocket::send_recv()))
×
623
          print(string("CURL recv FIX Error: ") + curl_easy_strerror(rc));
×
624
        while (accept_msg(FixSocket::unframe()));
×
625
      };
×
626
  };
627

628
  class GwBinance: public GwApiWs {
629
    public:
630
      GwBinance()
631
      {
632
        http   = "https://api.binance.com";
633
        ws     = "wss://stream.binance.com:9443/ws";
634
        randId = Random::uuid36Id;
635
        webMarket = "https://www.binance.com/en/trade/";
636
        webOrders = "https://www.binance.com/en/my/orders/exchange/tradeorder";
637
      };
638
      string web(const string &base, const string &quote) const {
×
639
        return webMarket + base + "_" + quote + "?layout=pro";
×
640
      };
641
    protected:
642
      string nonce() const override {
×
643
        return to_string(Tstamp);
×
644
      };
645
      void pairs(string &report) const override {
×
646
        const json reply = Curl::Web::xfer(*guard, http + "/api/v3/exchangeInfo");
×
647
        if (!reply.is_object()
648
          or !reply.contains("symbols")
649
          or !reply.at("symbols").is_array()
×
650
          or reply.at("symbols").empty()
×
651
          or !reply.at("symbols").at(0).is_object()
×
652
          or !reply.at("symbols").at(0).contains("isSpotTradingAllowed")
×
653
        ) print("Error while reading pairs: " + reply.dump());
×
654
        else for (const json &it : reply.at("symbols"))
×
655
          if (it.value("isSpotTradingAllowed", false)
×
656
            and it.value("status", "") == "TRADING"
×
657
          ) report += it.value("baseAsset", "") + "/" + it.value("quoteAsset", "") + ANSI_NEW_LINE;
×
658
      };
×
659
      json handshake() const override {
×
660
        json reply1 = Curl::Web::xfer(*guard, http + "/api/v3/exchangeInfo");
×
661
        if (reply1.contains("symbols") and reply1.at("symbols").is_array())
×
662
          for (const json &it : reply1.at("symbols"))
×
663
            if (it.value("symbol", "") == base + quote) {
×
664
              reply1 = it;
×
665
              if (reply1.contains("filters") and reply1.at("filters").is_array())
×
666
                for (const json &it_ : reply1.at("filters")) {
×
667
                  if (it_.value("filterType", "") == "PRICE_FILTER")
×
668
                    reply1["tickPrice"] = stod(it_.value("tickSize", "0"));
×
669
                  else if (it_.value("filterType", "") == "MIN_NOTIONAL")
×
670
                    reply1["minValue"] = stod(it_.value("minNotional", "0"));
×
671
                  else if (it_.value("filterType", "") == "LOT_SIZE") {
×
672
                    reply1["tickSize"] = stod(it_.value("stepSize", "0"));
×
673
                    reply1["minSize"] = stod(it_.value("minQty", "0"));
×
674
                  }
675
                }
676
              break;
677
            }
678
        const json reply2 = fees();
×
679
        return {
680
          {     "base", base                                      },
×
681
          {    "quote", quote                                     },
×
682
          {   "symbol", base + quote                              },
×
683
          {"tickPrice", reply1.value("tickPrice", 0.0)            },
×
684
          { "tickSize", reply1.value("tickSize", 0.0)             },
×
685
          {  "minSize", reply1.value("minSize", 0.0)              },
×
686
          { "minValue", reply1.value("minValue", 0.0)             },
×
687
          {  "makeFee", stod(reply2.value("makerCommission", "0"))},
×
688
          {  "takeFee", stod(reply2.value("takerCommission", "0"))},
×
689
          {    "reply", {reply1, reply2}                          }
690
        };
×
691
      };
×
692
      json xfer(const string &url, const string &h1, const string &crud) const {
×
693
        return Curl::Web::xfer(*guard, url, crud, "", {
×
694
          "X-MBX-APIKEY: " + h1
695
        });
×
696
      };
×
697
    private:
698
      json fees() const {
×
699
        const string crud = "GET",
×
700
                     path = "/sapi/v1/asset/tradeFee?",
×
701
                     post = "symbol="     + base + quote
×
702
                          + "&timestamp=" + nonce(),
×
703
                     sign = "&signature=" + Text::HMAC256(post, secret);
×
704
        const json reply = xfer(http + path + post + sign, apikey, crud);
×
705
        if (!reply.is_array()
×
706
          or reply.empty()
×
707
          or !reply.at(0).is_object()
×
708
          or !reply.at(0).contains("symbol")
×
709
          or !reply.at(0).at("symbol").is_string()
×
710
          or reply.at(0).value("symbol", "") != base + quote
×
711
        ) {
712
          print("Error while reading fees: " + reply.dump());
×
713
          return json::object();
×
714
        }
715
        return reply.at(0);
×
716
      };
×
717
  };
718
  class GwBitmex: public GwApiWs {
719
    public:
720
      GwBitmex()
721
      {
722
        http   = "https://www.bitmex.com/api/v1";
723
        ws     = "wss://www.bitmex.com/realtime";
724
        randId = Random::uuid36Id;
725
        askForReplace = true;
726
        webMarket = "https://www.bitmex.com/app/trade/";
727
        webOrders = "https://www.bitmex.com/app/orderHistory";
728
      };
729
      string web(const string &base, const string &quote) const {
×
730
        return webMarket + base + quote;
×
731
      };
732
    protected:
733
      string nonce() const override {
734
        return to_string(Tstamp);
735
      };
736
      void pairs(string &report) const override {
×
737
        const json reply = Curl::Web::xfer(*guard, http + "/instrument/active");
×
738
        if (!reply.is_array()
739
          or reply.empty()
×
740
          or !reply.at(0).is_object()
×
741
          or !reply.at(0).contains("symbol")
×
742
        ) print("Error while reading pairs: " + reply.dump());
×
743
        else for (const json &it : reply)
×
744
          report += it.value("symbol", "") + ANSI_NEW_LINE;
×
745
      };
×
746
      json handshake() const override {
×
747
        json reply = {
748
          {"object", Curl::Web::xfer(*guard, http + "/instrument?symbol=" + base + quote)}
×
749
        };
×
750
        if (reply.at("object").is_array() and !reply.at("object").empty())
×
751
          reply = reply.at("object").at(0);
×
752
        return {
753
          {     "base", base                           },
×
754
          {    "quote", quote                          },
×
755
          {   "symbol", base + quote                   },
×
756
          {   "margin", reply.value("isInverse", false)
×
757
                          ? Future::Inverse
×
758
                          : Future::Linear             },
759
          {"tickPrice", reply.value("tickSize", 0.0)   },
×
760
          { "tickSize", reply.value("lotSize", 0.0)    },
×
761
          {  "minSize", reply.value("lotSize", 0.0)    },
×
762
          {  "makeFee", reply.value("makerFee", 0.0)   },
×
763
          {  "takeFee", reply.value("takerFee", 0.0)   },
×
764
          {    "reply", reply                          }
765
        };
×
766
      };
×
767
      json xfer(const string &url, const string &h1, const string &h2, const string &h3, const string &post, const string &crud) const {
768
        return Curl::Web::xfer(*guard, url, crud, post, {
769
          "api-expires: "   + h1,
770
          "api-key: "       + h2,
771
          "api-signature: " + h3
772
        });
773
      };
774
  };
775
  class GwGateio: public GwApiWs {
776
    public:
777
      GwGateio()
778
      {
779
        http   = "https://api.gateio.ws/api/v4";
780
        ws     = "wss://api.gateio.ws/ws/v4/";
781
        randId = Random::int45Id;
782
        webMarket = "https://www.gate.io/trade/";
783
        webOrders = "https://www.gate.io/myaccount/myorders";
784
      };
785
      string web(const string &base, const string &quote) const {
×
786
        return webMarket + base + "_" + quote;
×
787
      };
788
    protected:
789
      string nonce() const override {
790
        return to_string(Tstamp / 1e+3);
791
      };
792
      void pairs(string &report) const override {
×
793
        const json reply = Curl::Web::xfer(*guard, http + "/spot/currency_pairs");
×
794
        if (!reply.is_array()
795
          or reply.empty()
×
796
          or !reply.at(0).is_object()
×
797
          or !reply.at(0).contains("trade_status")
×
798
        ) print("Error while reading pairs: " + reply.dump());
×
799
        else for (const json &it : reply)
×
800
          if (it.value("trade_status", "") == "tradable")
×
801
            report += it.value("base", "") + "/" + it.value("quote", "") + ANSI_NEW_LINE;
×
802
      };
×
803
      json handshake() const override {
×
804
        json reply = {
805
          {"object", Curl::Web::xfer(*guard, http + "/spot/currency_pairs")}
×
806
        };
×
807
        if (reply.at("object").is_array() and !reply.at("object").empty())
×
808
          for (const json &it : reply.at("object"))
×
809
            if (it.value("id", "") == base + "_" + quote) {
×
810
              reply = it;
×
811
              break;
×
812
            }
813
        return {
814
          {     "base", base                                            },
×
815
          {    "quote", quote                                           },
×
816
          {   "symbol", base + "_" + quote                              },
×
817
          {"tickPrice", pow(10, -reply.value("precision", 0))           },
×
818
          { "tickSize", pow(10, -reply.value("amount_precision", 0))    },
×
819
          {  "minSize", stod(reply.value("min_base_amount", "0"))
×
820
                         ?: pow(10, -reply.value("amount_precision", 0))},
×
821
          { "minValue", stod(reply.value("min_quote_amount", "0"))
×
822
                         ?: pow(10, -reply.value("precision", 0))       },
×
823
          {  "makeFee", stod(reply.value("fee", "0")) / 1e+2            },
×
824
          {  "takeFee", stod(reply.value("fee", "0")) / 1e+2            },
×
825
          {    "reply", reply                                           }
826
        };
×
827
      };
×
828
      json xfer(const string &url, const string &h1, const string &h2, const string &h3, const string &post, const string &crud) const {
829
        return Curl::Web::xfer(*guard, url, crud, post, {
830
          "Content-Type: application/json",
831
          "KEY: "       + h1,
832
          "Timestamp: " + h2,
833
          "SIGN: "      + h3
834
        });
835
      };
836
  };
837
  class GwHitBtc: public GwApiWsWs {
838
    public:
839
      GwHitBtc()
840
      {
841
        http   = "https://api.hitbtc.com/api/2";
842
        ws     = "wss://api.hitbtc.com/api/2/ws/public";
843
        randId = Random::uuid32Id;
844
        webMarket = "https://hitbtc.com/exchange/";
845
        webOrders = "https://hitbtc.com/reports/orders";
846
      };
847
      string web(const string &base, const string &quote) const {
×
848
        return webMarket + base + "-to-" + quote;
×
849
      };
850
    protected:
851
      string nonce() const override {
852
        return randId() + randId();
853
      };
854
      void pairs(string &report) const override {
×
855
        const json reply = Curl::Web::xfer(*guard, http + "/public/symbol");
×
856
        if (!reply.is_array()
857
          or reply.empty()
×
858
          or !reply.at(0).is_object()
×
859
          or !reply.at(0).contains("baseCurrency")
×
860
          or !reply.at(0).contains("quoteCurrency")
×
861
        ) print("Error while reading pairs: " + reply.dump());
×
862
        else for (const json &it : reply)
×
863
          report += it.value("baseCurrency", "") + "/" + it.value("quoteCurrency", "") + ANSI_NEW_LINE;
×
864
      };
×
865
      json handshake() const override {
×
866
        const json reply = Curl::Web::xfer(*guard, http + "/public/symbol/" + base + quote);
×
867
        return {
868
          {     "base", base == "USDT" ? "USD" : base                 },
×
869
          {    "quote", quote == "USDT" ? "USD" : quote               },
×
870
          {   "symbol", base + quote                                  },
×
871
          {"tickPrice", stod(reply.value("tickSize", "0"))            },
×
872
          { "tickSize", stod(reply.value("quantityIncrement", "0"))   },
×
873
          {  "minSize", stod(reply.value("quantityIncrement", "0"))   },
×
874
          {  "makeFee", stod(reply.value("provideLiquidityRate", "0"))},
×
875
          {  "takeFee", stod(reply.value("takeLiquidityRate", "0"))   },
×
876
          {    "reply", reply                                         }
877
        };
×
878
      };
×
879
      string twin(const string &ws) const override {
×
880
        return ws.substr(0, ws.length() - 6) + "trading";
×
881
      };
882
      json xfer(const string &url, const string &auth, const string &post) const {
883
        return Curl::Web::xfer(*guard, url, "DELETE", post, {}, auth);
884
      };
885
  };
886
  class GwBequant: virtual public GwHitBtc {
887
    public:
888
      GwBequant()
889
      {
890
        http = "https://api.bequant.io/api/2";
891
        ws   = "wss://api.bequant.io/api/2/ws";
892
        webMarket = "https://bequant.io/exchange/";
893
        webOrders = "https://bequant.io/reports/orders";
894
      };
895
  };
896
  class GwCoinbase: public GwApiWsWs {
897
    public:
898
      GwCoinbase()
899
      {
900
        http   = "https://api.coinbase.com/api/v3/brokerage";
901
        ws     = "wss://advanced-trade-ws.coinbase.com";
902
        randId = Random::uuid36Id;
903
        webMarket = "https://www.coinbase.com/advanced-trade/spot/";
904
        webOrders = "https://www.coinbase.com/orders/";
905
      };
906
      string web(const string &base, const string &quote) const {
×
907
        return webMarket + base + "-" + quote;
×
908
      };
909
    protected:
910
//BO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions below).
911
/**/  virtual string token(const string &crud = "", const string &url = "") const = 0;             // return logon message.
912
//EO non-free Gw class member functions from lib build-*/lib/K-*.a (it just redefines all virtual gateway functions above).
913
      string nonce() const override {
914
        return Random::char16Id();
915
      };
916
      void pairs(string &report) const override {
×
917
        const json reply = xfer(http + "/products");
×
918
        if (!reply.is_object()
919
          or reply.empty()
×
920
          or !reply.contains("products")
921
          or !reply.at("products").is_array()
×
922
          or reply.at("products").empty()
×
923
          or !reply.at("products").at(0).is_object()
×
924
          or !reply.at("products").at(0).contains("base_currency_id")
×
925
          or !reply.at("products").at(0).contains("quote_currency_id")
×
926
        ) print("Error while reading pairs: " + reply.dump());
×
927
        else for (const json &it : reply.at("products"))
×
928
          if (!it.value("trading_disabled", true) and it.value("status", "") == "online")
×
929
            report += it.value("base_currency_id", "") + "/" + it.value("quote_currency_id", "") + ANSI_NEW_LINE;
×
930
      };
×
931
      json handshake() const override {
×
932
        const json reply = xfer(http + "/products/" + base + "-" + quote);
×
933
        return {
934
          {     "base", base                                     },
×
935
          {    "quote", quote                                    },
×
936
          {   "symbol", base + "-" + quote                       },
×
937
          {"tickPrice", stod(reply.value("quote_increment", "0"))},
×
938
          { "tickSize", stod(reply.value("base_increment", "0")) },
×
939
          {  "minSize", stod(reply.value("base_min_size", "0"))  },
×
940
          {    "reply", reply                                    }
941
        };
×
942
      };
×
943
      string twin(const string &ws) const override {
×
944
        return string(ws).insert(ws.find("ws.") + 2, "-user");
×
945
      };
946
      json xfer(const string &url, const string &post = "", const string &crud = "GET") const {
×
947
        return Curl::Web::xfer(*guard, url, crud, post, {
×
948
          "Content-Type: application/json",
949
          "Authorization: Bearer " + token(crud, url)
×
950
        });
×
951
    };
×
952
  };
953
  class GwBitfinex: public GwApiWs {
954
    protected:
955
      string trading = "exchange";
956
    public:
957
      GwBitfinex()
958
      {
959
        http   = "https://api.bitfinex.com/v2";
960
        ws     = "wss://api.bitfinex.com/ws/2";
961
        randId = Random::int45Id;
962
        askForReplace = true;
963
        webMarket = "https://www.bitfinex.com/trading/";
964
        webOrders = "https://www.bitfinex.com/reports/orders";
965
      };
966
      string web(const string &base, const string &quote) const {
×
967
        return webMarket + base + quote;
×
968
      };
969
    protected:
970
      string nonce() const override {
971
        return to_string(Tstamp * 1e+3);
972
      };
973
      void pairs(string &report) const override {
×
974
        const json reply = Curl::Web::xfer(*guard, http + "/conf/pub:list:pair:" + trading);
×
975
        if (!reply.is_array()
976
          or reply.empty()
×
977
          or !reply.at(0).is_array()
×
978
          or reply.at(0).empty()
×
979
          or !reply.at(0).at(0).is_string()
×
980
        ) print("Error while reading pairs: " + reply.dump());
×
981
        else for (const json &it : reply.at(0))
×
982
          if (it.get<string>().find(":") != string::npos)
×
983
            report += it.get<string>().substr(0, it.get<string>().find(":"))  + "/"
×
984
                    + it.get<string>().substr(it.get<string>().find(":") + 1) + ANSI_NEW_LINE;
×
985
          else
986
            report += it.get<string>().substr(0, 3) + "/"
×
987
                    + it.get<string>().substr(3)    + ANSI_NEW_LINE;
×
988
      };
×
989
      json handshake() const override {
×
990
        json reply1 = {
991
          {"object", Curl::Web::xfer(*guard, http + "/ticker/t" + base + quote)}
×
992
        };
×
993
        if (reply1.at("object").is_array()
×
994
          and reply1.at("object").size() > 6
×
995
          and reply1.at("object").at(6).is_number()
×
996
        ) reply1["tickPrice"] = pow(10, fmax((int)log10(
×
997
            reply1.at("object").at(6).get<double>()
×
998
          ), -4) -4);
×
999
        json reply2 = {
1000
          {"object", Curl::Web::xfer(*guard, http + "/conf/pub:info:pair")}
×
1001
        };
×
1002
        if (reply2.at("object").is_array() and !reply2.at("object").empty())
×
1003
          for (const json &it : reply2.at("object").at(0)) {
×
1004
            if (it.at(0).is_string()
×
1005
              and it.at(0).get<string>() == base + quote
×
1006
              and it.at(1).is_array()
×
1007
              and it.at(1).size() > 3
×
1008
              and it.at(1).at(3).is_string()
×
1009
            ) {
1010
              reply2 = {
1011
                {"object", it}
1012
              };
×
1013
              reply2["minSize"] = stod(reply2.at("object").at(1).at(3).get<string>());
×
1014
              break;
×
1015
            }
1016
          }
1017
        return {
1018
          {     "base", base                          },
×
1019
          {    "quote", quote                         },
×
1020
          {   "symbol", base + quote                  },
×
1021
          {"tickPrice", reply1.value("tickPrice", 0.0)},
×
1022
          { "tickSize", 1e-8                          },
×
1023
          {  "minSize", reply2.value("minSize", 0.0)  },
×
1024
          {    "reply", {reply1, reply2}              }
1025
        };
×
1026
      };
×
1027
      json xfer(const string &url, const string &post, const string &h1, const string &h2, const string &h3) const {
1028
        return Curl::Web::xfer(*guard, url, "GET", post, {
1029
          "Content-Type: application/json",
1030
          "bfx-apikey: "    + h1,
1031
          "bfx-nonce: "     + h2,
1032
          "bfx-signature: " + h3
1033
        });
1034
      };
1035
  };
1036
  class GwEthfinex: virtual public GwBitfinex {
1037
    public:
1038
      GwEthfinex()
1039
      {
1040
        http = "https://api.ethfinex.com/v1";
1041
        ws   = "wss://api.ethfinex.com/ws/2";
1042
        webMarket = "https://www.ethfinex.com/trading/";
1043
        webOrders = "https://www.ethfinex.com/reports/orders";
1044
      };
1045
  };
1046
  class GwKuCoin: public GwApiWs {
1047
    public:
1048
      GwKuCoin()
1049
      {
1050
        http   = "https://api.kucoin.com";
1051
        ws     = "wss://push-private.kucoin.com/endpoint";
1052
        randId = Random::uuid36Id;
1053
        webMarket = "https://trade.kucoin.com/";
1054
        webOrders = "https://www.kucoin.com/order/trade";
1055
      };
1056
      string web(const string &base, const string &quote) const {
×
1057
        return webMarket + base + "-" + quote;
×
1058
      };
1059
    protected:
1060
      string nonce() const override {
×
1061
        return to_string(Tstamp);
×
1062
      };
1063
      void pairs(string &report) const override {
×
1064
        const json reply = Curl::Web::xfer(*guard, http + "/api/v1/symbols");
×
1065
        if (!reply.is_object()
1066
          or !reply.contains("data")
1067
          or !reply.at("data").is_array()
×
1068
          or reply.at("data").empty()
×
1069
          or !reply.at("data").at(0).is_object()
×
1070
          or !reply.at("data").at(0).contains("enableTrading")
×
1071
        ) print("Error while reading pairs: " + reply.dump());
×
1072
        else for (const json &it : reply.at("data"))
×
1073
          if (it.value("enableTrading", false))
×
1074
            report += it.value("baseCurrency", "") + "/" + it.value("quoteCurrency", "") + ANSI_NEW_LINE;
×
1075
      };
×
1076
      json handshake() const override {
×
1077
        json reply1 = Curl::Web::xfer(*guard, http + "/api/v1/symbols");
×
1078
        if (reply1.contains("data") and reply1.at("data").is_array())
×
1079
          for (const json &it : reply1.at("data"))
×
1080
            if (it.value("symbol", "") == base + "-" + quote) {
×
1081
              reply1 = it;
×
1082
              break;
×
1083
            }
1084
        const json reply2 = fees();
×
1085
        return {
1086
          {     "base", base                                     },
×
1087
          {    "quote", quote                                    },
×
1088
          {   "symbol", base + "-" + quote                       },
×
1089
          {"tickPrice", stod(reply1.value("priceIncrement", "0"))},
×
1090
          { "tickSize", stod(reply1.value("baseIncrement", "0")) },
×
1091
          {  "minSize", stod(reply1.value("baseMinSize", "0"))   },
×
1092
          {  "makeFee", stod(reply2.value("makerFeeRate", "0"))  },
×
1093
          {  "takeFee", stod(reply2.value("takerFeeRate", "0"))  },
×
1094
          {    "reply", {reply1, reply2}                         }
1095
        };
×
1096
      };
×
1097
      json xfer(const string &url, const string &h1, const string &h2, const string &h3, const string &h4, const string &crud, const string &post = "") const {
×
1098
        return Curl::Web::xfer(*guard, url, crud, post, {
×
1099
          "Content-Type: application/json",
1100
          "KC-API-KEY: "        + h1,
1101
          "KC-API-SIGN: "       + h2,
1102
          "KC-API-PASSPHRASE: " + h3,
1103
          "KC-API-TIMESTAMP: "  + h4,
1104
          "KC-API-KEY-VERSION: 2"
1105
        });
×
1106
      };
×
1107
    private:
1108
      json fees() const {
×
1109
        const string crud = "GET",
×
1110
                     path = "/api/v1/base-fee",
×
1111
                     time = nonce(),
×
1112
                     hash = time + crud + path,
×
1113
                     sign = Text::B64(Text::HMAC256(hash, secret, true)),
×
1114
                     code = Text::B64(Text::HMAC256(apikeyid, secret, true));
×
1115
        const json reply = xfer(http + path, apikey, sign, code, time, crud);
×
1116
        if (!reply.contains("code")
×
1117
          or !reply.at("code").is_string()
×
1118
          or reply.value("code", "") != "200000"
×
1119
          or !reply.contains("data")
1120
          or !reply.at("data").is_object()
×
1121
        ) {
1122
          print("Error while reading fees: " + reply.dump());
×
1123
          return json::object();
×
1124
        }
1125
        return reply.at("data");
×
1126
      };
×
1127
  };
1128
  class GwKraken: public GwApiWsWs {
1129
    public:
1130
      GwKraken()
1131
      {
1132
        http   = "https://api.kraken.com";
1133
        ws     = "wss://ws.kraken.com";
1134
        randId = Random::int32Id;
1135
        webMarket = "https://trade.kraken.com/charts/KRAKEN:";
1136
        webOrders = "https://www.kraken.com/u/trade";
1137
      };
1138
      string web(const string &base, const string &quote) const {
×
1139
        return webMarket + base + "-" + quote;
×
1140
      };
1141
    protected:
1142
      string nonce() const override {
1143
        return to_string(Tstamp);
1144
      };
1145
      void pairs(string &report) const override {
×
1146
        const json reply = Curl::Web::xfer(*guard, http + "/0/public/AssetPairs");
×
1147
        if (!reply.is_object()
1148
          or !reply.contains("result")
1149
          or !reply.at("result").is_object()
×
1150
        ) print("Error while reading pairs: " + reply.dump());
×
1151
        else for (const json &it : reply.at("result"))
×
1152
          if (it.contains("wsname"))
1153
            report += it.value("wsname", "") + ANSI_NEW_LINE;
×
1154
      };
×
1155
      json handshake() const override {
×
1156
        json reply = Curl::Web::xfer(*guard, http + "/0/public/AssetPairs?pair=" + base + quote);
×
1157
        if (reply.contains("result"))
1158
          for (const json &it : reply.at("result"))
×
1159
            if (it.contains("pair_decimals")) {
1160
              reply = it;
×
1161
              break;
×
1162
            }
1163
        return {
1164
          {     "base", base                                     },
×
1165
          {    "quote", quote                                    },
×
1166
          {   "symbol", reply.value("wsname", "")                },
×
1167
          {"tickPrice", pow(10, -reply.value("pair_decimals", 0))},
×
1168
          { "tickSize", pow(10, -reply.value("lot_decimals", 0)) },
×
1169
          {  "minSize", pow(10, -reply.value("lot_decimals", 0)) },
×
1170
          {    "reply", reply                                    }
1171
        };
×
1172
      };
×
1173
      string twin(const string &ws) const override {
×
1174
        return string(ws).insert(ws.find("ws.") + 2, "-auth");
×
1175
      };
1176
      json xfer(const string &url, const string &h1, const string &h2, const string &post) const {
1177
        return Curl::Web::xfer(*guard, url, "GET", post, {
1178
          "API-Key: "  + h1,
1179
          "API-Sign: " + h2
1180
        });
1181
      };
1182
  };
1183
  class GwPoloniex: public GwApiWs {
1184
    public:
1185
      GwPoloniex()
1186
      {
1187
        http   = "https://poloniex.com";
1188
        ws     = "wss://api2.poloniex.com";
1189
        randId = Random::int45Id;
1190
        webMarket = "https://poloniex.com/exchange/";
1191
        webOrders = "https://poloniex.com/tradeHistory";
1192
      };
1193
      string web(const string &base, const string &quote) const {
×
1194
        return webMarket + quote + "_" + base;
×
1195
      };
1196
    protected:
1197
      string nonce() const override {
1198
        return to_string(Tstamp);
1199
      };
1200
      void pairs(string &report) const override {
×
1201
        const json reply = Curl::Web::xfer(*guard, http + "/public?command=returnTicker");
×
1202
        if (!reply.is_object())
×
1203
          print("Error while reading pairs: " + reply.dump());
×
1204
        else for (auto it = reply.begin(); it != reply.end(); ++it)
×
1205
          report += it.key() + ANSI_NEW_LINE;
×
1206
      };
×
1207
      json handshake() const override {
×
1208
        const json reply = Curl::Web::xfer(*guard, http + "/public?command=returnTicker")
×
1209
                             .value(quote + "_" + base, json::object());
×
1210
        return {
1211
          {     "base", base              },
×
1212
          {    "quote", quote             },
1213
          {   "symbol", quote + "_" + base},
×
1214
          {"tickPrice", reply.empty()
1215
                          ? 0 : 1e-8      },
×
1216
          { "tickSize", 1e-8              },
×
1217
          {  "minSize", 1e-4              },
×
1218
          {    "reply", reply             }
1219
        };
×
1220
      };
×
1221
      json xfer(const string &url, const string &post, const string &h1, const string &h2) const {
1222
        return Curl::Web::xfer(*guard, url, "GET", post, {
1223
          "Content-Type: application/x-www-form-urlencoded",
1224
          "Key: "  + h1,
1225
          "Sign: " + h2
1226
        });
1227
      };
1228
  };
1229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc