Skip to content

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_EMAIL and THETADATA_PASSWORD environment variables (or constructor arguments).
  • For the Rust path: a toolchain matching rust-toolchain.toml in the repo.
  • For the Python path: CPython 3.10 or newer. The wheel ships an abi3 extension 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-thetadata

The 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-mode Cargo feature. Over the production C-ABI link the call currently fails closed with LinkError::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-mode Cargo 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

Proprietary. All rights reserved.