algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
config.hpp
1#pragma once
2
3#include "../../network/oauth.hpp"
4#include <string>
5#include <chrono>
6#include <ctime>
7#include <boost/json.hpp>
8#include <cstdlib>
9#include <fstream>
10#include <iterator>
11#include <stdexcept>
12#include <string_view>
13
14namespace algotradelib {
15namespace kis {
16
17// https://apiportal.koreainvestment.com/apiservice-apiservice?/oauth2/tokenP
18// Real-time(websocket):
19// https://apiportal.koreainvestment.com/apiservice-apiservice?/oauth2/Approval
20// https://github.com/koreainvestment/open-trading-api/blob/main/examples_user/auth/auth_examples.py
21// https://github.com/koreainvestment/open-trading-api/blob/main/examples_user/auth/auth_functions.py
22struct KisConfig;
23
33
39inline constexpr const char* market_division_code(MarketTape tape) noexcept {
40 switch (tape) {
41 case MarketTape::KRX:
42 return "J";
43 case MarketTape::NXT:
44 return "NX";
46 return "UN";
47 }
48 return "UN";
49}
50struct KisRest;
51struct KisWebsocket;
52
58static inline std::chrono::system_clock::time_point parse_expiry(const char* s) {
59 std::tm tm{};
60 if (
61 std::sscanf(
62 s,
63 "%d-%d-%d %d:%d:%d",
64 &tm.tm_year,
65 &tm.tm_mon,
66 &tm.tm_mday,
67 &tm.tm_hour,
68 &tm.tm_min,
69 &tm.tm_sec
70 ) != 6
71 ) {
72 return {};
73 }
74 tm.tm_year -= 1900;
75 tm.tm_mon -= 1;
76 tm.tm_isdst = 0;
77#ifdef _WIN32
78 const std::time_t utc = _mkgmtime(&tm) - 9 * 3600;
79#else
80 const std::time_t utc = timegm(&tm) - 9 * 3600;
81#endif
82 return std::chrono::system_clock::from_time_t(utc);
83}
84
90struct KisConfig : public network::ClientCredentials<KisConfig> {
91 // std::string grant_type = "client_credentials"; // length = 18
92 // std::string app_key_; // length = 36
93 // std::string app_secret_; // length = 180
101 bool is_demo_ = true;
102
110
117 const char* key = std::getenv("KIS_APP_KEY");
118 const char* secret = std::getenv("KIS_APP_SECRET");
119 if (!key || !secret) {
120 throw std::runtime_error("Add KIS_APP_KEY and KIS_APP_SECRET environment variables.");
121 }
122 // Read before anything is assigned, so a refused load leaves this config as it was.
123 const bool demo = demo_from_env();
124 key_ = key;
125 secret_ = secret;
126 is_demo_ = demo;
127 }
128
141 const std::string& filename
142 ) {
143 std::ifstream ifs(filename);
144 if (!ifs.is_open()) {
145 throw std::runtime_error("Cannot open KIS credential file: " + filename);
146 }
147 const std::string content(
148 std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>{}
149 );
150 boost::system::error_code ec;
151 const auto jv = json::parse(content, ec);
152 if (ec) {
153 throw std::runtime_error(
154 "Failed to parse KIS credentials file: " + filename + " — " + ec.message()
155 );
156 }
157 if (!jv.is_object()) {
158 throw std::runtime_error("Failed to parse KIS credentials file: " + filename + " — not a JSON object");
159 }
160 const auto& doc = jv.as_object();
161 const json::value* key = doc.if_contains("app_key");
162 const json::value* secret = doc.if_contains("app_secret");
163 if (!key || !secret || !key->is_string() || !secret->is_string()) {
164 throw std::runtime_error("Credential file should contain app_key and app_secret as strings");
165 }
166 // Everything is read and checked before anything is assigned, so a refused file leaves this
167 // config -- and the endpoint it already selected -- exactly as it was.
168 const bool demo = demo_from_json(doc, filename);
169 key_ = key->get_string().c_str();
170 secret_ = secret->get_string().c_str();
171 is_demo_ = demo;
172 }
173
179 static bool demo_from_env() {
180 const char* demo = std::getenv("KIS_DEMO");
181 if (!demo || std::string_view(demo) == "1") {
182 return true;
183 }
184 if (std::string_view(demo) == "0") {
185 return false;
186 }
187 throw std::runtime_error(
188 std::string("KIS_DEMO is \"") + demo + "\"; set it to 1 for the demo endpoints or 0 to trade "
189 "live, or leave it unset for demo."
190 );
191 }
192
200 static bool demo_from_json(const json::object& doc, const std::string& filename) {
201 const json::value* demo = doc.if_contains("demo");
202 if (!demo) {
203 return true;
204 }
205 if (!demo->is_bool()) {
206 throw std::runtime_error(
207 "KIS credential file " + filename + " has a \"demo\" that is not true or false; write "
208 "true for the demo endpoints or false to trade live, or leave it out for demo."
209 );
210 }
211 return demo->get_bool();
212 }
213
218 static constexpr std::string_view key_field() noexcept { return "appkey"; }
219
224 static constexpr std::string_view secret_field() noexcept { return "appsecret"; }
225
230 const char* rest_host() const noexcept {
231 return is_demo_ ? "openapivts.koreainvestment.com" : "openapi.koreainvestment.com";
232 }
233
238 const char* rest_port() const noexcept { return is_demo_ ? "29443" : "9443"; }
239
244 const char* ws_host() const noexcept { return "ops.koreainvestment.com"; }
245
250 const char* ws_port() const noexcept { return is_demo_ ? "31000" : "21000"; }
251
256 std::string ws_to_jsonbody() const {
257 return R"({"grant_type":"client_credentials","appkey":")" + key_ + R"(","secretkey":")" +
258 secret_ + R"("})";
259 }
260};
261
263struct KisRest : public network::AuthBody<KisRest> {
264 // std::string access_token_; // length = 350
265 // // std::string token_type; // length = 20, Bearer
266 // int64_t expires_in_; // 10-digits
268 std::chrono::system_clock::time_point access_token_expired_; // length = 50, format: "2022-08-30 08:10:10"
269
272
277 static constexpr std::string_view token_field() noexcept { return "access_token"; }
278
284 const json::object& doc
285 ) {
286 // if (!doc.contains("access_token")) {
287 // throw std::runtime_error("Token authentication failed");
288 // }
289 // access_token_ = doc.at("access_token").as_string().c_str();
290 // expires_in_ = doc.contains("expires_in")
291 // ? doc.at("expires_in").as_int64()
292 // : 86400;
293 access_token_expired_ = doc.contains("access_token_token_expired") ?
294 parse_expiry(doc.at("access_token_token_expired").as_string().c_str()) :
296 if (access_token_expired_ != std::chrono::system_clock::time_point{}) {
298 }
299 }
300
305 bool valid() const noexcept { return network::AuthBody<KisRest>::valid(); }
306};
307
311 std::string approval_key_; // length = 286
313 std::chrono::system_clock::time_point last_auth_time_;
314
317
324 const json::object& doc
325 ) {
326 if (!doc.contains("approval_key")) {
327 throw std::runtime_error("WS approval key authentication failed");
328 }
329 approval_key_ = doc.at("approval_key").as_string().c_str();
330 last_auth_time_ = std::chrono::system_clock::now();
331 }
332
337 bool valid() const noexcept {
338 // https://github.com/koreainvestment/open-trading-api/blob/33e0e1e65cd1c8c8b639531483ec0b327087bab1/examples_user/kis_auth.py#L507
339 const auto now = std::chrono::system_clock::now();
340 const bool never_authed = last_auth_time_ == std::chrono::system_clock::time_point{};
341 const auto elapsed =
342 std::chrono::duration_cast<std::chrono::seconds>(now - last_auth_time_).count();
343 return !approval_key_.empty() && !never_authed && elapsed < 86400;
344 }
345};
346
347} // namespace kis
348} // namespace algotradelib
OAuth client credentials (an app key and secret), the base of each broker's credentials class.
Definition oauth.hpp:36
const std::string & key() const noexcept
Definition oauth.hpp:62
const std::string & secret() const noexcept
Definition oauth.hpp:68
The Korea Investment & Securities (KIS) Open API client.
Definition auth.hpp:9
constexpr const char * market_division_code(MarketTape tape) noexcept
Returns KIS's FID_COND_MRKT_DIV_CODE for a tape.
Definition config.hpp:39
MarketTape
Which venue's prices KIS market-data requests read (see KisConfig::market_tape_).
Definition config.hpp:25
@ CONSOLIDATED
KRX and NXT combined (UN).
Definition config.hpp:27
static std::chrono::system_clock::time_point parse_expiry(const char *s)
Reads KIS's token expiry, YYYY-MM-DD HH:MM:SS in Korean time.
Definition config.hpp:58
The library: broker-neutral types (core), and the modules below.
KIS credentials (app key and secret), demo or real account, and the market-data tape.
Definition config.hpp:90
MarketTape market_tape_
Which venue's prices every KIS market-data request reads (quotes, bars, depth, price bands,...
Definition config.hpp:109
bool is_demo_
Whether to use KIS's demo (모의투자) endpoints; true unless stated otherwise.
Definition config.hpp:101
static bool demo_from_json(const json::object &doc, const std::string &filename)
Reads a credential file's "demo".
Definition config.hpp:200
const char * ws_port() const noexcept
Returns the WebSocket port for the environment.
Definition config.hpp:250
static constexpr std::string_view key_field() noexcept
Returns the token request's field for the app key.
Definition config.hpp:218
void from_json_impl(const std::string &filename)
Reads the credentials from a JSON file; called by from_json(filename). Nothing changes if it throws.
Definition config.hpp:140
void from_env_impl()
Reads the credentials from KIS_APP_KEY and KIS_APP_SECRET, and the environment from KIS_DEMO (see dem...
Definition config.hpp:116
static bool demo_from_env()
Reads KIS_DEMO.
Definition config.hpp:179
static constexpr std::string_view secret_field() noexcept
Returns the token request's field for the app secret.
Definition config.hpp:224
std::string ws_to_jsonbody() const
Builds the WebSocket approval-key request body (the secret under secretkey).
Definition config.hpp:256
const char * rest_port() const noexcept
Returns the REST port for the environment.
Definition config.hpp:238
const char * rest_host() const noexcept
Returns the REST host for the environment.
Definition config.hpp:230
const char * ws_host() const noexcept
Returns the WebSocket host (the same for both environments).
Definition config.hpp:244
void from_json_impl(const json::object &doc)
Reads KIS's stated expiry from a token response; called by from_json.
Definition config.hpp:283
std::chrono::system_clock::time_point access_token_expired_
KIS's stated expiry (access_token_token_expired), which takes precedence over expires_in.
Definition config.hpp:268
static constexpr std::string_view token_field() noexcept
Returns the token response's field for the token.
Definition config.hpp:277
KisRest()
Constructs an empty token.
Definition config.hpp:271
bool valid() const noexcept
Tests whether the token is set and stays valid for at least five more minutes.
Definition config.hpp:305
KisWebsocket()
Constructs an empty key.
Definition config.hpp:316
void from_json(const json::object &doc)
Reads an approval-key response.
Definition config.hpp:323
bool valid() const noexcept
Tests whether the key is set and was received less than a day ago.
Definition config.hpp:337
std::chrono::system_clock::time_point last_auth_time_
When it was received.
Definition config.hpp:313
std::string approval_key_
The approval key.
Definition config.hpp:311
An OAuth access token and its expiry, the base of each broker's token-response class.
Definition oauth.hpp:131
bool valid(std::chrono::seconds margin=std::chrono::minutes(5)) const noexcept
Tests whether the token is set and stays valid for at least a margin.
Definition oauth.hpp:182
std::chrono::system_clock::time_point expires_at_
Definition oauth.hpp:135