diff --git a/README.md b/README.md index b0db9a9..8edbc00 100644 --- a/README.md +++ b/README.md @@ -5,35 +5,220 @@ [![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -A modern C++23 client library for the [Kalshi](https://kalshi.com) prediction market API. +A modern C++23 client library for the [Kalshi](https://kalshi.com) prediction +market API. It gives you typed methods for the full Kalshi v2 REST surface plus +real-time WebSocket streaming, with RSA-PSS request signing built in. Errors are +returned as `std::expected` — no exceptions on the hot path — and the +library drops into any CMake project via `FetchContent` pinned to a release tag. -## Upstream SDK / spec discovery +## Features -These commands are useful for auditing published SDK artifacts and specs; see `docs/research.md` for findings and parity matrix. +- **RSA-PSS authentication** — secure API signing compatible with Kalshi's auth + scheme (SHA-256, PSS padding). +- **Modern C++23** — `std::expected` for every return (no exceptions), + plus concepts and other modern features. +- **Full REST API** — typed methods for all Kalshi v2 endpoints (markets, + portfolio, orders, RFQ/quotes, subaccounts, and more). +- **WebSocket streaming** — real-time orderbook, trade, fill, and lifecycle + events with full depth parsing. +- **Clean, layered architecture** — modular static libs (core → auth → http → + models → ws → api) behind one `kalshi::kalshi` interface target. +- **Fast JSON** — [Glaze](https://github.com/stephenberry/glaze) for the write + path; hand-rolled scanners for the read hot path. +- **CMake + Make** — easy build system with convenient `Makefile` wrappers and a + drop-in `FetchContent` / `find_package` integration. + +## Quick start + +The primary way to consume kalshi-cpp is **CMake `FetchContent` pinned to a +release tag** — that is how the downstream services consume it, and it needs no +system install of the SDK itself. + +### 1. Add it to your CMake project -```bash -# TypeScript SDK (npm) - Community, WebSocket-only -npm view kalshi name version repository homepage dist.tarball -npm pack kalshi@0.0.5 -# then: tar -xzf kalshi-0.0.5.tgz - -# Python SDK sync (PyPI) - Official -pip download --no-deps kalshi-python -# or specific version: pip download --no-deps kalshi-python==2.1.4 - -# Python SDK async (PyPI) - Official -pip download --no-deps kalshi-python-async -# or specific version: pip download --no-deps kalshi-python-async==3.2.0 +```cmake +include(FetchContent) +FetchContent_Declare( + kalshi + GIT_REPOSITORY https://github.com/Reddimus/kalshi-cpp.git + GIT_TAG v0.4.8 # pin a tagged release; bump per "Versioning" below +) +# Consumers don't need the SDK's own tests or example binaries: +set(KALSHI_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(KALSHI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(kalshi) + +target_link_libraries(myapp PRIVATE kalshi::kalshi) ``` -## Features +You still need a few system libraries on the build host (OpenSSL, libcurl, +libwebsockets) — they are resolved with `find_package`/`pkg-config`, not vendored. +See [Prerequisites](#prerequisites) for the one-line install. + +### 2. Get API credentials + +1. Go to [Kalshi API settings](https://kalshi.com/settings/api). +2. Generate an API key pair and download the private key PEM file. +3. Note the **key ID** shown on the page. + +The examples read them from `KALSHI_API_KEY_ID` and `KALSHI_API_KEY_FILE`. + +### 3. Make your first authenticated call + +A minimal, read-only program that authenticates and lists markets: + +```cpp +#include +#include + +int main() { + auto signer = kalshi::Signer::from_pem_file("your-key-id", "path/to/key.pem"); + if (!signer) { + std::cerr << "auth setup failed: " << signer.error().message << "\n"; + return 1; + } + kalshi::KalshiClient client(kalshi::HttpClient(std::move(*signer))); + + auto markets = client.get_markets(); + if (!markets) { + std::cerr << "request failed: " << markets.error().message << "\n"; + return 1; + } + for (const auto& m : markets->items) { + std::cout << m.ticker << ": " << m.title << "\n"; + } + return 0; +} +``` + +That's the whole loop: create a signer from your PEM, wrap it in a client, call a +typed method, and check the `std::expected`. See [Usage](#usage) for order +placement and WebSocket streaming, or the runnable programs in +[`examples/`](examples/README.md) (`basic_usage.cpp`, `get_markets.cpp`, +`get_daily_temp.cpp`). + +## Versioning & staying up to date + +kalshi-cpp follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +(`MAJOR.MINOR.PATCH`). While the package is pre-1.0 (`0.y.z`), a **minor** bump +(`0.4 → 0.5`) may carry breaking changes — review the +[CHANGELOG](CHANGELOG.md) before bumping the minor. + +- **Discover a new release** — releases appear on the + [Releases page](https://github.com/Reddimus/kalshi-cpp/releases) (the Release + badge above links there). They are auto-published from a `vX.Y.Z` tag push, and + the release body is the matching [CHANGELOG.md](CHANGELOG.md) section + ([Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format). +- **Understand what changed** — read [CHANGELOG.md](CHANGELOG.md), the + authoritative change log. +- **Bump your pin** — pick the new tag, edit `GIT_TAG vX.Y.Z` in your + `FetchContent_Declare`, then re-configure. `FetchContent` caches the resolved + tag, so re-run `cmake` (or clear `build/_deps`) to actually move. +- **Verify at compile time** — `include/kalshi/version.hpp` is generated by + CMake `configure_file()` from `project(kalshi-cpp VERSION ...)` (the single + source of truth), so `kalshi::VERSION` can never drift from the tag you pin. + Guard it if you depend on a minimum: + + ```cpp + #include + static_assert(kalshi::VERSION_MAJOR == 0 && kalshi::VERSION_MINOR >= 4, + "kalshi-cpp: expected >= 0.4.x — check your FetchContent GIT_TAG"); + ``` + + The release workflow refuses to publish unless the pushed `vX.Y.Z` tag equals + `PROJECT_VERSION`, so a tag and its embedded `kalshi::VERSION` are always + consistent. + +## Usage + +### REST API + +```cpp +#include + +int main() { + // Create signer with your API key + auto signer = kalshi::Signer::from_pem_file("your-key-id", "path/to/key.pem"); + if (!signer) { + std::cerr << "Failed: " << signer.error().message << "\n"; + return 1; + } + + // Create HTTP client and API client + kalshi::HttpClient http(std::move(*signer)); + kalshi::KalshiClient client(std::move(http)); + + // Get markets + auto markets = client.get_markets(); + if (markets) { + for (const auto& m : markets->items) { + std::cout << m.ticker << ": " << m.title << "\n"; + } + } + + // Get account balance + auto balance = client.get_balance(); + if (balance) { + std::cout << "Balance: $" << balance->balance / 100.0 << "\n"; + } + + // Create an order + kalshi::CreateOrderParams order; + order.ticker = "MARKET-TICKER"; + order.side = kalshi::Side::Yes; + order.action = kalshi::Action::Buy; + order.type = "limit"; + order.count = 10; + order.yes_price = 50; // 50 cents + + auto result = client.create_order(order); + if (result) { + std::cout << "Order created: " << result->order_id << "\n"; + } + + return 0; +} +``` + +### WebSocket streaming + +```cpp +#include + +int main() { + auto signer = kalshi::Signer::from_pem_file("your-key-id", "path/to/key.pem"); + if (!signer) return 1; + + // Create WebSocket client + kalshi::WebSocketClient ws(*signer); + + // Set up callbacks + ws.on_message([](const kalshi::WsMessage& msg) { + std::visit([](auto&& m) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + std::cout << "Delta: " << m.market_ticker + << " " << m.price << " " << m.delta << "\n"; + } + }, msg); + }); + + ws.on_state_change([](bool connected) { + std::cout << (connected ? "Connected" : "Disconnected") << "\n"; + }); + + // Connect and subscribe + if (ws.connect()) { + auto sub = ws.subscribe_orderbook({"MARKET-TICKER"}); + // ... run event loop ... + } + + return 0; +} +``` -- **RSA-PSS Authentication** - Secure API signing compatible with Kalshi's auth scheme -- **Modern C++23** - Uses `std::expected`, concepts, and other modern features -- **Clean Architecture** - Modular design with separate auth, HTTP, and model layers -- **Full REST API** - Typed methods for all Kalshi v2 endpoints -- **WebSocket Streaming** - Real-time orderbook, trade, fill, and lifecycle events -- **CMake + Make** - Easy build system with convenient Makefile wrappers +See [examples/README.md](examples/README.md) for full live-streaming usage, +including the `get_daily_temp` example with `--stream`. ## Architecture @@ -51,17 +236,24 @@ graph TD B --> H[OpenSSL] D --> I[libwebsockets] end - + subgraph API["Kalshi API"] J[REST API] K[WebSocket] end - + E --> J D --> K ``` -## Directory Structure +The library is a stack of layered static libraries — +`kalshi_core` → `kalshi_auth` → `kalshi_http` → `kalshi_models` → +`kalshi_ws` → `kalshi_api` — exposed through a single `kalshi` INTERFACE +target. JSON is serialized with Glaze on the write path; the WebSocket-receive +and REST-response hot paths use hand-rolled string scanners for throughput and +v2-schema correctness. + +### Directory structure ```mermaid graph LR @@ -89,28 +281,47 @@ graph LR | `examples/` | Usage examples | | `docs/` | Documentation and research | -## Quick Start +## Building from source / development + +You only need this section if you are **contributing to the SDK** or running its +tests/benchmarks locally. Consumers should use the +[Quick start](#quick-start) `FetchContent` path instead. ### Prerequisites - C++23 compatible compiler (GCC 13+, Clang 16+) - CMake 3.20+ - clang-format (required for `make lint`) -- OpenSSL development libraries -- libcurl development libraries -- libwebsockets development libraries +- OpenSSL, libcurl, and libwebsockets development libraries + +Install the system dependencies: + +```bash +# Debian / Ubuntu +sudo apt-get install -y build-essential cmake pkg-config clang-format \ + libssl-dev libcurl4-openssl-dev libwebsockets-dev + +# macOS (Homebrew) +brew install cmake pkg-config openssl@3 curl libwebsockets clang-format +# If CMake can't find OpenSSL on macOS, point it at the brew prefix: +# cmake -S . -B build -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3) +``` + +OpenSSL, libcurl, and libwebsockets are resolved via `find_package`/`pkg-config` +from the system (not vendored), so they must be present on the build host even +when you consume the SDK via `FetchContent`. ### Build ```bash # Clone the repository -git clone https://github.com/your-org/kalshi-cpp.git +git clone https://github.com/Reddimus/kalshi-cpp.git cd kalshi-cpp # Build (Release with -O3 and LTO by default) make build -# Run tests (GoogleTest, 155 tests) +# Run tests (GoogleTest, 160+ tests) make test # Generate code coverage report (requires lcov) @@ -123,14 +334,17 @@ make bench make lint ``` -`make lint` is fail-fast: it exits non-zero if `clang-format` is missing or if any source/header file violates formatting rules. +`make lint` is fail-fast: it exits non-zero if `clang-format` is missing or if +any source/header file violates formatting rules. -### Build Options +### Build options -The SDK supports several CMake options for optimization: +The SDK supports several CMake options: | Option | Default | Description | | ------ | ------- | ----------- | +| `KALSHI_BUILD_TESTS` | ON | Build the GoogleTest suite (set OFF when consuming via FetchContent) | +| `KALSHI_BUILD_EXAMPLES` | ON | Build the example binaries (set OFF when consuming via FetchContent) | | `KALSHI_ENABLE_LTO` | ON | Enable Link Time Optimization for Release builds | | `KALSHI_NATIVE_ARCH` | OFF | Use `-march=native` for CPU-specific tuning (not portable) | | `KALSHI_ENABLE_SANITIZERS` | OFF | Enable AddressSanitizer + UndefinedBehaviorSanitizer | @@ -148,28 +362,10 @@ cmake -S . -B build-san -DCMAKE_BUILD_TYPE=Debug -DKALSHI_ENABLE_SANITIZERS=ON cmake --build build-san && ctest --test-dir build-san ``` -### Install & use as a package +### Install to a prefix + `find_package` -Two consumption paths: - -#### A. CMake FetchContent (recommended — no system install) - -```cmake -include(FetchContent) -FetchContent_Declare( - kalshi - GIT_REPOSITORY https://github.com/Reddimus/kalshi-cpp.git - GIT_TAG v0.4.8 # pin a tagged release -) -# Suppress upstream tests + examples in your service builds -set(KALSHI_BUILD_TESTS OFF CACHE BOOL "" FORCE) -set(KALSHI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(kalshi) - -target_link_libraries(myapp PRIVATE kalshi::kalshi) -``` - -#### B. Install to a prefix + `find_package` +`FetchContent` (see [Quick start](#quick-start)) is recommended for almost all +consumers. Use `find_package` only when you install the SDK to a system prefix: ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @@ -184,95 +380,13 @@ find_package(kalshi CONFIG REQUIRED) target_link_libraries(myapp PRIVATE kalshi::kalshi) ``` -### Usage - -#### REST API - -```cpp -#include - -int main() { - // Create signer with your API key - auto signer = kalshi::Signer::from_pem_file("your-key-id", "path/to/key.pem"); - if (!signer) { - std::cerr << "Failed: " << signer.error().message << "\n"; - return 1; - } - - // Create HTTP client and API client - kalshi::HttpClient http(std::move(*signer)); - kalshi::KalshiClient client(std::move(http)); +Note: the installed `kalshiConfig.cmake` calls +`find_dependency(OpenSSL/CURL/PkgConfig)` and +`pkg_check_modules(libwebsockets REQUIRED)`, so the consuming machine needs +`pkg-config` and the libwebsockets `.pc` file available at `find_package` time +(not just when the SDK was built). - // Get markets - auto markets = client.get_markets(); - if (markets) { - for (const auto& m : markets->items) { - std::cout << m.ticker << ": " << m.title << "\n"; - } - } - - // Get account balance - auto balance = client.get_balance(); - if (balance) { - std::cout << "Balance: $" << balance->balance / 100.0 << "\n"; - } - - // Create an order - kalshi::CreateOrderParams order; - order.ticker = "MARKET-TICKER"; - order.side = kalshi::Side::Yes; - order.action = kalshi::Action::Buy; - order.type = "limit"; - order.count = 10; - order.yes_price = 50; // 50 cents - - auto result = client.create_order(order); - if (result) { - std::cout << "Order created: " << result->order_id << "\n"; - } - - return 0; -} -``` - -#### WebSocket Streaming - -```cpp -#include - -int main() { - auto signer = kalshi::Signer::from_pem_file("your-key-id", "path/to/key.pem"); - if (!signer) return 1; - - // Create WebSocket client - kalshi::WebSocketClient ws(*signer); - - // Set up callbacks - ws.on_message([](const kalshi::WsMessage& msg) { - std::visit([](auto&& m) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - std::cout << "Delta: " << m.market_ticker - << " " << m.price << " " << m.delta << "\n"; - } - }, msg); - }); - - ws.on_state_change([](bool connected) { - std::cout << (connected ? "Connected" : "Disconnected") << "\n"; - }); - - // Connect and subscribe - if (ws.connect()) { - auto sub = ws.subscribe_orderbook({"MARKET-TICKER"}); - // ... run event loop ... - } - - return 0; -} -``` - -## Build Targets +### Build targets | Target | Description | | ------ | ----------- | @@ -283,7 +397,7 @@ int main() { | `kalshi_api` | REST API client with typed methods | | `kalshi_ws` | WebSocket streaming client | | `kalshi_models` | Data models | -| `kalshi_tests` | Test executable (GoogleTest, 155 tests) | +| `kalshi_tests` | Test executable (GoogleTest, 160+ tests) | | `kalshi_parse_benchmark` | JSON parse-throughput regression guard (1000 iters; ctest-invoked) | ## API Coverage @@ -417,9 +531,15 @@ See [examples/README.md](examples/README.md) for live streaming usage. ## Documentation -- [Research Notes](docs/research.md) - Analysis of official SDKs, API behavior, and parity matrix -- [Examples](examples/README.md) - Usage examples including WebSocket streaming -- [API Reference](docs/) - (Coming soon) +- [Research Notes](docs/research.md) - Official-SDK analysis, the parity matrix, + and the upstream artifact-audit (npm/PyPI) discovery commands +- [API Reference](docs/README.md) - Component overview with auth / REST / WS + snippets, pagination, rate-limit, and retry behavior +- [Examples](examples/README.md) - Runnable usage examples, including WebSocket + streaming +- [Changelog](CHANGELOG.md) - All notable changes (Keep a Changelog + SemVer) +- [Releases](https://github.com/Reddimus/kalshi-cpp/releases) - Published + tagged releases ## Contributing @@ -433,8 +553,14 @@ make lint # clang-format --dry-run -Werror make format # clang-format -i (call before pushing) ``` -CI runs the same lint+build+test on push and PR (Ubuntu 24.04 + -macos-latest); see `.github/workflows/ci.yml`. +CI runs lint + build + test on Ubuntu 24.04 and build-only on macos-latest and +windows-latest, on push and PR; lint runs only on Linux (clang-format output +drifts between platform versions). See `.github/workflows/ci.yml`. + +**Releasing**: bump `project(kalshi-cpp VERSION ...)` in `CMakeLists.txt`, add +the matching `## [X.Y.Z]` section to `CHANGELOG.md`, then push tag `vX.Y.Z` — +`.github/workflows/release.yml` verifies `tag == PROJECT_VERSION` and publishes +the release from the CHANGELOG section automatically. ## License @@ -443,6 +569,6 @@ See [LICENSE](LICENSE) for details. ## References - [Kalshi API Documentation](https://docs.kalshi.com) -- [Python SDK (sync)](https://pypi.org/project/kalshi-python/) (PyPI) - v2.1.4 (official) -- [Python SDK (async)](https://pypi.org/project/kalshi-python-async/) (PyPI) - v3.2.0+ (official) -- [TypeScript SDK](https://www.npmjs.com/package/kalshi) (npm) - v0.0.5 (community, WebSocket-only) +- Official SDK packages, versions, and the full parity matrix: + see [docs/research.md](docs/research.md) (kept as the single source of truth + to avoid version drift). diff --git a/docs/research.md b/docs/research.md index 74bc422..542a1a0 100644 --- a/docs/research.md +++ b/docs/research.md @@ -20,6 +20,29 @@ This document summarizes findings from analyzing the official Kalshi SDK impleme > The older community npm package named `kalshi` is no longer the correct > parity target for this SDK. +### Reproducing the artifact audit + +The version and ownership facts above were derived by fetching each published +SDK artifact directly; re-run these commands to re-audit upstream after a +release. + +```bash +# TypeScript SDK (npm) - official, see https://docs.kalshi.com/typescript-sdk +npm view kalshi-typescript name version repository homepage dist.tarball + +# Python SDK sync (PyPI) - official +pip download --no-deps kalshi-python-sync +# or a specific version: pip download --no-deps kalshi-python-sync==3.16.0 + +# Python SDK async (PyPI) - official +pip download --no-deps kalshi-python-async +# or a specific version: pip download --no-deps kalshi-python-async==3.16.0 +``` + +After downloading, unpack each artifact (e.g. `tar -xzf ` for npm, +`unzip ` for PyPI) and diff the generated endpoint/model surface +against the [SDK Parity Matrix](#sdk-parity-matrix) below. + ### SDK Ownership - **Python SDKs** (`kalshi-python-sync`, `kalshi-python-async`): Official