algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
example-portable-market-data.cpp
1// One market-data report written once and compiled for two brokers, KIS and Toss.
2//
3// `report()` names no vendor type. Everything that differs between KIS and Toss
4// appears as an `if constexpr` on a capability, which is what the port is for: the
5// common questions are asked the same way, and each broker's own strengths are
6// reached deliberately rather than faked for the other.
7//
8// Run it as:
9//
10// example-portable-market-data kis 005930
11// example-portable-market-data tossinvest 005930 [account-sequence]
12//
13// Credentials come from the same environment variables the other examples document.
14// It reads market data only and can place no order.
15
16#include "kis_credentials.hpp"
17
18#include <algotradelib/kis>
19#include <algotradelib/tossinvest>
20
21#include <boost/asio/ssl/context.hpp>
22
23#include <cstdlib>
24#include <exception>
25#include <iostream>
26#include <memory>
27#include <string>
28#include <string_view>
29#include <utility>
30#include <vector>
31
32namespace ssl = boost::asio::ssl;
33
34namespace {
35
37using algotradelib::BarQuery;
38using algotradelib::BarSeries;
39using algotradelib::BlockingSession;
40using algotradelib::BrokerCaps;
41using algotradelib::BrokerError;
43using algotradelib::DepthSnapshot;
44using algotradelib::Instrument;
45using algotradelib::InstrumentId;
47using algotradelib::PriceLimits;
48using algotradelib::StatedDate;
49using algotradelib::TradingCalendar;
52
54void print_error(std::string_view operation, const BrokerError& error) {
55 std::cerr << " " << operation << " failed";
56 if (!error.broker_code_.empty()) {
57 std::cerr << " [" << error.broker_code_ << ']';
58 }
59 if (!error.message_.empty()) {
60 std::cerr << ": " << error.message_;
61 }
62 std::cerr << '\n';
63}
64
66void print_bars(const BarSeries& series) {
67 std::cout << "bars: " << series.bars_.size() << ' '
68 << bar_interval_name(series.interval_) << (series.adjusted_ ? " adjusted" : " raw")
69 << ' ' << series.currency_.str() << ", stamped at each period's "
70 << (series.stamp_ == algotradelib::BarStamp::OPEN ? "open" : "close") << '\n';
71 if (series.bars_.empty()) {
72 return;
73 }
74 std::cout << " ascending: " << (series.ascending() ? "yes" : "no") << '\n';
75 for (const auto* bar : {&series.bars_.front(), &series.bars_.back()}) {
76 std::cout << " " << (bar == &series.bars_.front() ? "oldest" : "newest")
77 << " close=" << bar->close_.str() << " volume=" << bar->volume_.str()
78 << " open=" << (bar->open_ ? bar->open_->str() : std::string("not reported")) << '\n';
79 }
80 if (series.next_cursor_) {
81 std::cout << " may continue from " << *series.next_cursor_ << '\n';
82 }
83}
84
86void print_depth(const DepthSnapshot& book) {
87 std::cout << "book: " << book.bids_.size() << " bids, " << book.asks_.size() << " asks\n";
88 const auto bid = book.best_bid();
89 const auto ask = book.best_ask();
90 std::cout << " best bid: " << (bid ? bid->price_.str() : std::string("none"))
91 << " best ask: " << (ask ? ask->price_.str() : std::string("none")) << '\n';
92 const auto spread = book.spread();
93 const auto mid = book.mid();
94 std::cout << " spread: " << (spread ? spread->str() : std::string("not both sides"))
95 << " mid: " << (mid ? mid->str() : std::string("not both sides")) << '\n';
96}
97
99void print_price_limits(const PriceLimits& limits) {
100 std::cout << "price bounds: "
101 << (limits.lower_ ? limits.lower_->str() : std::string("no lower limit")) << " .. "
102 << (limits.upper_ ? limits.upper_->str() : std::string("no upper limit")) << ' '
103 << limits.currency_.str() << '\n';
104}
105
107void print_instrument(const Instrument& instrument) {
108 std::cout << "instrument: " << instrument.name_;
109 if (!instrument.english_name_.empty()) {
110 std::cout << " (" << instrument.english_name_ << ')';
111 }
112 std::cout << "\n market: " << instrument.segment_.str();
113 if (!instrument.isin_.empty()) {
114 std::cout << " isin: " << instrument.isin_;
115 }
116 switch (instrument.listed_on_.state()) {
117 case StatedDate::State::STATED: {
118 const auto& listed = instrument.listed_on_.date();
119 std::cout << " listed: " << listed.year_ << '-' << listed.month_ << '-' << listed.day_;
120 break;
121 }
122 case StatedDate::State::UNREADABLE:
123 // The broker stated a date nobody can read, which is not the same as stating none.
124 std::cout << " listed: (unreadable)";
125 break;
126 case StatedDate::State::NOT_STATED:
127 break;
128 }
129 std::cout << '\n';
130 std::cout << " price grid: " << instrument.price_rule_.source_ << '\n';
131}
132
134void print_calendar(const TradingCalendar& calendar) {
135 std::cout << "calendar: " << calendar.days_.size() << " date(s) covered\n";
136 for (const auto& day : calendar.days_) {
137 std::cout << " " << day.date_.year_ << '-' << day.date_.month_ << '-' << day.date_.day_
138 << (day.open_ ? " market opens" : " market closed") << '\n';
139 }
140}
141
148template <typename Client>
149int report(BlockingSession<Client>& session, const InstrumentId& subject) {
150 using Caps = BrokerCaps<Client>;
151
152 std::cout << "broker: " << session.broker_name() << "\n\n";
153
154 algotradelib::PriceRule reference_rule;
155 if (auto reference = session.instrument(subject)) {
156 print_instrument(reference.value());
157 reference_rule = reference.value().price_rule_;
158 } else {
159 print_error("reference data", reference.error());
160 }
161
162 BarQuery daily;
163 daily.instrument_ = subject;
164 daily.interval_ = BarInterval::ONE_DAY;
165 daily.count_ = 120;
166 if (auto bars = session.bars(daily)) {
167 print_bars(bars.value());
168 } else {
169 print_error("daily bars", bars.error());
170 }
171
172 if constexpr (Caps::supports_interval(BarInterval::ONE_WEEK)) {
173 // Only one broker publishes bars longer than a day. They are not aggregated for
174 // the other, because resampling answers a different question from the one asked.
175 BarQuery weekly = daily;
176 weekly.interval_ = BarInterval::ONE_WEEK;
177 weekly.count_ = 12;
178 if (auto bars = session.bars(weekly)) {
179 print_bars(bars.value());
180 } else {
181 print_error("weekly bars", bars.error());
182 }
183 } else {
184 std::cout << "bars: this broker publishes no period longer than a day\n";
185 }
186
187 if constexpr (Caps::has_depth) {
188 if (auto book = session.depth(subject)) {
189 print_depth(book.value());
190
191 // A computed price has to sit on the exchange's grid, and the grid is the
192 // library's own because no broker publishes one.
193 if (const auto mid = book.value().mid()) {
194 // The grid comes from the instrument, because it depends on what the
195 // instrument is: KRX prices funds and notes on a ladder this library does not
196 // carry, and those refuse to round rather than rounding onto the wrong one.
197 const auto rule = reference_rule;
198 const auto buy = rule.round_to_tick(*mid, OrderSide::BUY);
199 std::cout << " a buy at the midpoint would rest at "
200 << (buy ? buy->str() : std::string("no grid known for this instrument")) << '\n';
201 }
202 } else {
203 print_error("order book", book.error());
204 }
205 }
206
207 if constexpr (Caps::has_price_limits) {
208 if (auto limits = session.price_limits(subject)) {
209 print_price_limits(limits.value());
210 } else {
211 print_error("price bounds", limits.error());
212 }
213 }
214
215 if constexpr (Caps::has_trade_prints) {
216 if (auto prints = session.trades(subject, 5)) {
217 std::cout << "recent executions: " << prints.value().size() << '\n';
218 for (const auto& print : prints.value()) {
219 std::cout << " " << print.price_.str() << " x " << print.quantity_.str() << '\n';
220 }
221 } else {
222 print_error("recent executions", prints.error());
223 }
224 }
225
226 if constexpr (Caps::has_batch_prices) {
227 // One request for several instruments, where the broker offers it. Looping
228 // single quotes instead would spend several times the request budget.
229 if (auto quotes = session.prices({subject})) {
230 std::cout << "batch quotes: " << quotes.value().size() << '\n';
231 } else {
232 print_error("batch quotes", quotes.error());
233 }
234 } else {
235 std::cout << "batch quotes: this broker quotes one instrument per request\n";
236 }
237
238 if constexpr (Caps::has_calendar) {
239 if (auto calendar = session.calendar(subject.country_)) {
240 print_calendar(calendar.value());
241 if constexpr (!Caps::has_calendar_range) {
242 std::cout << " one request covers the requested day and the ones either side\n";
243 }
244 } else {
245 print_error("calendar", calendar.error());
246 }
247 }
248
249 if constexpr (Caps::has_calendar_sessions) {
250 std::cout << "sessions: this broker publishes intraday session windows; the library "
251 << "does not expose them yet\n";
252 }
253
254 return 0;
255}
256
258int report_kis(ssl::context& tls, const std::string& symbol) {
259 namespace kis = algotradelib::kis;
260
261 kis::KisConfig config;
262 kis::KisAccount account;
263 kis_credentials::load(kis_credentials::environment_directory(), config, &account, std::cout);
264
265 auto client =
266 std::make_shared<kis::KisBlockingClient>(tls, std::move(config), kis_credentials::paced_options());
267 BlockingSession<kis::KisBlockingClient> session(std::move(client), std::move(account));
268 return report(session, InstrumentId{Country::KR, Venue::COMBINED, symbol});
269}
270
272int report_tossinvest(ssl::context& tls, const std::string& symbol, const char* sequence) {
273 namespace toss = algotradelib::tossinvest;
274
275 toss::TossConfig config;
276 config.from_env();
277 auto client = std::make_shared<toss::TossBlockingClient>(tls, std::move(config));
278
279 std::int64_t selected = 0;
280 if (sequence) {
281 selected = std::strtoll(sequence, nullptr, 10);
282 } else {
283 auto accounts = algotradelib::list_accounts(*client);
284 if (!accounts) {
285 print_error("account discovery", accounts.error());
286 return 1;
287 }
288 if (accounts.value().empty()) {
289 std::cerr << "no Toss accounts are available for these credentials\n";
290 return 1;
291 }
292 selected = accounts.value().front().selector_;
293 }
294
295 BlockingSession<toss::TossBlockingClient> session(std::move(client), selected);
296 return report(session, InstrumentId{Country::KR, Venue::COMBINED, symbol});
297}
298
299} // namespace
300
301int main(int argc, char** argv) {
302 const std::string broker = argc > 1 ? argv[1] : "";
303 if (broker != "kis" && broker != "tossinvest") {
304 std::cerr
305 << "usage: example-portable-market-data <kis|tossinvest> [symbol] [account-sequence]\n";
306 return 2;
307 }
308 const std::string symbol = argc > 2 ? argv[2] : "005930";
309
310 try {
311 ssl::context tls(ssl::context::tls_client);
312 tls.set_default_verify_paths();
313 tls.set_verify_mode(ssl::verify_peer);
314
315 if (broker == "kis") {
316 return report_kis(tls, symbol);
317 }
318 return report_tossinvest(tls, symbol, argc > 3 ? argv[3] : nullptr);
319 } catch (const std::exception& exception) {
320 std::cerr << "example failed: " << exception.what() << '\n';
321 return 1;
322 }
323}
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...
void bar(S &strategy, Ctx &context, const BarEvent &event)
Calls strategy.on_bar(context, event) if the strategy declares on_bar; otherwise does nothing.
Definition concept.hpp:191
void bars(S &strategy, Ctx &context, const BarSlice &slice)
Calls strategy.on_bars(context, slice) if the strategy declares on_bars; otherwise does nothing.
Definition concept.hpp:218
@ OPEN
The timestamp is the first instant of the period.
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...
constexpr std::string_view bar_interval_name(BarInterval interval) noexcept
Returns a short name for an interval, such as "1m", "5m", "1h" or "1d".
Country
A country (market) the library supports.
Definition types.hpp:30
Venue
A trading venue: an exchange, or the combined view across exchanges.
Definition types.hpp:43
BarInterval
The period one bar covers.
OrderSide
The side of an order: its economic direction, buy or sell.
Definition types.hpp:1683
std::optional< Decimal64 > round_to_tick(const Decimal64 &price, OrderSide side) const
Rounds a price to its band's tick in the order's favour: a buy rounds down, a sell up.