36#include <algotradelib/kis>
37#include <algotradelib/live>
38#include <algotradelib/vanilla>
39#include <algotradelib/tossinvest>
41#include <boost/asio/ssl/context.hpp>
54namespace ssl = boost::asio::ssl;
58namespace strategy = algotradelib::strategy;
59namespace runner = algotradelib::runner;
60namespace live = algotradelib::live;
61namespace vanilla = algotradelib::strategy::vanilla;
64using algotradelib::BarQuery;
65using algotradelib::BlockingSession;
67using algotradelib::Decimal64;
68using algotradelib::InstrumentId;
85vanilla::BuyAndHold strategy_for(
const InstrumentId& instrument) {
86 vanilla::BuyAndHold holding;
87 holding.params_.instrument_ = instrument;
88 holding.params_.quantity_ = Decimal64::parse(
"1");
93strategy::RiskLimits example_limits() {
94 strategy::RiskLimits limits;
112 algotradelib::Instrument reference_;
113 algotradelib::BarSeries bars_;
114 algotradelib::HoldingsSnapshot holdings_;
124template <
typename Tag,
typename R>
126 const InstrumentId& instrument,
const MarketState& state,
127 const algotradelib::WorkingOrders<Tag>& open_orders, R live_runner
129 live_runner.context().set_instrument(state.reference_);
135 strategy::Portfolio<Tag> portfolio;
152 live_runner.context().set_portfolio(std::move(portfolio));
154 const auto& last = state.bars_.
bars_.back();
155 live_runner.context().set_last_price(instrument, last.close_);
156 const auto report = live_runner.feed(
160 for (
const auto& order : report.would_place_) {
161 std::cout <<
"order: " << (order.side_ == OrderSide::BUY ?
"BUY " :
"SELL") <<
' '
162 << order.quantity_.str() <<
" at "
163 << (order.price_ ? order.price_->str() : std::string(
"market")) <<
'\n';
165 for (
const auto& refusal : report.refused_) {
168 for (
const auto& rejection : report.rejected_) {
173 for (
const auto& held : report.withheld_) {
174 std::cout <<
"withheld pending reconciliation: " << held.detail_ <<
'\n';
176 for (
const auto& order : report.indeterminate_) {
177 std::cout <<
"UNKNOWN OUTCOME for " << order.instrument_.symbol_
178 <<
": read the broker's order list before trading it again\n";
180 if (report.quiet()) {
181 std::cout <<
"the strategy asked for nothing: the target is already met\n";
183 if (!live_runner.ledger().empty()) {
184 std::cout <<
"\nreconciliation required before this instrument may be traded again\n";
191void note_interruption_hazard() {
193 <<
"\nnote: interrupting a live run does not withdraw an accepted order, and an order in\n"
194 <<
" flight at that moment leaves no record that its outcome is unknown. Read the\n"
195 <<
" broker's order list before running again, and wait until the holdings show any\n"
196 <<
" fill: a filled buy they do not show yet is bought again.\n";
212template <
typename Session>
213int trade(Session& session,
const InstrumentId& instrument, Mode mode) {
214 using Caps =
typename Session::Caps;
215 using Tag =
typename Session::BrokerTag;
217 std::cout <<
"broker: " << Tag::name <<
" instrument: " << instrument.symbol_
218 <<
" mode: " << (mode == Mode::TRADE ?
"TRADE" :
"observe") <<
"\n\n";
220 if constexpr (Caps::has_client_order_id) {
221 std::cout <<
"this broker accepts an idempotency key, so a retry could be made safe\n";
223 std::cout <<
"this broker accepts no idempotency key, so no order is ever replayed\n";
230 auto reference = session.instrument(instrument);
232 std::cerr <<
"could not read reference data: " << reference.error().message_ <<
'\n';
235 state.reference_ = std::move(reference.value());
238 query.instrument_ = instrument;
239 query.interval_ = BarInterval::ONE_DAY;
241 auto bars = session.bars(query);
243 std::cerr <<
"could not read bars: " <<
bars.error().message_ <<
'\n';
246 if (
bars.value().bars_.empty()) {
247 std::cerr <<
"the broker returned no bars\n";
250 state.bars_ = std::move(
bars.value());
254 algotradelib::WorkingOrders<Tag> open_orders;
255 if constexpr (Caps::has_working_orders) {
256 auto listed = session.working_orders();
258 std::cerr <<
"could not read open orders: " << listed.error().message_ <<
'\n';
261 open_orders = std::move(listed.value());
263 if (mode == Mode::TRADE) {
264 std::cerr <<
"this broker lists no open orders, so a live run could not see an earlier order "
265 "still waiting to fill; use example-live-program\n";
270 auto holdings = session.holdings();
272 std::cerr <<
"could not read holdings: " << holdings.error().message_ <<
'\n';
275 state.holdings_ = std::move(holdings.value());
279 if (mode == Mode::TRADE) {
280 const auto status = drive<Tag>(
281 instrument, state, open_orders, live::bind(session, strategy_for(instrument), example_limits())
283 note_interruption_hazard();
287 instrument, state, open_orders, live::bind_observer(session, strategy_for(instrument), example_limits())
291int trade_kis(ssl::context& tls,
const std::string& symbol, Mode mode) {
292 namespace kis = algotradelib::kis;
294 kis::KisConfig config;
295 kis::KisAccount account;
299 std::make_shared<kis::KisBlockingClient>(tls, std::move(config), kis_credentials::paced_options());
300 BlockingSession<kis::KisBlockingClient> session(std::move(client), std::move(account));
301 return trade(session, InstrumentId{Country::KR, Venue::COMBINED, symbol}, mode);
306 const std::string& symbol,
307 const char* sequence,
310 namespace toss = algotradelib::tossinvest;
312 toss::TossConfig config;
314 auto client = std::make_shared<toss::TossBlockingClient>(tls, std::move(config));
319 std::int64_t selected = 0;
320 if (sequence !=
nullptr) {
321 selected = std::strtoll(sequence,
nullptr, 10);
325 std::cerr <<
"account discovery failed: " << accounts.error().message_ <<
'\n';
328 if (accounts.value().empty()) {
329 std::cerr <<
"no Toss accounts are available for these credentials\n";
332 selected = accounts.value().front().selector_;
335 BlockingSession<toss::TossBlockingClient> session(std::move(client), selected);
336 return trade(session, InstrumentId{Country::KR, Venue::COMBINED, symbol}, mode);
340int trade_dry(
const std::string& symbol) {
341 const InstrumentId instrument{Country::KR, Venue::COMBINED, symbol};
342 std::cout <<
"no broker bound: nothing in this path can reach a market\n\n";
345 algotradelib::Instrument reference;
346 reference.
id_ = instrument;
347 reference.
type_ = algotradelib::SecurityType::STOCK;
348 reference.
currency_ = algotradelib::Currency{
"KRW"};
349 reference.
lot_size_ = Decimal64::parse(
"1");
351 dry.context().set_instrument(reference);
353 dry.context().set_last_price(instrument, Decimal64::parse(
"70000"));
355 algotradelib::Bar
bar;
356 bar.timestamp_ = std::chrono::system_clock::now();
357 bar.high_ = Decimal64::parse(
"70500");
358 bar.low_ = Decimal64::parse(
"69400");
359 bar.close_ = Decimal64::parse(
"70000");
360 bar.volume_ = Decimal64::parse(
"1200000");
362 const auto report = dry.feed(
367 for (
const auto& order : report.would_place_) {
368 std::cout <<
"would send: " << (order.side_ == OrderSide::BUY ?
"BUY " :
"SELL") <<
' '
369 << order.quantity_.str() <<
" at market\n";
376int main(
int argc,
char** argv) {
377 std::vector<std::string> arguments(argv + 1, argv + argc);
378 Mode mode = Mode::OBSERVE;
380 std::vector<std::string> positional;
381 for (
const auto& argument : arguments) {
382 if (argument ==
"--live") {
384 }
else if (argument ==
"--dry-run") {
386 }
else if (argument.rfind(
"--", 0) == 0) {
389 std::cerr <<
"example-strategy-live: unknown option " << argument
390 <<
" (credentials come from the environment or ATL_CREDENTIALS_DIR)\n";
393 positional.push_back(argument);
397 const std::string symbol = positional.size() > 1 ? positional[1] :
"005930";
399 return trade_dry(symbol);
402 const std::string broker = positional.empty() ?
"" : positional[0];
403 if (broker !=
"kis" && broker !=
"tossinvest") {
405 <<
"usage: example-strategy-live --dry-run\n"
406 <<
" example-strategy-live <kis|tossinvest> [symbol] [account-sequence] [--live]\n\n"
407 <<
"Without --live the session is used for market data only: the send step goes to an\n"
408 <<
"executor that holds no session, so there is nothing for an order to be sent through.\n";
413 ssl::context tls(ssl::context::tls_client);
414 tls.set_default_verify_paths();
415 tls.set_verify_mode(ssl::verify_peer);
417 if (broker ==
"kis") {
418 return trade_kis(tls, symbol, mode);
420 return trade_tossinvest(
421 tls, symbol, positional.size() > 2 ? positional[2].c_str() :
nullptr, mode
423 }
catch (
const std::exception& exception) {
424 std::cerr <<
"example failed: " << exception.what() <<
'\n';
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...
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.
BrokerError refusal()
Returns the error every account-side member of DataOnlyTraits answers with.
Runner< Strategy, CapsType, DryTag, RecordingExecutor< DryTag > > DryRunner
A runner that sends nothing: handlers fire and instructions are recorded by a RecordingExecutor.
void bar(S &strategy, Ctx &context, const BarEvent &event)
Calls strategy.on_bar(context, event) if the strategy declares on_bar; otherwise does nothing.
void bars(S &strategy, Ctx &context, const BarSlice &slice)
Calls strategy.on_bars(context, slice) if the strategy declares on_bars; otherwise does nothing.
BarEvent make_bar_event(InstrumentId instrument, BarInterval interval, BarStamp stamp, Bar bar)
Makes a bar event, computing its period_end_ with bar_period_end.
@ OPEN
Inside the session.
constexpr std::string_view reject_reason_name(RejectReason reason) noexcept
Returns a lowercase name for a reason, such as "no_reference_data", for logs.
constexpr std::string_view guard_reason_name(GuardReason reason) noexcept
Returns a lowercase name for a refusal reason, such as "order_rate_exceeded", for logs.
@ CLOSE
The timestamp is the instant the period ended.
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...
Country
A country (market) the library supports.
Venue
A trading venue: an exchange, or the combined view across exchanges.
BarInterval
The period one bar covers.
OrderSide
The side of an order: its economic direction, buy or sell.
BarInterval interval_
The period each bar covers.
BarStamp stamp_
Which edge of its period each bar's timestamp marks.
std::vector< Bar > bars_
The bars, oldest first.
std::chrono::system_clock::time_point received_at_
When this process received the snapshot (brokers do not timestamp holdings).
SellableBasis sellable_basis_
What the rows' sellable_quantity_ figures cover (unless a row carries its own).
std::vector< HoldingPosition > positions_
Every position row; one instrument may appear in more than one.
Currency currency_
The currency it trades in.
InstrumentId id_
The instrument.
PriceRule price_rule_
The price grid; kind UNKNOWN where the library has no table.
std::optional< Decimal64 > lot_size_
The order quantity increment, if the broker publishes one.
SecurityType type_
The kind of security, which selects the price grid.
static PriceRule krx_equity()
Returns the KRX cash-equity tick table (January 2023 revision).
std::vector< WorkingOrder< Broker > > orders_
The open orders.
std::chrono::system_clock::time_point received_at_
When the listing was received, on this machine's clock.
std::vector< WorkingOrder< Broker > > working_
Orders still outstanding at the broker.
SellableBasis sellable_basis_
What the sellable_quantity_ figures on positions_ cover.
std::chrono::system_clock::time_point working_as_of_
When the working-order listing was received, on the same clock as as_of_.
std::chrono::system_clock::time_point as_of_
When the holdings were received, on this machine's clock.
std::vector< HoldingPosition > positions_
Every position row the broker reported.
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.