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
78 changes: 76 additions & 2 deletions API_REFERENCE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Kyro API Reference

Request/response documentation for every modular method: **exchange**, **markets**, **events**, **orders**, **portfolio**.
Request/response documentation for every modular method: **exchange**, **markets**, **events**, **orders**, **portfolio**, **search**.

- **Import:** `from kyro.rest import exchange, markets, events, orders, portfolio`
- **Import:** `from kyro.rest import exchange, markets, events, orders, portfolio, search`
- **Client:** Pass `RestClient` as the first argument: `await exchange.get_exchange_status(client)`
- **Base path:** `{KyroConfig.base_url}` (e.g. `https://api.elections.kalshi.com/trade-api/v2`)
- **Auth:** Endpoints marked *Auth required* need `KyroConfig(auth_headers={...})` with KALSHI-ACCESS-KEY, TIMESTAMP, SIGNATURE.
Expand Down Expand Up @@ -532,6 +532,32 @@ data = await events.get_event_metadata(client, "KXBTC")

---

### `get_event_candlesticks`

**HTTP:** `GET /series/{series_ticker}/events/{event_ticker}/candlesticks`
**Auth:** No

**Usage:**
```python
data = await events.get_event_candlesticks(
client,
"KXBTC",
"KXBTC-24JAN15",
start_ts=1704067200,
end_ts=1704153600,
period_interval=60,
limit=24,
)
```

If `start_ts`/`end_ts` omitted, uses last 24h.

**Query:** `start_ts`, `end_ts` (Unix), `period_interval` (1|60|1440 minutes), `limit`, `include_latest_before_start`

**Response (200):** `{ "candlesticks": [ { "start_ts", "end_ts", "open", "high", "low", "close", "volume" }, ... ] }` per Kalshi

---

### `get_multivariate_events`

**HTTP:** `GET /events/multivariate`
Expand All @@ -553,6 +579,40 @@ data = await events.get_multivariate_events(client, limit=100, cursor=None)

---

## Search

No auth unless noted.

---

### `get_sports_filters`

**HTTP:** `GET /search/filters_by_sport`
**Auth:** No

**Usage:**
```python
data = await search.get_sports_filters(client)
```

**Response (200):** sport-based filter metadata for search/discovery (structure per Kalshi)

---

### `get_tags_by_categories`

**HTTP:** `GET /search/tags_by_categories`
**Auth:** No

**Usage:**
```python
data = await search.get_tags_by_categories(client)
```

**Response (200):** tags grouped by category for search/filtering (structure per Kalshi)

---

## Orders

All order endpoints **require auth**.
Expand Down Expand Up @@ -856,6 +916,20 @@ All portfolio endpoints **require auth**.

---

### `get_portfolio`

**HTTP:** `GET /portfolio`
**Auth:** Yes

**Usage:**
```python
data = await portfolio.get_portfolio(client)
```

**Response (200):** portfolio summary (structure per Kalshi). If the endpoint is not available for an account, use `get_balance`, `get_positions`, `get_fills` instead.

---

### `get_balance`

**HTTP:** `GET /portfolio/balance`
Expand Down
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<p align="center">
<img src="https://cdn.jsdelivr.net/gh/UTXOnly/kyro@more_testing/assets/logo.png" alt="Kyro" width="420">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/UTXOnly/kyro@main/assets/logo.png">
<img src="https://cdn.jsdelivr.net/gh/UTXOnly/kyro@main/assets/logo-light-bg.png" alt="Kyro" width="420">
</picture>
</p>

# Kyro

[![Ruff](https://github.com/UTXOnly/kyro/actions/workflows/ruff.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/ruff.yml) [![Black](https://github.com/UTXOnly/kyro/actions/workflows/black.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/black.yml) [![Tests](https://github.com/UTXOnly/kyro/actions/workflows/test.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/test.yml) [![Benchmarks](https://github.com/UTXOnly/kyro/actions/workflows/benchmarks.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/benchmarks.yml)
[![PyPI](https://img.shields.io/pypi/v/kyro.svg)](https://pypi.org/project/kyro/) [![Ruff](https://github.com/UTXOnly/kyro/actions/workflows/ruff.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/ruff.yml) [![Black](https://github.com/UTXOnly/kyro/actions/workflows/black.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/black.yml) [![Tests](https://github.com/UTXOnly/kyro/actions/workflows/test.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/test.yml) [![Benchmarks](https://github.com/UTXOnly/kyro/actions/workflows/benchmarks.yml/badge.svg)](https://github.com/UTXOnly/kyro/actions/workflows/benchmarks.yml)

Kyro is an async Python client library for the Kalshi REST API.

Expand All @@ -16,6 +19,7 @@ API areas are grouped into:
- `exchange`
- `markets`
- `events`
- `search`
- `orders`
- `portfolio`

Expand All @@ -31,7 +35,7 @@ Errors are surfaced as explicit exception types: `KyroError` (base), `KyroHTTPEr

## Install

From PyPI (after a release):
From [PyPI](https://pypi.org/project/kyro/):

```bash
pip install kyro
Expand Down Expand Up @@ -171,7 +175,7 @@ async with RestClient(cfg) as client:

| Requires auth | Endpoints |
|---------------|-----------|
| **No** | `exchange.get_exchange_status`, `get_exchange_announcements`, `get_exchange_schedule`, `get_series_fee_changes`; all of `markets.*` and `events.*` |
| **No** | `exchange.get_exchange_status`, `get_exchange_announcements`, `get_exchange_schedule`, `get_series_fee_changes`; all of `markets.*`, `events.*`, and `search.*` |
| **Yes** | `exchange.get_user_data_timestamp`; all of `orders.*` and `portfolio.*` |

Without auth, public endpoints work as usual. Auth-required calls return `401` if the headers are missing or invalid.
Expand All @@ -186,18 +190,19 @@ Without auth, public endpoints work as usual. Auth-required calls return `401` i

---

## Modular API (exchange, markets, events, orders, portfolio)
## Modular API (exchange, markets, events, search, orders, portfolio)

```python
from kyro import RestClient, KyroConfig
from kyro.rest import exchange, markets, events, orders, portfolio
from kyro.rest import exchange, markets, events, search, orders, portfolio

async with RestClient(KyroConfig()) as client:
# Exchange (no auth)
# Exchange (no auth except get_user_data_timestamp)
status = await exchange.get_exchange_status(client)
await exchange.get_exchange_announcements(client)
await exchange.get_exchange_schedule(client)
await exchange.get_series_fee_changes(client, series_ticker="KXBTC")
await exchange.get_user_data_timestamp(client) # auth

# Markets — filters: series_ticker, event_ticker, status, tickers, min/max_*_ts, cursor
ms = await markets.get_markets(
Expand All @@ -222,6 +227,8 @@ async with RestClient(KyroConfig()) as client:
period_interval=60,
limit=100,
)
await markets.get_live_data(client, "KXBTC-24JAN15")
await markets.get_multiple_live_data(client, "KXBTC-24JAN15,INXD-25")
await markets.get_series(client, "KXBTC")
await markets.get_series_list(client, limit=20) # cursor= for pagination

Expand All @@ -235,8 +242,15 @@ async with RestClient(KyroConfig()) as client:
)
ev = await events.get_event(client, "INXD-25", with_nested_markets=True)
await events.get_event_metadata(client, "INXD-25")
await events.get_event_candlesticks(
client, "KXBTC", "INXD-25", period_interval=60, limit=100
)
await events.get_multivariate_events(client, limit=10)

# Search (no auth)
await search.get_sports_filters(client)
await search.get_tags_by_categories(client)

# Orders (auth) — filters: ticker, event_ticker, status, min_ts, max_ts, cursor, subaccount
ords = await orders.get_orders(
client, ticker="KXBTC-24JAN15", status="resting", limit=50
Expand All @@ -255,13 +269,15 @@ async with RestClient(KyroConfig()) as client:
await orders.amend_order(
client, "order-id", ticker="KXBTC-24JAN15", side="yes", action="buy", yes_price=55
)
await orders.decrease_order(client, "order-id", reduce_by=1)
await orders.batch_create_orders(
client,
[{"ticker": "KXBTC-24JAN15", "side": "yes", "action": "buy", "count": 1, "yes_price": 50}],
)
await orders.batch_cancel_orders(client, ids=["id1", "id2"])
await orders.batch_cancel_orders(client, order_ids=["id1", "id2"])

# Portfolio (auth) — filters: ticker, event_ticker, min_ts, max_ts, cursor, subaccount
await portfolio.get_portfolio(client)
bal = await portfolio.get_balance(client)
pos = await portfolio.get_positions(
client, ticker="KXBTC-24JAN15", limit=100
Expand All @@ -283,7 +299,7 @@ async with RestClient(KyroConfig()) as client:

## API Reference

Full request/response docs for **every method** (exchange, markets, events, orders, portfolio):
Full request/response docs for **every method** (exchange, markets, events, search, orders, portfolio):
**[API_REFERENCE.md](API_REFERENCE.md)**

---
Expand Down Expand Up @@ -410,12 +426,13 @@ kyro/
│ ├── _version.py
│ ├── exceptions.py # KyroError, KyroHTTPError, KyroTimeoutError, KyroConnectionError, KyroValidationError
│ └── rest/
│ ├── __init__.py # RestClient, exchange, markets, events, orders, portfolio
│ ├── __init__.py # RestClient, exchange, markets, events, search, orders, portfolio
│ ├── client.py
│ └── api/
│ ├── exchange.py
│ ├── markets.py
│ ├── events.py
│ ├── search.py
│ ├── orders.py
│ └── portfolio.py
├── benchmarks/ # pytest-benchmark: serialization, REST client vs local mock
Expand Down
Binary file added assets/logo-light-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "kyro"
version = "0.1.1"
version = "0.1.2"
description = "Async Kalshi API client (aiohttp, Pydantic). Library for building apps."
readme = "README.md"
license = { text = "MIT" }
Expand Down
28 changes: 26 additions & 2 deletions scripts/live_api_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

from kyro import RestClient, config_from_env
from kyro.exceptions import KyroConnectionError, KyroHTTPError, KyroTimeoutError
from kyro.rest import events, exchange, markets, orders, portfolio
from kyro.rest import events, exchange, markets, orders, portfolio, search

AUDIT_LOG_ENV = "KALSHI_SMOKE_AUDIT_LOG"
DEFAULT_AUDIT_LOG = "live_smoke_audit.log"
Expand Down Expand Up @@ -71,6 +71,11 @@
("orders", "batch_create_orders"): "POST /portfolio/orders/batched",
("orders", "batch_cancel_orders"): 'DELETE /portfolio/orders/batched body {"ids":[...]}',
("orders", "cancel_order"): "DELETE /portfolio/orders/{order_id}",
(
"events",
"get_event_candlesticks",
): "GET /series/{series_ticker}/events/{event_ticker}/candlesticks?start_ts=&end_ts=&period_interval=1|60|1440",
("portfolio", "get_portfolio"): "GET /portfolio (may 404 for some accounts)",
}


Expand Down Expand Up @@ -230,7 +235,10 @@ async def _get_events(client: RestClient, ctx: dict) -> Any:
r = await events.get_events(client, limit=5)
evs = (r or {}).get("events") or []
if evs:
ctx["event_ticker"] = evs[0].get("event_ticker")
e = evs[0]
ctx["event_ticker"] = e.get("event_ticker")
if e.get("series_ticker") and not ctx.get("series_ticker"):
ctx["series_ticker"] = e["series_ticker"]
return r


Expand Down Expand Up @@ -339,6 +347,8 @@ async def _run_and_log(
("exchange", "get_exchange_schedule", lambda c, x: exchange.get_exchange_schedule(c), {}),
("exchange", "get_series_fee_changes", lambda c, x: exchange.get_series_fee_changes(c), {}),
("exchange", "get_user_data_timestamp", lambda c, x: exchange.get_user_data_timestamp(c), {}),
("search", "get_sports_filters", lambda c, x: search.get_sports_filters(c), {}),
("search", "get_tags_by_categories", lambda c, x: search.get_tags_by_categories(c), {}),
("events", "get_events", _get_events, {"limit": 5}),
(
"events",
Expand All @@ -352,6 +362,19 @@ async def _run_and_log(
lambda c, x: events.get_event_metadata(c, _event_ticker(x)),
lambda ctx: {"event_ticker": _event_ticker(ctx)},
),
(
"events",
"get_event_candlesticks",
lambda c, x: events.get_event_candlesticks(
c, _series_ticker(x), _event_ticker(x), limit=5, period_interval=60
),
lambda ctx: {
"series_ticker": _series_ticker(ctx),
"event_ticker": _event_ticker(ctx),
"limit": 5,
"period_interval": 60,
},
),
(
"events",
"get_multivariate_events",
Expand Down Expand Up @@ -405,6 +428,7 @@ async def _run_and_log(
lambda ctx: {"tickers": _ticker(ctx)},
),
("orders", "get_orders", lambda c, x: orders.get_orders(c, limit=5), {"limit": 5}),
("portfolio", "get_portfolio", lambda c, x: portfolio.get_portfolio(c), {}),
("portfolio", "get_balance", lambda c, x: portfolio.get_balance(c), {}),
("portfolio", "get_positions", lambda c, x: portfolio.get_positions(c, limit=5), {"limit": 5}),
("portfolio", "get_fills", lambda c, x: portfolio.get_fills(c, limit=5), {"limit": 5}),
Expand Down
2 changes: 1 addition & 1 deletion src/kyro/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Package version."""

__version__ = "0.1.1"
__version__ = "0.1.2"
5 changes: 3 additions & 2 deletions src/kyro/rest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""REST client and modular Kalshi API (exchange, markets, events, orders, portfolio)."""
"""REST client and modular Kalshi API (exchange, markets, events, orders, portfolio, search)."""

from kyro.rest.api import events, exchange, markets, orders, portfolio
from kyro.rest.api import events, exchange, markets, orders, portfolio, search
from kyro.rest.client import RestClient

__all__ = [
Expand All @@ -10,4 +10,5 @@
"markets",
"orders",
"portfolio",
"search",
]
6 changes: 3 additions & 3 deletions src/kyro/rest/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

Example:
>>> from kyro import RestClient, KyroConfig
>>> from kyro.rest import exchange, markets, events, orders, portfolio
>>> from kyro.rest import exchange, markets, events, orders, portfolio, search
>>> async with RestClient(KyroConfig()) as client:
... status = await exchange.get_exchange_status(client)
... ms = await markets.get_markets(client, limit=10)
"""

from . import events, exchange, markets, orders, portfolio
from . import events, exchange, markets, orders, portfolio, search

__all__ = ["exchange", "events", "markets", "orders", "portfolio"]
__all__ = ["exchange", "events", "markets", "orders", "portfolio", "search"]
Loading