Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 51 additions & 7 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
# - performance-enum-size: narrowing public enums would change the ABI for little gain.
# - optin.performance.Padding: public parameter structs are ordered for readability and
# aggregate initialization, not layout.
# - modernize: the clang-tidy 18 checks are listed by name, so a newer clang-tidy cannot add
# errors CI never sees. Left out: use-trailing-return-type and avoid-c-arrays (style this
# code base does not follow; C arrays appear only at C API boundaries), use-nodiscard
# ([[nodiscard]] is placed deliberately), and use-auto (local types are spelled out; see
# tools/cpp_auto_audit.py).
Checks: >
-*,
bugprone-*,
Expand All @@ -12,19 +17,58 @@ Checks: >
performance-*,
-performance-enum-size,
portability-*,
modernize-use-nullptr,
modernize-use-override,
modernize-avoid-bind,
modernize-concat-nested-namespaces,
modernize-deprecated-headers,
modernize-deprecated-ios-base-aliases,
modernize-loop-convert,
modernize-macro-to-enum,
modernize-make-shared,
modernize-make-unique,
modernize-pass-by-value,
modernize-raw-string-literal,
modernize-redundant-void-arg,
modernize-replace-auto-ptr,
modernize-replace-disallow-copy-and-assign-macro,
modernize-replace-random-shuffle,
modernize-return-braced-init-list,
modernize-shrink-to-fit,
modernize-type-traits,
modernize-unary-static-assert,
modernize-use-bool-literals,
modernize-use-constraints,
modernize-use-default-member-init,
modernize-use-emplace,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-redundant-void-arg,
modernize-use-noexcept,
modernize-use-nullptr,
modernize-use-override,
modernize-use-starts-ends-with,
modernize-use-std-numbers,
modernize-use-std-print,
modernize-use-transparent-functors,
modernize-use-uncaught-exceptions,
modernize-use-using,
readability-braces-around-statements,
readability-const-return-type,
readability-container-size-empty,
readability-redundant-string-init,
readability-else-after-return,
readability-inconsistent-declaration-parameter-name,
readability-make-member-function-const,
misc-unused-using-decls,
readability-misleading-indentation,
readability-redundant-control-flow,
readability-redundant-member-init,
readability-redundant-smartptr-get,
readability-redundant-string-init,
readability-simplify-boolean-expr,
misc-definitions-in-headers,
cppcoreguidelines-virtual-class-destructor,
cppcoreguidelines-slicing
misc-unused-using-decls,
cppcoreguidelines-init-variables,
cppcoreguidelines-missing-std-forward,
cppcoreguidelines-prefer-member-initializer,
cppcoreguidelines-slicing,
cppcoreguidelines-virtual-class-destructor
WarningsAsErrors: '*'
HeaderFilterRegex: '.*/(include/kalshi(/detail)?|src(/api|/auth|/http|/models|/ws)?)/[^/]+$'
FormatStyle: file
57 changes: 57 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Docs

# Builds the Doxygen API reference on pull requests that touch it. Deploys it
# to GitHub Pages when dispatched, which release.yml does after it publishes a
# release, so only released headers are documented.
on:
pull_request:
paths:
- "include/**"
- "docs/**"
- "*.md"
- "Doxyfile"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: docs-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Doxygen
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends doxygen
- name: Build the API reference
run: make docs
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: github.event_name == 'workflow_dispatch'
with:
path: build-docs/html

deploy:
if: github.event_name == 'workflow_dispatch'
needs: build
# One deploy at a time, in order, so an older release never lands last.
concurrency:
group: pages-deploy
cancel-in-progress: false
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ jobs:
run: >-
gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME"
--notes-file "$RUNNER_TEMP/notes.md" --verify-tag
# Events from this workflow's token start no other workflows, except a
# dispatch, so the docs deploy is requested explicitly.
- name: Publish the API reference
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run docs.yml --repo "$GITHUB_REPOSITORY" --ref "$GITHUB_REF_NAME"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ __pycache__/
*~
.DS_Store
Thumbs.db

# Local Python environment for tools/
.venv/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- An API reference built from the headers with `make docs` and published to
<https://reddimus.github.io/kalshi-cpp/> for each release.
- `WebSocketClient` covers every channel and command in Kalshi's AsyncAPI
document: all 13 channels, `update_subscription` for markets, snapshots, CF
Benchmarks indices, and Pyth underlyings, and `list_subscriptions`. Message
Expand Down Expand Up @@ -61,6 +63,10 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Changed

- libwebsockets is found through its own CMake package when one is installed
(vcpkg, Homebrew, and Ubuntu ship one), with pkg-config as the fallback on
every platform. The installed `kalshiConfig.cmake` finds it the same way and
reports kalshi-cpp as not found, instead of stopping, when it is missing.
- CMake 3.31 or newer is required when Glaze is fetched (Glaze's own
minimum), or 3.21 with `KALSHI_USE_SYSTEM_GLAZE=ON`. Glaze 8.3 or newer is
enforced at compile time.
Expand Down
11 changes: 3 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,8 @@ configure_file(

find_package(OpenSSL 3.0 REQUIRED)
find_package(CURL REQUIRED)
if(WIN32)
find_package(libwebsockets CONFIG REQUIRED)
set(KALSHI_WEBSOCKETS_TARGET websockets_shared)
else()
find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBSOCKETS REQUIRED IMPORTED_TARGET libwebsockets)
set(KALSHI_WEBSOCKETS_TARGET PkgConfig::WEBSOCKETS)
endif()
include(cmake/KalshiWebsockets.cmake)
kalshi_find_websockets(TRUE)

# Glaze is a private, header-only build dependency; installed headers never
# include it. src/json.hpp rejects versions older than 8.3 at compile time,
Expand Down Expand Up @@ -178,6 +172,7 @@ install(EXPORT kalshiTargets
NAMESPACE kalshi::
DESTINATION ${KALSHI_INSTALL_CMAKEDIR})
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/KalshiWebsockets.cmake
${CMAKE_CURRENT_BINARY_DIR}/kalshiConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/kalshiConfigVersion.cmake
DESTINATION ${KALSHI_INSTALL_CMAKEDIR})
169 changes: 80 additions & 89 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,104 @@
# Contributing to kalshi-cpp
# Contributing

Thanks for your interest in contributing. This guide covers the local
build flow, code style expectations, and PR conventions used in this
repo. For security-vulnerability reports, see [SECURITY.md](SECURITY.md)
and do not open a public issue for those.
Report security problems privately as described in [SECURITY.md](SECURITY.md),
not in a public issue.

## Getting started
## Set up

```bash
git clone https://github.com/Reddimus/kalshi-cpp.git
cd kalshi-cpp

# One-time: install build deps (Ubuntu 24.04 example)
sudo apt install -y build-essential cmake clang-format \
libssl-dev libcurl4-openssl-dev libwebsockets-dev

make build # CMake configure + Release build
make test # Run unit tests (ctest)
```

macOS uses Homebrew (`brew install openssl curl libwebsockets`),
Windows uses vcpkg (the CI workflow has the exact invocations).

## Development workflow
On Ubuntu 24.04, whose apt CMake is older than the 3.31 this build needs:

```bash
make debug # Debug build in build-debug/
make test # Build and run the tests
make sanitize # ASan + UBSan
make tsan # ThreadSanitizer
make tidy # clang-tidy build
make lint # clang-format 18, cpp_auto_audit, generated-code check
make codegen # Regenerate the REST client from spec/openapi.yaml
make format # Apply clang-format in place
make bench # Google Benchmark suite
make coverage # lcov report (needs lcov)
make clean # Remove build directories
sudo apt install build-essential ninja-build pkg-config clang-format-18 python3-yaml \
pipx libssl-dev libcurl4-openssl-dev libwebsockets-dev
pipx install cmake && pipx ensurepath # then open a new shell
make test
```

Run `make lint` before pushing; CI runs the same checks. It needs
clang-format 18 and PyYAML (`python3 -m pip install pyyaml`).
On macOS, Homebrew's `llvm@18` provides clang-format 18 without putting it on
`PATH`, and PyYAML goes in a virtual environment:

## Generated code

`tools/codegen/generate.py` writes the REST client from `spec/openapi.yaml`
and the WebSocket messages from `spec/asyncapi.yaml`: `include/kalshi/api.hpp`,
`models.hpp`, and `ws_models.hpp`, `src/api/operations/`, `src/api/validate.hpp`,
`src/models/json_meta.hpp`, `src/ws/wire.hpp`, `tests/test_operation_routes.cpp`,
`tests/test_ws_messages.cpp`, `docs/operations.md`, and `docs/channels.md`.
Change the generator or a spec, run `make codegen`, and commit both. CI rejects stale
output. `docs/research.md` explains how to refresh the spec.
```bash
brew install cmake ninja pkg-config openssl libwebsockets llvm@18
python3 -m venv .venv && .venv/bin/pip install pyyaml
export CLANG_FORMAT="$(brew --prefix llvm@18)/bin/clang-format" PYTHON=.venv/bin/python
make test lint
```

## Code style
Windows builds use vcpkg; the `build-windows` job in `.github/workflows/ci.yml`
has the steps.

- **C++23** features encouraged: `std::expected<T, Error>` for all
error-returning operations, no exceptions in the public API.
- **No `auto`** for local variable declarations. Spell out the type so
reviewers can verify intent without IDE help. Carve-outs:
- Structured bindings: `auto& [k, v] = ...`
- Lambda closures: `auto callback = ...`
- Iterator-like results: `auto it = container.find(...)`
- **Formatting**: `.clang-format` (LLVM base, tabs, 100-col limit).
`make format` applies it.
- **Includes**: project headers first, then system headers
(enforced by clang-format `SortIncludes`).
- **JSON**: use Glaze for structured payloads. Keep hand-rolled scanners limited
to measured hot paths with focused parser and benchmark tests.

## PR conventions

- Branch names: `feat/...`, `fix/...`, `docs/...`, `chore/...`,
`test/...`, `build/...`, `ci/...`, `refactor/...`.
- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/):
`<type>(<scope>): <summary>`, for example:
`fix(ws): null-guard moved-from accessors`.
- Squash + delete branch on merge. PR titles become the squash commit
subject, so write them clearly.
- Update `CHANGELOG.md` under `## [Unreleased]` for any user-visible
change (new API, fix that consumers will notice, dep bump). Use the
Keep-a-Changelog sub-headers: Added / Changed / Fixed / Removed.
- CI must pass on all platforms (Ubuntu 24.04 + macOS + Windows) before
merge.

## Release process

Releases are cut from `main` via tag push:
## Everyday commands

```bash
# 1. Update CMakeLists.txt VERSION and move CHANGELOG.md entries into [X.Y.Z].
# release.yml refuses tags without a matching CHANGELOG section or passing CI.
# 2. Commit the version bump
git commit -am "chore(release): cut vX.Y.Z"
git push origin main

# 3. Tag and push the tag; release.yml creates the GitHub Release
git tag vX.Y.Z
git push origin vX.Y.Z
make test # Release build and tests
make debug # Debug build in build-debug/
make sanitize tsan # ASan + UBSan, ThreadSanitizer
make tidy # clang-tidy build
make lint # clang-format 18, explicit-type audit, generated-code check
make format # Apply clang-format
make codegen # Regenerate from spec/
make docs # Doxygen API reference in build-docs/html (needs Doxygen)
make bench # Google Benchmark suite
make consumers # Build installed and FetchContent consumers
make coverage # lcov report (needs lcov)
```

Semver: bump MINOR for new public API, PATCH for fixes/docs/CI.
Run `make format lint test` before pushing. `make install-hooks` runs format
and lint on every commit. CI adds the sanitizers, clang-tidy, and the consumer
check, on Linux, macOS, and Windows.

## Generated code

`tools/codegen/generate.py` writes the REST client from `spec/openapi.yaml` and
the WebSocket types from `spec/asyncapi.yaml`: `include/kalshi/api.hpp`,
`models.hpp`, and `ws_models.hpp`, `src/api/operations/`,
`src/api/validate.hpp`, `src/models/json_meta.hpp`, `src/ws/wire.hpp`,
`tests/test_operation_routes.cpp`, `tests/test_ws_messages.cpp`,
`docs/operations.md`, and `docs/channels.md`. Change the generator, a template
in `tools/codegen/`, or a spec, run `make codegen`, and commit the result.
`make lint` fails on stale output. [docs/research.md](docs/research.md)
explains how to refresh the specs.

## Reporting issues
## Code style

- **Bugs / feature requests**: open a GitHub issue with reproduction
steps + the kalshi-cpp version (`kalshi::VERSION`).
- **Security vulnerabilities**: see [SECURITY.md](SECURITY.md) for the
private reporting channel.
- Public functions return `kalshi::Result<T>` (`std::expected<T, Error>`) and
don't throw.
- Spell out local variable types. `auto` is fine for structured bindings,
lambdas, and iterators. Anything else needs an `// auto-ok: reason` comment
or an entry in `tools/cpp_auto_allowlist.txt`; `make lint` checks this.
- `.clang-format` sets the layout: tabs, 100 columns, project includes before
system includes.
- Glaze reads and writes JSON. `strip_null_members` is the only hand-written
scanner, and it runs only after a parse fails.
- Keep tests offline. Inject an `HttpTransport`, or use the local HTTP and
WebSocket servers in `tests/`.

## Pull requests

- Name branches `feat/`, `fix/`, `docs/`, `ci/`, `refactor/`, `test/`, or
`chore/`.
- Title PRs as [Conventional Commits](https://www.conventionalcommits.org/),
such as `fix(ws): keep subscriptions across reconnects`. PRs are
squash-merged, so the title becomes the commit subject.
- Note user-visible changes in `CHANGELOG.md` under `[Unreleased]`.

## Releases

1. Set `VERSION` in `CMakeLists.txt`, move the `[Unreleased]` notes into a new
`[X.Y.Z]` section, and update the `GIT_TAG` in `README.md`.
2. Merge that change to `main`.
3. Run `git tag vX.Y.Z && git push origin vX.Y.Z`. `release.yml` publishes the
GitHub release after CI passes on the tagged commit, and `docs.yml`
publishes the API reference.

While the version is 0.x, a minor release may break the API; patch releases
only fix things.

## License

By contributing, you agree your changes are licensed under the MIT
license that covers this repository.
Contributions are licensed under the repository's MIT license.
Loading
Loading