CPPKiteConnect
kitews.hpp
1 /*
2  * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
3  * SPDX-License-Identifier: MIT
4  *
5  * Copyright (c) 2020-2021 Bhumit Attarde
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all
15  * copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #pragma once
27 
28 #include <algorithm> //reverse
29 #include <atomic>
30 #include <chrono>
31 #include <cstdint>
32 #include <cstring> //memcpy
33 #include <functional>
34 #include <ios>
35 #include <iostream>
36 #include <limits>
37 #include <string>
38 #include <thread>
39 #include <unordered_map>
40 #include <vector>
41 
42 #include "config.hpp"
43 #include "kiteppexceptions.hpp"
44 #include "responses.hpp"
45 #include "userconstants.hpp" //modes
46 
47 #include "rapidjson/document.h"
48 #include "rapidjson/rapidjson.h"
49 #include "rapidjson/writer.h"
50 #include "rjutils.hpp"
51 #include <uWS/uWS.h>
52 
53 namespace kiteconnect {
54 
55 // To make sure doubles are parsed correctly
56 static_assert(std::numeric_limits<double>::is_iec559, "Requires IEEE 754 floating point!");
57 
58 using std::string;
59 namespace rj = rapidjson;
60 namespace kc = kiteconnect;
61 namespace rju = kc::rjutils;
62 
67 class kiteWS {
68 
69  public:
70  // member variables
71 
72  // callbacks
73 
77  std::function<void(kiteWS* ws)> onConnect;
78 
82  std::function<void(kiteWS* ws, const std::vector<kc::tick>& ticks)> onTicks;
83 
87  std::function<void(kiteWS* ws, const kc::postback& postback)> onOrderUpdate;
88 
92  std::function<void(kiteWS* ws, const string& message)> onMessage;
93 
97  std::function<void(kiteWS* ws, int code, const string& message)> onError;
98 
102  std::function<void(kiteWS* ws)> onConnectError;
103 
117  std::function<void(kiteWS* ws, unsigned int attemptCount)> onTryReconnect;
118 
123  std::function<void(kiteWS* ws)> onReconnectFail;
124 
128  std::function<void(kiteWS* ws, int code, const string& message)> onClose;
129 
130  // constructors & destructors
131 
142  kiteWS(const string& apikey, unsigned int connecttimeout = 5, bool enablereconnect = false,
143  unsigned int maxreconnectdelay = 60, unsigned int maxreconnecttries = 30)
144  : _apiKey(apikey), _connectTimeout(connecttimeout * 1000), _enableReconnect(enablereconnect),
145  _maxReconnectDelay(maxreconnectdelay), _maxReconnectTries(maxreconnecttries),
146  _hubGroup(_hub.createGroup<uWS::CLIENT>()) {};
147 
148  // x~kiteWS() {};
149 
150  // methods
151 
157  void setAPIKey(const string& arg) { _apiKey = arg; };
158 
164  string getAPIKey() const { return _apiKey; };
165 
174  void setAccessToken(const string& arg) { _accessToken = arg; };
175 
181  string getAccessToken() const { return _accessToken; };
182 
187  void connect() {
188  _assignCallbacks();
189  _connect();
190  };
191 
196  bool isConnected() const { return _WS; };
197 
203  std::chrono::time_point<std::chrono::system_clock> getLastBeatTime() { return _lastBeatTime; };
204 
209  void run() { _hub.run(); };
210 
215  void stop() {
216 
217  if (isConnected()) { _WS->close(); };
218  };
219 
225  void subscribe(const std::vector<int>& instrumentToks) {
226 
227  rj::Document req;
228  req.SetObject();
229  auto& reqAlloc = req.GetAllocator();
230  rj::Value val;
231  rj::Value toksArr(rj::kArrayType);
232 
233  val.SetString("subscribe", reqAlloc);
234  req.AddMember("a", val, reqAlloc);
235 
236  for (const int tok : instrumentToks) { toksArr.PushBack(tok, reqAlloc); }
237  req.AddMember("v", toksArr, reqAlloc);
238 
239  string reqStr = rju::_dump(req);
240  if (isConnected()) {
241  _WS->send(reqStr.data(), reqStr.size(), uWS::OpCode::TEXT);
242  for (const int tok : instrumentToks) { _subbedInstruments[tok] = ""; };
243  } else {
244  throw kc::libException("Not connected to websocket server");
245  };
246  };
247 
253  void unsubscribe(const std::vector<int>& instrumentToks) {
254 
255  rj::Document req;
256  req.SetObject();
257  auto& reqAlloc = req.GetAllocator();
258  rj::Value val;
259  rj::Value toksArr(rj::kArrayType);
260 
261  val.SetString("unsubscribe", reqAlloc);
262  req.AddMember("a", val, reqAlloc);
263 
264  for (const int tok : instrumentToks) { toksArr.PushBack(tok, reqAlloc); }
265  req.AddMember("v", toksArr, reqAlloc);
266 
267  string reqStr = rju::_dump(req);
268  if (isConnected()) {
269  _WS->send(reqStr.data(), reqStr.size(), uWS::OpCode::TEXT);
270  for (const int tok : instrumentToks) {
271  auto it = _subbedInstruments.find(tok);
272  if (it != _subbedInstruments.end()) { _subbedInstruments.erase(it); };
273  };
274  } else {
275  throw kc::libException("Not connected to websocket server");
276  };
277  };
278 
285  void setMode(const string& mode, const std::vector<int>& instrumentToks) {
286 
287  rj::Document req;
288  req.SetObject();
289  auto& reqAlloc = req.GetAllocator();
290  rj::Value val;
291  rj::Value valArr(rj::kArrayType);
292  rj::Value toksArr(rj::kArrayType);
293 
294  val.SetString("mode", reqAlloc);
295  req.AddMember("a", val, reqAlloc);
296 
297  val.SetString(mode.c_str(), mode.size(), reqAlloc);
298  valArr.PushBack(val, reqAlloc);
299  for (const int tok : instrumentToks) { toksArr.PushBack(tok, reqAlloc); }
300  valArr.PushBack(toksArr, reqAlloc);
301  req.AddMember("v", valArr, reqAlloc);
302 
303  string reqStr = rju::_dump(req);
304  if (isConnected()) {
305  _WS->send(reqStr.data(), reqStr.size(), uWS::OpCode::TEXT);
306  for (const int tok : instrumentToks) { _subbedInstruments[tok] = mode; };
307  } else {
308  throw kc::libException("Not connected to websocket server");
309  };
310  };
311 
312  private:
313  // For testing binary parsing
314  friend class kWSTest_binaryParsingTest_Test;
315  // member variables
316  const string _connectURLFmt = "wss://ws.kite.trade/?api_key={0}&access_token={1}";
317  string _apiKey;
318  string _accessToken;
319  const std::unordered_map<string, int> _segmentConstants = {
320  { "nse", 1 },
321  { "nfo", 2 },
322  { "cds", 3 },
323  { "bse", 4 },
324  { "bfo", 5 },
325  { "bsecds", 6 },
326  { "mcx", 7 },
327  { "mcxsx", 8 },
328  { "indices", 9 },
329  };
330  std::unordered_map<int, string> _subbedInstruments; // instrument ID, mode
331 
332  uWS::Hub _hub;
333  uWS::Group<uWS::CLIENT>* _hubGroup;
334  uWS::WebSocket<uWS::CLIENT>* _WS = nullptr;
335  const unsigned int _connectTimeout = 5000; // in ms
336 
337  const string _pingMessage = "";
338  const unsigned int _pingInterval = 3000; // in ms
339 
340  const bool _enableReconnect = false;
341  const unsigned int _initReconnectDelay = 2; // in seconds
342  unsigned int _reconnectDelay = _initReconnectDelay;
343  const unsigned int _maxReconnectDelay = 0; // in seconds
344  unsigned int _reconnectTries = 0;
345  const unsigned int _maxReconnectTries = 0; // in seconds
346  std::atomic<bool> _isReconnecting { false };
347 
348  std::chrono::time_point<std::chrono::system_clock> _lastPongTime;
349  std::chrono::time_point<std::chrono::system_clock> _lastBeatTime;
350 
351  // methods
352 
353  void _connect() {
354 
355  _hub.connect(FMT(_connectURLFmt, _apiKey, _accessToken), nullptr, {}, _connectTimeout, _hubGroup);
356  };
357 
358  void _reconnect() {
359 
360  if (isConnected()) { return; };
361 
362  _isReconnecting = true;
363  _reconnectTries++;
364 
365  if (_reconnectTries <= _maxReconnectTries) {
366  std::this_thread::sleep_for(std::chrono::seconds(_reconnectDelay));
367  _reconnectDelay = (_reconnectDelay * 2 > _maxReconnectDelay) ? _maxReconnectDelay : _reconnectDelay * 2;
368 
369  if (onTryReconnect) { onTryReconnect(this, _reconnectTries); };
370  _connect();
371 
372  if (isConnected()) { return; };
373  } else {
374  if (onReconnectFail) { onReconnectFail(this); };
375  _isReconnecting = false;
376  };
377  };
378 
379  void _processTextMessage(char* message, size_t length) {
380 
381  rj::Document res;
382  rju::_parse(res, string(message, length));
383  if (!res.IsObject()) { throw libException("Expected a JSON object"); };
384 
385  string type;
386  rju::_getIfExists(res, type, "type");
387  if (type.empty()) { throw kc::libException(FMT("Cannot recognize websocket message type {0}", type)); }
388 
389  if (type == "order" && onOrderUpdate) { onOrderUpdate(this, kc::postback(res["data"].GetObject())); }
390  if (type == "message" && onMessage) { onMessage(this, string(message, length)); };
391  if (type == "error" && onError) { onError(this, 0, res["data"].GetString()); };
392  };
393 
394  // Convert bytesarray(array[start], arrray[end]) to number of type T
395  template <typename T> T _getNum(const std::vector<char>& bytes, size_t start, size_t end) {
396 
397  T value;
398  std::vector<char> requiredBytes(bytes.begin() + start, bytes.begin() + end + 1);
399 
400 // clang-format off
401  #ifndef WORDS_BIGENDIAN
402  std::reverse(requiredBytes.begin(), requiredBytes.end());
403  #endif
404  // clang-format on
405 
406  std::memcpy(&value, requiredBytes.data(), sizeof(T));
407 
408  return value;
409  };
410 
411  std::vector<std::vector<char>> _splitPackets(const std::vector<char>& bytes) {
412 
413  const int16_t numberOfPackets = _getNum<int16_t>(bytes, 0, 1);
414 
415  std::vector<std::vector<char>> packets;
416 
417  unsigned int packetLengthStartIdx = 2;
418  for (int i = 1; i <= numberOfPackets; i++) {
419  unsigned int packetLengthEndIdx = packetLengthStartIdx + 1;
420  int16_t packetLength = _getNum<int16_t>(bytes, packetLengthStartIdx, packetLengthEndIdx);
421  packetLengthStartIdx = packetLengthEndIdx + packetLength + 1;
422  packets.emplace_back(bytes.begin() + packetLengthEndIdx + 1, bytes.begin() + packetLengthStartIdx);
423  };
424 
425  return packets;
426  };
427 
428  std::vector<kc::tick> _parseBinaryMessage(char* bytes, size_t size) {
429 
430  std::vector<std::vector<char>> packets = _splitPackets(std::vector<char>(bytes, bytes + size));
431  if (packets.empty()) { return {}; };
432 
433  std::vector<kc::tick> ticks;
434  for (const auto& packet : packets) {
435  size_t packetSize = packet.size();
436  int32_t instrumentToken = _getNum<int32_t>(packet, 0, 3);
437  int segment = instrumentToken & 0xff;
438  double divisor = (segment == _segmentConstants.at("cds")) ? 10000000.0 : 100.0;
439  bool tradable = (segment == _segmentConstants.at("indices")) ? false : true;
440 
441  kc::tick Tick;
442 
443  Tick.isTradable = tradable;
444  Tick.instrumentToken = instrumentToken;
445 
446  // LTP packet
447  if (packetSize == 8) {
448  Tick.mode = MODE_LTP;
449  Tick.lastPrice = _getNum<int32_t>(packet, 4, 7) / divisor;
450 
451  } else if (packetSize == 28 || packetSize == 32) {
452  // indices quote and full mode
453 
454  Tick.mode = (packetSize == 28) ? MODE_QUOTE : MODE_FULL;
455  Tick.lastPrice = _getNum<int32_t>(packet, 4, 7) / divisor;
456  Tick.OHLC.high = _getNum<int32_t>(packet, 8, 11) / divisor;
457  Tick.OHLC.low = _getNum<int32_t>(packet, 12, 15) / divisor;
458  Tick.OHLC.open = _getNum<int32_t>(packet, 16, 19) / divisor;
459  Tick.OHLC.close = _getNum<int32_t>(packet, 20, 23) / divisor;
460  // xTick.netChange = (Tick.lastPrice - Tick.OHLC.close) * 100 / Tick.OHLC.close;
461  Tick.netChange = _getNum<int32_t>(packet, 24, 27) / divisor;
462 
463  // parse full mode with timestamp
464  if (packetSize == 32) { Tick.timestamp = _getNum<int32_t>(packet, 28, 33); }
465 
466  } else if (packetSize == 44 || packetSize == 184) {
467  // Quote and full mode
468 
469  Tick.mode = (packetSize == 44) ? MODE_QUOTE : MODE_FULL;
470  Tick.lastPrice = _getNum<int32_t>(packet, 4, 7) / divisor;
471  Tick.lastTradedQuantity = _getNum<int32_t>(packet, 8, 11);
472  Tick.averageTradePrice = _getNum<int32_t>(packet, 12, 15) / divisor;
473  Tick.volumeTraded = _getNum<int32_t>(packet, 16, 19);
474  Tick.totalBuyQuantity = _getNum<int32_t>(packet, 20, 23);
475  Tick.totalSellQuantity = _getNum<int32_t>(packet, 24, 27);
476  Tick.OHLC.open = _getNum<int32_t>(packet, 28, 31) / divisor;
477  Tick.OHLC.high = _getNum<int32_t>(packet, 32, 35) / divisor;
478  Tick.OHLC.low = _getNum<int32_t>(packet, 36, 39) / divisor;
479  Tick.OHLC.close = _getNum<int32_t>(packet, 40, 43) / divisor;
480 
481  Tick.netChange = (Tick.lastPrice - Tick.OHLC.close) * 100 / Tick.OHLC.close;
482 
483  // parse full mode
484  if (packetSize == 184) {
485  Tick.lastTradeTime = _getNum<int32_t>(packet, 44, 47);
486  Tick.OI = _getNum<int32_t>(packet, 48, 51);
487  Tick.OIDayHigh = _getNum<int32_t>(packet, 52, 55);
488  Tick.OIDayLow = _getNum<int32_t>(packet, 56, 59);
489  Tick.timestamp = _getNum<int32_t>(packet, 60, 63);
490 
491  unsigned int depthStartIdx = 64;
492  for (int i = 0; i <= 9; i++) {
493  kc::depthWS depth;
494  depth.quantity = _getNum<int32_t>(packet, depthStartIdx, depthStartIdx + 3);
495  depth.price = _getNum<int32_t>(packet, depthStartIdx + 4, depthStartIdx + 7) / divisor;
496  depth.orders = _getNum<int16_t>(packet, depthStartIdx + 8, depthStartIdx + 9);
497 
498  (i >= 5) ? Tick.marketDepth.sell.emplace_back(depth) : Tick.marketDepth.buy.emplace_back(depth);
499  depthStartIdx = depthStartIdx + 12;
500  };
501  };
502  };
503 
504  ticks.emplace_back(Tick);
505  };
506 
507  return ticks;
508  };
509 
510  void _resubInstruments() {
511 
512  std::vector<int> LTPInstruments;
513  std::vector<int> quoteInstruments;
514  std::vector<int> fullInstruments;
515  for (const auto& i : _subbedInstruments) {
516  if (i.second == MODE_LTP) { LTPInstruments.push_back(i.first); };
517  if (i.second == MODE_QUOTE) { quoteInstruments.push_back(i.first); };
518  if (i.second == MODE_FULL) { fullInstruments.push_back(i.first); };
519  // Set mode as quote if no mode was set
520  if (i.second.empty()) { quoteInstruments.push_back(i.first); };
521  };
522 
523  if (!LTPInstruments.empty()) { setMode(MODE_LTP, LTPInstruments); };
524  if (!quoteInstruments.empty()) { setMode(MODE_QUOTE, quoteInstruments); };
525  if (!fullInstruments.empty()) { setMode(MODE_FULL, fullInstruments); };
526  };
527 
528  /*
529  handled by uWS::Group::StartAutoPing() now. Code kept here as fallback.
530  void _pingLoop() {
531 
532  while (!_stop) {
533 
534  std::cout << "Sending ping..\n";
535  if (isConnected()) { _WS->ping(_pingMessage.data()); };
536  std::this_thread::sleep_for(std::chrono::seconds(_pingInterval));
537 
538  if (_enableReconnect) {
539 
540  auto tmDiff =
541  std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - _lastPongTime)
542  .count();
543 
544  if (tmDiff > _maxPongDelay) {
545  std::cout << FMT("Max pong exceeded.. tmDiff={0}\n", tmDiff);
546  if (isConnected()) { _WS->close(1006, "ping timed out"); };
547  };
548  };
549  };
550  };*/
551 
552  void _assignCallbacks() {
553 
554  _hubGroup->onConnection([&](uWS::WebSocket<uWS::CLIENT>* ws, uWS::HttpRequest req) {
555  _WS = ws;
556  // Not setting this time would prompt reconnecting immediately, even when conected since pongTime would be
557  // far back or default
558  _lastPongTime = std::chrono::system_clock::now();
559 
560  _reconnectTries = 0;
561  _reconnectDelay = _initReconnectDelay;
562  _isReconnecting = false;
563  if (!_subbedInstruments.empty()) { _resubInstruments(); };
564  if (onConnect) { onConnect(this); };
565  });
566 
567  _hubGroup->onMessage([&](uWS::WebSocket<uWS::CLIENT>* ws, char* message, size_t length, uWS::OpCode opCode) {
568  if (opCode == uWS::OpCode::BINARY && onTicks) {
569  if (length == 1) {
570  // is a heartbeat
571  _lastBeatTime = std::chrono::system_clock::now();
572  } else {
573  onTicks(this, _parseBinaryMessage(message, length));
574  };
575  } else if (opCode == uWS::OpCode::TEXT) {
576  _processTextMessage(message, length);
577  };
578  });
579 
580  _hubGroup->onPong([&](uWS::WebSocket<uWS::CLIENT>* ws, char* message, size_t length) {
581  _lastPongTime = std::chrono::system_clock::now();
582  });
583 
584  _hubGroup->onError([&](void*) {
585  if (onConnectError) { onConnectError(this); }
586  // Close the non-responsive connection
587  if (isConnected()) { _WS->close(1006); };
588  if (_enableReconnect) { _reconnect(); };
589  });
590 
591  _hubGroup->onDisconnection([&](uWS::WebSocket<uWS::CLIENT>* ws, int code, char* reason, size_t length) {
592  _WS = nullptr;
593 
594  if (code != 1000) {
595  if (onError) { onError(this, code, string(reason, length)); };
596  };
597  if (onClose) { onClose(this, code, string(reason, length)); };
598  if (code != 1000) {
599  if (_enableReconnect && !_isReconnecting) { _reconnect(); };
600  };
601  });
602 
603  _hubGroup->startAutoPing(_pingInterval, _pingMessage);
604  };
605 };
606 
607 } // namespace kiteconnect
kiteconnect::libException
This exception is thrown when some error occures while processing data on library level....
Definition: kiteppexceptions.hpp:207
kiteconnect::kiteWS::onTicks
std::function< void(kiteWS *ws, const std::vector< kc::tick > &ticks)> onTicks
Called when ticks are received.
Definition: kitews.hpp:82
kiteconnect::kiteWS::onReconnectFail
std::function< void(kiteWS *ws)> onReconnectFail
Called when reconnect attempts exceed maximum reconnect attempts set by user i.e.,...
Definition: kitews.hpp:123
kiteconnect::kiteWS::getAPIKey
string getAPIKey() const
get set API key
Definition: kitews.hpp:164
kiteconnect::kiteWS::setAPIKey
void setAPIKey(const string &arg)
Set the API key.
Definition: kitews.hpp:157
kiteconnect::kiteWS::kiteWS
kiteWS(const string &apikey, unsigned int connecttimeout=5, bool enablereconnect=false, unsigned int maxreconnectdelay=60, unsigned int maxreconnecttries=30)
Construct a new kiteWS object.
Definition: kitews.hpp:142
kiteconnect::depthWS
Reoresents a single entry in market depth returned by kWS.
Definition: responses.hpp:1071
kiteconnect::kiteWS::onMessage
std::function< void(kiteWS *ws, const string &message)> onMessage
Called when a message is received.
Definition: kitews.hpp:92
kiteconnect::postback
Represents postback sent via websockets.
Definition: responses.hpp:1115
kiteconnect::kiteWS::getAccessToken
string getAccessToken() const
Get the Access Token set currently.
Definition: kitews.hpp:181
kiteconnect::kiteWS::onError
std::function< void(kiteWS *ws, int code, const string &message)> onError
Called when connection is closed with an error or websocket server sends an error message.
Definition: kitews.hpp:97
kiteconnect::kiteWS::onConnectError
std::function< void(kiteWS *ws)> onConnectError
Called when an error occures while trying to connect.
Definition: kitews.hpp:102
kiteconnect::kiteWS
Used for accessing websocket interface of Kite API.
Definition: kitews.hpp:67
kiteconnect::kiteWS::onConnect
std::function< void(kiteWS *ws)> onConnect
Called on successful connect.
Definition: kitews.hpp:77
responses.hpp
This file has all the structs returned by kitepp.
userconstants.hpp
this file has useful constants that users can use
kiteconnect::tick
Represents a single tick returned by kWS.
Definition: responses.hpp:1079
kiteconnect::kiteWS::onOrderUpdate
std::function< void(kiteWS *ws, const kc::postback &postback)> onOrderUpdate
Called when an order update is received.
Definition: kitews.hpp:87
kiteconnect::kiteWS::connect
void connect()
Connect to websocket server.
Definition: kitews.hpp:187
kiteconnect::kiteWS::onTryReconnect
std::function< void(kiteWS *ws, unsigned int attemptCount)> onTryReconnect
Called when reconnection is being attempted.
Definition: kitews.hpp:117
kiteconnect::kiteWS::unsubscribe
void unsubscribe(const std::vector< int > &instrumentToks)
unsubscribe instrument tokens.
Definition: kitews.hpp:253
kiteconnect::kiteWS::setMode
void setMode(const string &mode, const std::vector< int > &instrumentToks)
Set the mode of instrument tokens.
Definition: kitews.hpp:285
kiteconnect::kiteWS::stop
void stop()
Stop the client. Closes the connection if connected. Should be the last method to be called.
Definition: kitews.hpp:215
kiteconnect::kiteWS::onClose
std::function< void(kiteWS *ws, int code, const string &message)> onClose
Called when connection is closed.
Definition: kitews.hpp:128
kiteconnect::kiteWS::subscribe
void subscribe(const std::vector< int > &instrumentToks)
Subscribe instrument tokens.
Definition: kitews.hpp:225
kiteconnect::kiteWS::run
void run()
Start the client. Should always be called after connect().
Definition: kitews.hpp:209
kiteconnect::kiteWS::getLastBeatTime
std::chrono::time_point< std::chrono::system_clock > getLastBeatTime()
Get the last time heartbeat was received. Should be used in conjunction with isConnected() method.
Definition: kitews.hpp:203
kiteconnect::kiteWS::setAccessToken
void setAccessToken(const string &arg)
Set the Access Token.
Definition: kitews.hpp:174
kiteconnect::kiteWS::isConnected
bool isConnected() const
Check if client is connected at present.
Definition: kitews.hpp:196