Appearance
Quickstart
This page walks a new integrator from a fresh workspace to a first Kairos analytics stream. Two language paths are shown — the public Rust client and the Python facade. Both ride the same analytics engine; pick the runtime that matches your downstream stack.
Prerequisites
- An account with the upstream market-data feed Kairos is wired against, exposed via the
THETADATA_EMAILandTHETADATA_PASSWORDenvironment variables (or constructor arguments). - For the Rust path: a toolchain matching
rust-toolchain.tomlin the repo. - For the Python path: CPython 3.10 or newer. The wheel ships an
abi3extension that imports against the system Python.
Add the dependency
Kairos is distributed privately at this stage; the Rust crate and the Python wheel resolve against the in-house registry rather than crates.io or PyPI. The snippets below show the manifest / install shape consumers will use once public release is enabled.
Rust
toml
[dependencies]
kairos = "0.1"The streaming live namespace is the production-ready path today. The historical-mode and replay-mode Cargo features opt into the preview namespaces.
Python
sh
pip install kairos-thetadataThe import is kairos_thetadata.
Connect
Rust
rust
use kairos::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let email = std::env::var("THETADATA_EMAIL")?;
let password = std::env::var("THETADATA_PASSWORD")?;
let client = Client::connect((email, password))?;
Ok(())
}Client::connect opens the streaming session with the default Adaptive wait strategy. Use Client::builder() to override that before connecting.
Python
python
import kairos_thetadata
creds = kairos_thetadata.Credentials.from_env()
client = kairos_thetadata.Client.connect(creds)Credentials.from_env() reads THETADATA_EMAIL / THETADATA_PASSWORD from the environment. The password is held in a zero-on-drop buffer through every layer.
Subscribe to your first analytic
Volatility (rolling realised + implied parity over the option chain) is the one-minute on-ramp.
Rust
rust
use kairos::{Client, VolatilityRow};
# fn run() -> Result<(), Box<dyn std::error::Error>> {
# let client = Client::connect(("me@example.com", "secret"))?;
let sub = client
.live()
.volatility(["SPX"])
.on_event(|row: &VolatilityRow| {
println!("{} vix={}", row.ms_of_day, row.value);
})?;
// … later …
sub.unsubscribe();
# Ok(())
# }Each per-analytic builder is generated by one macro line — the surface stays uniform across the full catalogue. The builder collects the contract filter and any cadence setters, encodes the analytic's request, calls the engine over a stable C ABI, and delivers each emission to your callback as a typed VolatilityRow. The .on_event call returns an EventSubscription handle; call unsubscribe() to stop.
Python
python
import kairos_thetadata as kt
client = kt.Client.connect(kt.Credentials.from_env())
def on_event(row):
print(row.symbol, row.iv)
with client.live().volatility(["SPX"]).on_event(on_event) as sub:
sub.wait(timeout_seconds=60.0).on_event(callback) is the one documented consumption path: it hands each tick to the callback as it arrives and returns a handle that is both a context manager (with ... as sub: drains on exit) and .unsubscribe()-able. Chain the per-field setters before it to override the analytic's spec (.window_size(60), .annual_dividend(0.014)); the universe is named in the accessor argument. See universe selection.
Analytics follow a suppress-when-undefined emission contract: the first tick an analytic emits is its first defined reading, so you never filter not-ready rows out of the stream. The universe is named in the analytic accessor argument, with for_index / for_sector for published-index and SEC-sector universes — see universe selection.
Drive the same analytic from a historical query
Historical streams are bounded: every (contract, date) unit emits exactly one terminal frame whether or not it produced any rows. Dates use the engine's integer-time convention (YYYYMMDD as i32).
Rust
Preview. The snippet below uses the
historical-modeCargo feature. Over the production C-ABI link the call currently fails closed withLinkError::HistoricalUnwired— the configuration knob the historical path needs is not yet wired on the scalar wire params and is tracked as a documented follow-up. The accessor is opt-in so production FFI builds cannot reach the unwired path. Run the example today through the in-tree test link (Client::with_link+ the in-process engine fixture).
rust
# #[cfg(feature = "historical-mode")]
# fn run(client: kairos::Client) -> Result<(), Box<dyn std::error::Error>> {
use kairos::VolatilityRow;
let sub = client
.historical(20260105, 20260109)
.volatility(["SPX"])
.on_event(|row: &VolatilityRow| println!("{} {}", row.ms_of_day, row.value))?;
# let _ = sub;
# Ok(())
# }The historical accessor is gated behind the historical-mode Cargo feature while the preview surface stabilises. On a bounded namespace the callback fires once per row over the finite result set.
Python
python
batch = (
client.historical()
.volatility(["SPX"])
.query(start=20260105, end=20260109)
)
# `batch` is an Arrow record batch. Lift into pandas, polars,
# or any Arrow-aware downstream.
print(batch.schema, batch.num_rows)Drive the same analytic from a recorded replay
Replay drives the analytic over a dataset the engine link holds. The replay output equals the live output frame for frame — the replay-equivalence contract, enforced on every commit by a property-test.
Preview. The snippet below uses the
replay-modeCargo feature. Over the production C-ABI link the call currently requires a dataset-staging entry that is not yet exposed on the public C ABI (a documented follow-up). Run the example today through the in-tree test link (Client::with_link+ the in-process engine fixture), which routes the dataset through the engine in-process.
rust
# #[cfg(feature = "replay-mode")]
# fn run(client: kairos::Client) -> Result<(), Box<dyn std::error::Error>> {
use kairos::VolatilityRow;
let sub = client
.replay()
.volatility(["SPX"])
.on_event(|row: &VolatilityRow| println!("{} {}", row.ms_of_day, row.value))?;
# let _ = sub;
# Ok(())
# }The replay accessor is gated behind the replay-mode Cargo feature while the preview surface stabilises.
Next steps
- Consuming events — the typed callback, the subscription handle, and panic isolation.
- Analytics catalogue — every shipped analytic with formula, citation, and worked example.
- API reference — every type, every field, every method.