57#include <algotradelib/kis>
58#include <algotradelib/kiwoom>
59#include <algotradelib/tossinvest>
60#include <algotradelib/vanilla>
62#include <boost/asio/ssl/context.hpp>
78namespace ssl = boost::asio::ssl;
88 std::optional<std::string> journal_;
89 std::optional<std::string> control_;
90 std::optional<std::int64_t> toss_account_;
91 std::optional<std::filesystem::path> credentials_;
92 bool credentials_from_flag_{
false};
93 std::optional<std::string> market_data_;
94 std::optional<std::string> kiwoom_account_;
99 vanilla::BuyAndHold holding;
100 holding.params_.instrument_ = instrument;
140std::string file_safe(std::string text) {
141 for (
auto& ch : text) {
142 if (!std::isalnum(
static_cast<unsigned char>(ch)) && ch !=
'-') {
158 const Arguments& arguments,
159 const std::string& broker,
161 const std::string& account
163 settings.
journal_scope_ = broker +
':' + (demo ?
"demo" :
"live") +
':' + account;
164 const std::string stem =
"atl-" + broker +
'-' + (demo ?
"demo" :
"live") +
'-' + file_safe(account);
165 settings.
journal_path_ = arguments.journal_ ? *arguments.journal_ : stem +
".jsonl";
166 settings.
control_file_ = arguments.control_ ? *arguments.control_ : stem +
".stop";
175 const Arguments& arguments,
178 const char* demo_variable
180 std::cout <<
"broker : " << arguments.broker_ << (demo ?
" (demo)" :
" (LIVE ACCOUNT)") <<
'\n';
183 if (demo && demo_variable !=
nullptr && std::getenv(demo_variable) ==
nullptr) {
184 std::cout <<
" demo by default; set " << demo_variable <<
"=0 to trade live\n";
186 std::cout <<
"instrument : " << arguments.symbol_ <<
'\n'
187 <<
"mode : " << (arguments.live_ ?
"TRADE -- orders will be sent" :
"observe") <<
'\n'
193 if (arguments.live_) {
199template <
typename Session,
typename MarketData>
202 MarketData& market_data,
209 const auto exit = live ?
210 live_program::trade(session, market_data, strategy_for(instrument), settings, stop, std::cout) :
211 live_program::
observe(session, market_data, strategy_for(instrument), settings,
stop, std::cout);
213 return static_cast<int>(exit);
217template <
typename Session>
219 return run(session, session, instrument, settings, live);
222int run_kis(ssl::context& tls,
const Arguments& arguments) {
225 kis::KisConfig config;
226 kis::KisAccount account;
228 const bool demo = config.is_demo_;
230 const atl::InstrumentId instrument{atl::Country::KR, atl::Venue::COMBINED, arguments.symbol_};
231 auto settings = settings_for(instrument);
232 name_journal(settings, arguments,
"kis", demo, account.account_number_ +
'-' + account.product_code_);
233 announce(arguments, settings, demo,
"KIS_DEMO");
242 if (arguments.market_data_ ==
"toss") {
246 throw std::runtime_error(
"--market-data toss is for a KIS demo run only");
248 if (!arguments.credentials_) {
249 throw std::runtime_error(
"--market-data toss reads tossinvest/real.json; give --credentials DIR");
251 const auto path = *arguments.credentials_ /
"tossinvest" /
"real.json";
255 std::make_shared<algotradelib::tossinvest::TossBlockingClient>(tls, std::move(toss_config)), 0
257 std::cout <<
"market data: toss " << path.string() <<
" (no account: cannot trade)\n";
258 return run(session, market_data, instrument, settings, arguments.live_);
260 if (!demo && arguments.market_data_ ==
"kis") {
261 throw std::runtime_error(
"--market-data kis is for a demo run; a live run reads its own market data");
267 std::make_shared<kis::KisBlockingClient>(
272 std::cout <<
"market data: kis " << (*arguments.credentials_ /
"kis" /
"real.json").
string()
273 <<
" (no account: cannot trade)\n";
274 return run(session, market_data, instrument, settings, arguments.live_);
276 if (arguments.market_data_ ==
"kis") {
277 throw std::runtime_error(
278 arguments.credentials_ ?
"--market-data kis reads kis/real.json, and there is none" :
279 "--market-data kis reads kis/real.json; give --credentials DIR"
282 std::cout <<
"market data: the demo account's own (no kis/real.json); a demo host refuses\n"
283 <<
" reference data, so this run stops there\n";
285 return run(session, instrument, settings, arguments.live_);
288int run_kiwoom(ssl::context& tls,
const Arguments& arguments) {
291 kiwoom::KiwoomConfig config;
299 config.holdings_venue_ = atl::Venue::KRX;
302 const bool demo = config.is_demo_;
306 kiwoom::KiwoomAccount{}
310 const auto accounts = session.
accounts();
311 if (!accounts || accounts.value().empty()) {
312 std::cerr <<
"could not read which account this Kiwoom key drives"
313 << (accounts ? std::string() :
": " + accounts.error().message_) <<
'\n';
317 const atl::InstrumentId instrument{atl::Country::KR, atl::Venue::COMBINED, arguments.symbol_};
318 auto settings = settings_for(instrument);
325 name_journal(settings, arguments,
"kiwoom", demo, accounts.value().front().display_number_);
326 announce(arguments, settings, demo,
"KIWOOM_DEMO");
332 std::cout <<
"note : on Kiwoom this program places nothing yet -- holdings need a stated\n"
333 <<
" scope, and the market phase is unknown without a calendar\n";
334 return run(session, instrument, settings, arguments.live_);
337int run_toss(ssl::context& tls,
const Arguments& arguments) {
340 toss::TossConfig config;
342 std::cout <<
"credentials: environment variables (Toss reads no credentials directory)\n";
343 auto client = std::make_shared<toss::TossBlockingClient>(tls, std::move(config));
350 if (!accounts || accounts.value().empty()) {
351 std::cerr <<
"could not list the accounts these Toss credentials reach"
352 << (accounts ? std::string() :
": " + accounts.error().message_) <<
'\n';
355 if (!arguments.toss_account_ && accounts.value().size() > 1) {
356 std::cerr <<
"these Toss credentials reach " << accounts.value().size()
357 <<
" accounts; choose one with --toss-account:\n";
358 for (
const auto& account : accounts.value()) {
359 std::cerr <<
" --toss-account " << account.selector_ <<
" (" << account.type_ <<
")\n";
363 auto chosen = accounts.value().front();
364 if (arguments.toss_account_) {
366 for (
const auto& account : accounts.value()) {
367 if (account.selector_ == *arguments.toss_account_) {
373 std::cerr <<
"no Toss account has sequence " << *arguments.toss_account_ <<
'\n';
377 if (chosen.display_number_.empty()) {
378 std::cerr <<
"Toss reported no account number for sequence " << chosen.selector_
379 <<
", so the journal could not be named after the account\n";
383 const atl::InstrumentId instrument{atl::Country::KR, atl::Venue::COMBINED, arguments.symbol_};
384 auto settings = settings_for(instrument);
387 name_journal(settings, arguments,
"toss",
false, chosen.display_number_);
388 announce(arguments, settings,
false,
nullptr);
389 std::cout <<
"note : Toss has no paper-trading environment; --live trades real money\n";
392 return run(session, instrument, settings, arguments.live_);
396std::optional<Arguments> parse(
int argc,
char** argv) {
398 const int given = argc;
401 arguments.credentials_from_flag_ = argc != given;
403 }
catch (
const std::runtime_error& error) {
404 std::cerr <<
"example-live-program: " << error.what() <<
'\n';
407 std::vector<std::string> positional;
408 for (
int i = 1; i < argc; ++i) {
409 const std::string argument = argv[i];
410 const bool has_value = i + 1 < argc;
411 if (argument ==
"--live") {
412 arguments.live_ =
true;
413 }
else if (argument ==
"--journal" && has_value) {
414 arguments.journal_ = argv[++i];
415 }
else if (argument ==
"--control" && has_value) {
416 arguments.control_ = argv[++i];
417 }
else if (argument ==
"--toss-account" && has_value) {
418 arguments.toss_account_ = std::strtoll(argv[++i],
nullptr, 10);
419 }
else if (argument ==
"--market-data" && has_value) {
420 arguments.market_data_ = argv[++i];
421 if (*arguments.market_data_ !=
"kis" && *arguments.market_data_ !=
"toss") {
424 }
else if (argument.rfind(
"--", 0) == 0) {
427 positional.push_back(argument);
430 if (positional.size() != 2) {
433 arguments.broker_ = positional[0];
434 arguments.symbol_ = positional[1];
435 if (arguments.broker_ !=
"kis" && arguments.broker_ !=
"kiwoom" && arguments.broker_ !=
"toss") {
441 if (arguments.market_data_ && arguments.broker_ !=
"kis") {
444 if (arguments.credentials_from_flag_ && arguments.broker_ ==
"toss") {
447 if (arguments.kiwoom_account_ && arguments.broker_ !=
"kiwoom") {
462int main(
int argc,
char** argv) {
463 const auto arguments = parse(argc, argv);
466 <<
"usage: example-live-program <kis|kiwoom|toss> <symbol> [--live] [--journal PATH]\n"
467 <<
" [--control PATH] [--toss-account SEQUENCE]\n"
468 <<
" [--credentials DIR] [--market-data kis|toss]\n"
469 <<
" [--kiwoom-account LABEL]\n\n"
470 <<
"Without --live it observes: nothing can be sent. KIS and Kiwoom are demo unless\n"
471 <<
"KIS_DEMO=0 / KIWOOM_DEMO=0; Toss has no demo, so toss --live trades real money.\n";
476 ssl::context tls(ssl::context::tls_client);
477 tls.set_default_verify_paths();
478 tls.set_verify_mode(ssl::verify_peer);
480 if (arguments->broker_ ==
"kis") {
481 return run_kis(tls, *arguments);
483 if (arguments->broker_ ==
"kiwoom") {
484 return run_kiwoom(tls, *arguments);
486 return run_toss(tls, *arguments);
487 }
catch (
const std::exception& exception) {
488 std::cerr <<
"example-live-program: " << exception.what() <<
'\n';
A blocking broker client bound to one account, with the same broker operations as BrokerSession,...
Result< std::vector< AccountInfo< Client > > > accounts()
Lists the broker's accounts; requires Caps::has_account_discovery.
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
void from_json(const std::string &filename)
Loads the credentials from a JSON file, in the broker's format.
static void install(StopSource &source) noexcept
Installs the handlers: SIGINT and SIGTERM request a quiesce on source; SIGHUP, where it exists,...
static void uninstall() noexcept
Restores the default disposition of SIGINT, SIGTERM and (POSIX) SIGHUP, and forgets the source.
A stop request the loop polls; safe to set from a signal handler.
The examples' KIS credentials: one JSON file per account, or the environment.
algotradelib::network::HttpClientOptions paced_options()
Returns transport options that pace a KIS client at one request a second.
std::optional< algotradelib::kis::KisConfig > market_data_config(const std::optional< std::filesystem::path > &directory)
Returns a configuration for market data alone, from DIR/kis/real.json.
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...
std::optional< std::filesystem::path > take_directory(int &argc, char **argv)
Removes --credentials DIR from the command line and returns the directory, else ATL_CREDENTIALS_DIR.
The examples' Kiwoom credentials: one JSON file per key, or the environment.
algotradelib::network::HttpClientOptions paced_options()
Returns transport options that pace a Kiwoom client at one request a second.
std::optional< std::string > take_label(int &argc, char **argv)
Removes --kiwoom-account LABEL from the command line and returns the label.
void load(const std::optional< std::filesystem::path > &directory, const std::optional< std::string > &label, algotradelib::kiwoom::KiwoomConfig &config, std::ostream &out)
Loads the credentials from DIR/kiwoom/{demo,real}-LABEL.json (as KIWOOM_DEMO chooses; label kr by def...
The production wiring of a live run, with no broker named: the part of example-live-program a consume...
Exit observe(Session &session, MarketData &market_data, Strategy strategy_instance, const Settings &settings, runner::StopSource &stop, std::ostream &out, Clock clock={})
Runs a strategy against a session's account and another's market data, with nothing to send through.
@ ACCOUNT_UNRESOLVED
The program could not settle which account to trade (used by example-live-program).
Exit trade(Session &session, MarketData &market_data, Strategy strategy_instance, const Settings &settings, runner::StopSource &stop, std::ostream &out, Clock clock={})
Trades a strategy through a session, reading market data and reference data from another.
The Korea Investment & Securities (KIS) Open API client.
The Kiwoom Securities REST API client.
Runs a strategy: the runner, the polling loop, the order journal, and stopping.
void stop(S &strategy, Ctx &context)
Calls strategy.on_stop(context) if the strategy declares on_stop; otherwise does nothing.
The strategies this library ships (buy and hold, crossovers, mean reversion, sizing,...
The Toss Securities Open API client.
The library: broker-neutral types (core), and the modules below.
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 venue_name(Venue venue) noexcept
Returns a display name for a venue, such as "KRX".
Identifies an instrument: its country, venue and symbol.
std::optional< std::chrono::nanoseconds > holdings_
How often to read holdings; without it the portfolio is never refreshed.
std::optional< std::chrono::nanoseconds > price_limits_
How often to read today's price bands.
std::optional< std::chrono::nanoseconds > bars_
How often to fetch bars.
std::optional< std::chrono::nanoseconds > working_orders_
How often to read the working orders.
std::optional< Decimal64 > max_position_quantity_
The largest absolute position, in shares, any instrument may reach.
std::optional< std::size_t > max_orders_per_window_
The most orders that may be sent within rate_window_.
std::optional< Decimal64 > max_order_notional_
The largest value one order may have, in its own currency.
Toss Securities credentials (client id and secret). Toss has no demo environment.
What a run is told, gathered so main states it once and a test can state it differently.
std::vector< atl::InstrumentId > universe_
The instruments the source polls.
std::string journal_path_
Where the order journal lives.
std::chrono::nanoseconds maximum_portfolio_age_
How old a holdings snapshot – and the order listing – may be before the loop stops sizing against it.
std::chrono::nanoseconds maximum_price_limits_age_
How old a price band may be before the loop stops clamping to it.
std::string control_file_
The file an operator writes quiesce, withdraw or flatten into to stop the run.
runner::PollSchedule schedule_
How often each kind is polled. trade needs working_orders_ set, shorter than maximum_portfolio_age_: ...
atl::Venue default_route_
The venue an order is sent to, since a strategy's orders name none; UNSPECIFIED takes the broker's ow...
std::string journal_scope_
Which account the journal belongs to; a journal naming another refuses to open.
strategy::RiskLimits limits_
The limits the guard enforces.