![]() |
algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
|
KisClient, KiwoomClient and TossClient are the layer underneath the broker port. Strategy code should not name them — that is what the port is for — but a program that needs one broker's particular verb, or that is not running a strategy at all, talks to them.
Every verb exists twice: an asynchronous form taking a completion handler, and a blocking facade (KisBlockingClient, KiwoomBlockingClient, TossBlockingClient) that drives the same implementation on the caller's thread and keeps the HTTPS connection between sequential calls. Both apply the same normalizers, so they cannot disagree about what a broker response means.
The brokers expose the same broker-neutral results while retaining concrete client types; this page covers KIS and Toss in detail, and Kiwoom's own examples are example-kiwoom-*. The first supported instrument scope is Korean equities.
examples/example-kis-quote.cpp is the complete runnable version. It reads KIS_APP_KEY, KIS_APP_SECRET, and KIS_DEMO from the environment: unset or 1 is the 모의투자 endpoints, 0 is live, and anything else refuses to load. The default symbol is Samsung Electronics (005930); pass another Korean equity symbol as the first argument.
examples/example-tossinvest-quote.cpp provides the same workflow for Toss Securities. It reads TOSS_CLIENT_ID and TOSS_CLIENT_SECRET from the environment.
KisBlockingClient and TossBlockingClient provide blocking convenience APIs. They drive the same async implementation on the caller's thread and retain the HTTP connection between sequential calls.
examples/example-kis-account.cpp loads KIS credentials from KIS_APP_KEY and KIS_APP_SECRET, selects the live or demo environment with KIS_DEMO (demo unless it is 0), and selects an account using KIS_CANO plus optional KIS_ACNT_PRDT_CD (default 01). It requests and prints normalized positions and KRW totals without displaying the account number.
An account's keys can also live in one JSON file per account, in a directory you choose and pass at run time: --credentials DIR to example-live-program kis and the example-kis-* programs that call KIS (not the offline example-kis-place-order), or ATL_CREDENTIALS_DIR, which example-portable-* and example-strategy-live also read. They then read DIR/kis/demo.json, or DIR/kis/real.json when KIS_DEMO=0, with KisConfig::from_json and KisAccount::from_json (app_key, app_secret, account_number, account_product_code, demo). A file whose "demo" contradicts its name is refused, and a directory without the file stops the program rather than falling back to the environment. Keep the files chmod 600.
A KIS demo host refuses reference data (CTPF1002R) and the holiday calendar (CTCA0903R) with EGW02006, "모의투자 TR 이 아닙니다". Both are market data, the same from any account. So live::SessionSource takes a second session for its four market reads (bars, quotes, price limits, the calendar), and live_program::trade and observe take one for those and for reference data. example-live-program kis on demo builds one when DIR/kis/real.json exists, with an empty KisAccount: the KIS client refuses every account request and order through it before any I/O (invalid_account). --market-data toss reads them from Toss instead (DIR/tossinvest/real.json, no account selected), and only on demo. Either way the run knows the market phase, so with --live it trades on demo. The example paces every KIS client at one request a second (kis_credentials::paced_options): faster, KIS refuses reads with EGW00201, and the real keys' holiday calendar takes about 20 s of pages at startup. Toss's daily price band can differ from KIS's for the same stock: after the close Toss already publishes the next session's band while KIS keeps the day's (observed 2026-10-01). The band clamps limit prices, so KIS may refuse an order priced from it.
Kiwoom issues one key per account: a Korean-stock and a US-stock account each have a key, and no request carries an account number. So the Kiwoom examples (example-kiwoom-* and example-live-program kiwoom) read one file per key: DIR/kiwoom/demo-<label>.json, or real-<label>.json when KIWOOM_DEMO=0, with KiwoomConfig::from_json (appkey, secretkey, demo). --kiwoom-account LABEL picks the file (default kr; lowercase letters, digits and -). As for KIS, a directory without the file stops the program, a file whose "demo" contradicts its name is refused, and with a directory the KIWOOM_* variables are not read, so setting ATL_CREDENTIALS_DIR for KIS also moves Kiwoom to its files. Without a directory --kiwoom-account is refused. The adapter calls domestic-stock endpoints only, so a US account's key authenticates, and its requests answer as Kiwoom answers them for that account. Toss trading still reads its environment variables, and example-live-program refuses --credentials for it.
examples/example-tossinvest-account.cpp loads TOSS_CLIENT_ID and TOSS_CLIENT_SECRET, discovers brokerage accounts, prints masked account numbers, and requests holdings for the first account. To select another discovered account, pass its displayed sequence as the first argument.
A Toss key holds one access token at a time: issuing a token revokes the one issued before it. Two programs running with the same key therefore revoke each other's token. When Toss refuses a token (HTTP 401), the request fails, and TossClient asks for a new token on the next request. The refused request is not sent again.
Both brokers expose order placement, modification, cancellation, and order inquiry through matching async and blocking entry points.
Read these before using the order clients.
examples/example-kis-orders.cpp is read-only. It lists every modifiable or cancellable order with its amendable quantity and branch code, then prints today's orders and executions. It uses the same environment variables as the KIS account example.
examples/example-tossinvest-orders.cpp is also read-only. It prints each open order on one line, then requests only the first page of closed orders and reports whether another cursor is available. It does not automatically exhaust closed-order history because both order-list requests use Toss's ORDER_HISTORY rate-limit group. It uses the same environment variables as the Toss account example. Pass an account sequence as the first argument to avoid an additional account-discovery request; without one, the example selects the first returned account.
examples/example-kis-place-order.cpp only constructs and prints an OrderRequest. It never creates a client, loads credentials, reads environment variables, or performs network I/O, so it cannot submit an order. Every argument is optional and positional:
With no arguments it shows a one-share limit buy of 005930 at KRW 70,000. The limit price applies only to LIMIT; a MARKET dry-run must omit it.
This executable deliberately demonstrates request construction rather than broker submission. Applications place orders through KisBlockingClient::place_order or TossBlockingClient::place_order; for Toss, OrderRequest::client_order_id_ carries the idempotency key, and KIS refuses an order that sets it.
A stop-limit order is placeable at KIS (ORD_DVSN 22, trigger in CNDT_PRIC) and at Kiwoom (trde_tp 28, trigger in cond_uv) through OrderRequest::trigger_price_, which a stop-limit order requires beside its limit and every other basis refuses. Both adapters read the trigger back onto OrderSnapshot::trigger_price_.
On an amendment, OrderShape::trigger_price_ follows the same rule as the condition and the session: set means the new trigger, unset means keep the resting one — but only when both the resting order and the new shape are stop-limit orders (amended_trigger_price). A change of basis into a stop must state a trigger, a change out of one carries none, and an instruction with no resting shape inherits nothing. reprice and cancel_all restate the resting trigger explicitly.
A broker whose BrokerCaps::has_trigger_price is false, Toss included, never receives a trigger on a placement or a modification: the session refuses it as unsupported_trigger_price. The session never refuses a cancellation for carrying one, and cancel_all restates the resting trigger; Kiwoom alone refuses a cancellation that states a different trigger, because kt10003 cannot change one on the way out.
Toss amount-based order placement, which is also how Toss buys a fraction — an amount-based order is read back with its amount (OrderSnapshot::order_amount_, rounded half to even to 18 significant digits when Toss states more than a Decimal64 holds), and placing one is outstanding. A fractional quantity is placed only where Toss accepts one: a US market sell, to six decimal places, during the regular session until an hour before the close. Toss conditional orders. KIS credit, reserved and overseas orders. KIS's 시간외 단일가 (07): the inquiry publishes it, the order sheet does not. KIS history older than three months, and real-time execution notifications.
The broker port is the layer above these clients, and is what strategy-side code should be written against. Binding a broker is the operator's view of putting one behind a running strategy.