algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
example-portable-holdings.cpp
1// One account report written once and compiled for two brokers, KIS and Toss.
2//
3// `report()` below is the point of this example: it names no vendor type, no
4// account selector, and no broker-specific acknowledgement. It is instantiated
5// twice — once for KIS and once for Toss Securities — and the only thing that
6// differs between the two runs is the type parameter chosen in `main()`.
7//
8// Run it as:
9//
10// example-portable-holdings kis
11// example-portable-holdings tossinvest [account-sequence]
12//
13// KIS reads its account from `DIR/kis/demo.json` (or `real.json` with `KIS_DEMO=0`) when
14// `ATL_CREDENTIALS_DIR` names a directory, and otherwise from `KIS_CANO` and the optional
15// `KIS_ACNT_PRDT_CD`; Toss discovers its accounts over the network and uses the
16// first one unless a sequence is supplied. Credentials come from the same
17// environment variables the other examples document, and no order is ever placed.
18
19#include "kis_credentials.hpp"
20
21#include <algotradelib/kis>
22#include <algotradelib/tossinvest>
23
24#include <boost/asio/ssl/context.hpp>
25
26#include <cstdlib>
27#include <exception>
28#include <iostream>
29#include <memory>
30#include <string>
31#include <string_view>
32#include <utility>
33
34namespace ssl = boost::asio::ssl;
35
36namespace {
37
38using algotradelib::BlockingSession;
39using algotradelib::BrokerCaps;
40using algotradelib::BrokerError;
41using algotradelib::HoldingsSnapshot;
42
44std::string mask_account_number(std::string_view account_number) {
45 if (account_number.size() <= 4) {
46 return std::string(account_number.size(), '*');
47 }
48 return std::string(account_number.size() - 4, '*') +
49 std::string(account_number.substr(account_number.size() - 4));
50}
51
53void print_error(std::string_view operation, const BrokerError& error) {
54 std::cerr << operation << " failed";
55 if (!error.broker_code_.empty()) {
56 std::cerr << " [" << error.broker_code_ << ']';
57 }
58 if (error.http_status_ != 0) {
59 std::cerr << " HTTP " << error.http_status_;
60 }
61 if (!error.message_.empty()) {
62 std::cerr << ": " << error.message_;
63 }
64 if (error.retry_after_) {
65 std::cerr << " (retry after " << error.retry_after_->count() << "s)";
66 }
67 std::cerr << '\n';
68}
69
71void print_holdings(const HoldingsSnapshot& snapshot) {
72 std::cout << "positions: " << snapshot.positions_.size() << '\n';
73 for (const auto& position : snapshot.positions_) {
74 std::cout << " " << position.instrument_.symbol_ << " " << position.name_
75 << " quantity=" << position.quantity_.str()
76 << " market=" << position.market_value_.amount_.str() << ' '
77 << position.market_value_.currency_.str() << '\n';
78 }
79 for (const auto& total : snapshot.totals_) {
80 std::cout << " total " << total.currency_.str()
81 << " purchase=" << total.purchase_value_.str()
82 << " market=" << total.market_value_.str()
83 << " unrealized=" << total.unrealized_profit_loss_.str() << '\n';
84 }
85}
86
88template <typename Broker>
89void print_working_orders(const algotradelib::WorkingOrders<Broker>& orders) {
90 std::cout << "working orders: " << orders.orders_.size() << '\n';
91 for (const auto& working : orders.orders_) {
92 std::cout << " " << working.order_.instrument_.symbol_
93 << " reference=" << working.ref_.str()
94 << " ordered=" << working.order_.quantity_.str()
95 << " filled=" << working.order_.execution_.filled_quantity_.str();
96 if (working.amendable_quantity_) {
97 std::cout << " amendable=" << working.amendable_quantity_->str();
98 } else {
99 std::cout << " amendable=not reported";
100 }
101 std::cout << '\n';
102 }
103}
104
112template <typename Client>
113int report(BlockingSession<Client>& session) {
114 using Caps = BrokerCaps<Client>;
115
116 std::cout << "broker: " << session.broker_name() << '\n';
117 std::cout << "reports an amendable quantity: "
118 << (Caps::has_amendable_quantity ? "yes" : "no") << '\n';
119 std::cout << "accepts an idempotency key: "
120 << (Caps::has_client_order_id ? "yes" : "no") << '\n';
121
122 auto holdings = session.holdings();
123 if (!holdings) {
124 print_error("holdings request", holdings.error());
125 return 1;
126 }
127 print_holdings(holdings.value());
128
129 if constexpr (Caps::has_working_orders) {
130 auto working = session.working_orders();
131 if (!working) {
132 // A failed inquiry is a run-time condition (a refused key, a closed host), not a missing
133 // feature, so the report continues instead of failing. On a KIS demo account the inquiry is
134 // answered from the day's order history.
135 print_error("working-order inquiry", working.error());
136 } else {
137 print_working_orders(working.value());
138 }
139 }
140
141 return 0;
142}
143
145int report_kis(ssl::context& tls) {
146 namespace kis = algotradelib::kis;
147
148 kis::KisConfig config;
149 kis::KisAccount account;
150 kis_credentials::load(kis_credentials::environment_directory(), config, &account, std::cout);
151
152 std::cout << "environment: " << (config.is_demo_ ? "demo" : "live") << '\n';
153
154 auto client =
155 std::make_shared<kis::KisBlockingClient>(tls, std::move(config), kis_credentials::paced_options());
156 BlockingSession<kis::KisBlockingClient> session(std::move(client), std::move(account));
157 return report(session);
158}
159
161int report_tossinvest(ssl::context& tls, const char* requested_sequence) {
162 namespace toss = algotradelib::tossinvest;
163
164 toss::TossConfig config;
165 config.from_env();
166
167 auto client = std::make_shared<toss::TossBlockingClient>(tls, std::move(config));
168
169 std::int64_t sequence = 0;
170 if (requested_sequence) {
171 sequence = std::strtoll(requested_sequence, nullptr, 10);
172 } else {
173 auto accounts = algotradelib::list_accounts(*client);
174 if (!accounts) {
175 print_error("account discovery", accounts.error());
176 return 1;
177 }
178 if (accounts.value().empty()) {
179 std::cerr << "no Toss accounts are available for these credentials\n";
180 return 1;
181 }
182 for (const auto& account : accounts.value()) {
183 std::cout << "account " << mask_account_number(account.display_number_) << " type=" << account.type_
184 << " sequence=" << account.selector_ << '\n';
185 }
186 sequence = accounts.value().front().selector_;
187 }
188
189 BlockingSession<toss::TossBlockingClient> session(std::move(client), sequence);
190 return report(session);
191}
192
193} // namespace
194
195int main(int argc, char** argv) {
196 const std::string broker = argc > 1 ? argv[1] : "";
197 if (broker != "kis" && broker != "tossinvest") {
198 std::cerr << "usage: example-portable-holdings <kis|tossinvest> [account-sequence]\n";
199 return 2;
200 }
201
202 try {
203 ssl::context tls(ssl::context::tls_client);
204 tls.set_default_verify_paths();
205 tls.set_verify_mode(ssl::verify_peer);
206
207 if (broker == "kis") {
208 return report_kis(tls);
209 }
210 return report_tossinvest(tls, argc > 2 ? argv[2] : nullptr);
211 } catch (const std::exception& exception) {
212 std::cerr << "example failed: " << exception.what() << '\n';
213 return 1;
214 }
215}
The examples' KIS credentials: one JSON file per account, or the environment.
void load(const std::optional< std::filesystem::path > &directory, algotradelib::kis::KisConfig &config, algotradelib::kis::KisAccount *account, std::ostream &out)
Loads the credentials (and account) KIS_DEMO chooses, and prints where from (a path or "environment v...
Result< std::vector< AccountInfo< Client > > > list_accounts(Client &client)
Lists a broker's accounts with a blocking client, before a session exists; requires BrokerCaps<Client...
std::vector< WorkingOrder< Broker > > orders_
The open orders.
Definition types.hpp:2314