Skip to content

Commit fdd057d

Browse files
authored
Merge pull request #29 from pbeets/feat/batch6-api-updates
feat: batch 6 API updates - balance subaccount, historical endpoints, deprecations
2 parents e69c69a + f5a51ff commit fdd057d

File tree

15 files changed

+1101
-518
lines changed

15 files changed

+1101
-518
lines changed

API_CHANGES_TODO.md

Lines changed: 0 additions & 467 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9090
- `subaccount` filter on `GetOrderGroupsParams` for per-subaccount order group
9191
queries. New `get_order_group_for_subaccount()` method on `KalshiClient`.
9292
- `market_id` (required) and `target_size_fp` (optional) fields on `IncentiveProgram`.
93+
- `GetBalanceParams` with `subaccount` filter for `GET /portfolio/balance`. New
94+
`get_balance_with_params()` method on `KalshiClient`. Omitting subaccount
95+
returns combined balance across all subaccounts; `subaccount(0)` filters to
96+
primary only.
97+
- `settlement_value` field on `MarketLifecycleData` in WebSocket
98+
`market_lifecycle_v2` channel. Present as a fixed-point dollar string on
99+
`market_determined` events.
100+
- Historical data endpoints (6 new endpoints):
101+
- `get_historical_cutoff()` — cutoff timestamps for archived data
102+
- `get_historical_markets()` / `get_historical_markets_with_params()`
103+
settled markets older than cutoff
104+
- `get_historical_market(ticker)` — single historical market
105+
- `get_historical_candlesticks(ticker, params)` — candlesticks for historical
106+
markets
107+
- `get_historical_fills()` / `get_historical_fills_with_params()` — archived
108+
fills
109+
- `get_historical_orders()` / `get_historical_orders_with_params()` — archived
110+
orders
111+
- New types: `HistoricalCutoffResponse`, `HistoricalCandlesticksResponse`,
112+
`HistoricalCandlestick`, `HistoricalOhlc`, `HistoricalPriceOhlc`,
113+
`GetHistoricalCandlesticksParams`, `GetHistoricalMarketsParams`,
114+
`GetHistoricalFillsParams`, `GetHistoricalOrdersParams`.
115+
- `yes_settlement_value_dollars` field on `MveSelectedLeg`.
93116
- Tightened `IncentiveProgram` struct: `id`, `market_id`, `market_ticker`,
94117
`incentive_type`, `start_date`, `end_date`, `period_reward`, and `paid_out`
95118
are now non-optional to match the official Kalshi OpenAPI spec.
@@ -157,6 +180,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
157180
- `Fill.trade_id`, `Fill.market_ticker`, `Fill.ts` — legacy field names.
158181
- `Event.category` — use series-level category instead.
159182
- `MarketPosition.resting_orders_count` — deprecated by the API.
183+
- `Market.liquidity` — always returns 0. Use orderbook data instead.
184+
- `Market.liquidity_dollars` — always returns `"0.0000"`. Use orderbook data
185+
instead.
160186

161187
## [0.2.0] - 2026-01-18
162188

examples/historical.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Historical Data API example
2+
//!
3+
//! Demonstrates the Historical Data API for fetching archived market data,
4+
//! including cutoff timestamps, historical markets, candlesticks, fills, and orders.
5+
//!
6+
//! Run with: cargo run --example historical
7+
8+
use kalshi_trade_rs::{
9+
CandlestickPeriod, GetHistoricalCandlesticksParams, GetHistoricalFillsParams,
10+
GetHistoricalMarketsParams, GetHistoricalOrdersParams, KalshiClient, KalshiConfig,
11+
};
12+
13+
#[tokio::main]
14+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
15+
dotenvy::dotenv().ok();
16+
17+
let config = KalshiConfig::from_env()?;
18+
println!("Connected to {:?} environment\n", config.environment);
19+
20+
let client = KalshiClient::new(config)?;
21+
22+
// 1. Get Historical Cutoff Timestamps
23+
println!("=== Historical Cutoff ===");
24+
let cutoff = client.get_historical_cutoff().await?;
25+
println!("Markets archived through: {}", cutoff.market_settled_ts);
26+
println!("Trades archived through: {}", cutoff.trades_created_ts);
27+
println!("Orders archived through: {}", cutoff.orders_updated_ts);
28+
println!();
29+
30+
// 2. Get Historical Markets (default params)
31+
println!("=== Historical Markets (first 5) ===");
32+
let params = GetHistoricalMarketsParams::new().limit(5);
33+
let markets = client.get_historical_markets_with_params(params).await?;
34+
println!("Returned {} historical markets", markets.markets.len());
35+
36+
for market in &markets.markets {
37+
println!(
38+
" {} | {:?} | result: {:?} | vol: {}",
39+
market.ticker,
40+
market.status,
41+
market.result,
42+
market.volume.unwrap_or(0)
43+
);
44+
}
45+
46+
if let Some(cursor) = &markets.cursor {
47+
println!(" Next cursor: {}...", &cursor[..cursor.len().min(20)]);
48+
}
49+
println!();
50+
51+
// 3. Get a specific historical market and its candlesticks
52+
if let Some(market) = markets.markets.first() {
53+
let ticker = &market.ticker;
54+
55+
println!("=== Historical Market Detail: {} ===", ticker);
56+
let detail = client.get_historical_market(ticker).await?;
57+
let m = &detail.market;
58+
59+
println!(" Title: {}", m.title.as_deref().unwrap_or("(none)"));
60+
println!(" Event: {}", m.event_ticker);
61+
println!(" Type: {:?}", m.market_type);
62+
println!(" Status: {:?}", m.status);
63+
if let Some(sv) = &m.settlement_value_dollars {
64+
println!(" Settlement value: ${}", sv);
65+
}
66+
println!();
67+
68+
// 4. Get Historical Candlesticks
69+
// Use a broad time range to catch data for archived markets
70+
println!("=== Historical Candlesticks: {} ===", ticker);
71+
let now = std::time::SystemTime::now()
72+
.duration_since(std::time::UNIX_EPOCH)?
73+
.as_secs() as i64;
74+
let one_year_ago = now - 365 * 86400;
75+
76+
let params =
77+
GetHistoricalCandlesticksParams::new(one_year_ago, now, CandlestickPeriod::OneDay);
78+
match client.get_historical_candlesticks(ticker, params).await {
79+
Ok(candles) => {
80+
println!(" {} candlesticks returned", candles.candlesticks.len());
81+
for candle in candles.candlesticks.iter().take(3) {
82+
let close = candle
83+
.price
84+
.as_ref()
85+
.and_then(|p| p.close.as_deref())
86+
.unwrap_or("N/A");
87+
println!(
88+
" ts={} close=${} vol={}",
89+
candle.end_period_ts, close, candle.volume
90+
);
91+
}
92+
if candles.candlesticks.len() > 3 {
93+
println!(" ... and {} more", candles.candlesticks.len() - 3);
94+
}
95+
}
96+
Err(e) => println!(" Could not fetch candlesticks: {}", e),
97+
}
98+
println!();
99+
}
100+
101+
// 5. Get Historical Fills (requires auth)
102+
println!("=== Historical Fills (first 5) ===");
103+
let params = GetHistoricalFillsParams::new().limit(5);
104+
let fills = client.get_historical_fills_with_params(params).await?;
105+
println!("Returned {} historical fills", fills.fills.len());
106+
107+
for fill in &fills.fills {
108+
println!(
109+
" {} {} {} {} @ {} ({})",
110+
fill.ticker,
111+
fill.action,
112+
fill.count_fp,
113+
fill.side,
114+
fill.yes_price_fixed,
115+
if fill.is_taker { "taker" } else { "maker" },
116+
);
117+
}
118+
119+
if fills.fills.is_empty() {
120+
println!(" (No historical fills found)");
121+
}
122+
println!();
123+
124+
// 6. Get Historical Orders (requires auth)
125+
println!("=== Historical Orders (first 5) ===");
126+
let params = GetHistoricalOrdersParams::new().limit(5);
127+
let orders = client.get_historical_orders_with_params(params).await?;
128+
println!("Returned {} historical orders", orders.orders.len());
129+
130+
for order in &orders.orders {
131+
println!(
132+
" {} {} {} {} | {:?} | filled: {}/{}",
133+
order.ticker,
134+
order.action,
135+
order.initial_count,
136+
order.side,
137+
order.status,
138+
order.fill_count,
139+
order.initial_count,
140+
);
141+
}
142+
143+
if orders.orders.is_empty() {
144+
println!(" (No historical orders found)");
145+
}
146+
147+
println!("\n=== Done ===");
148+
Ok(())
149+
}

examples/portfolio.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
//! Run with: cargo run --example portfolio
77
88
use kalshi_trade_rs::{
9-
GetFillsParams, GetOrdersParams, GetPositionsParams, GetSettlementsParams, KalshiClient,
10-
KalshiConfig, OrderStatus, cents_to_dollars,
9+
GetBalanceParams, GetFillsParams, GetOrdersParams, GetPositionsParams, GetSettlementsParams,
10+
KalshiClient, KalshiConfig, OrderStatus, cents_to_dollars,
1111
};
1212

1313
#[tokio::main]
@@ -21,8 +21,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2121

2222
let client = KalshiClient::new(config)?;
2323

24-
// 1. Get Balance
25-
println!("=== Balance ===");
24+
// 1. Get Balance (combined across all subaccounts)
25+
println!("=== Balance (all subaccounts) ===");
2626

2727
let balance = client.get_balance().await?;
2828

@@ -35,6 +35,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3535

3636
println!();
3737

38+
// 1b. Get Balance for primary account only
39+
println!("=== Balance (primary account only) ===");
40+
41+
let params = GetBalanceParams::new().subaccount(0);
42+
let primary_balance = client.get_balance_with_params(params).await?;
43+
44+
println!(
45+
"Primary Available: ${:.2}",
46+
cents_to_dollars(primary_balance.balance)
47+
);
48+
println!(
49+
"Primary Portfolio Value: ${:.2}",
50+
cents_to_dollars(primary_balance.portfolio_value)
51+
);
52+
53+
println!();
54+
3855
// 2. Get Positions
3956
println!("=== Positions ===");
4057

src/api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub(crate) mod communications;
99
pub(crate) mod events;
1010
pub(crate) mod exchange;
1111
pub(crate) mod fcm;
12+
pub(crate) mod historical;
1213
pub(crate) mod incentive_programs;
1314
pub(crate) mod live_data;
1415
pub(crate) mod markets;

src/api/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Complete reference for all Kalshi REST API endpoints supported by this library.
3737
| Structured Targets | 2 | 100% |
3838
| Incentive Programs | 1 | 100% |
3939
| FCM | 2 | 100% |
40-
| **Total** | **78** | **100%** |
40+
| Historical | 6 | 100% |
41+
| **Total** | **84** | **100%** |
4142

4243
---
4344

@@ -283,6 +284,23 @@ Specialized endpoints for FCM (Futures Commission Merchant) members.
283284

284285
---
285286

287+
## Historical API
288+
289+
Endpoints for accessing archived historical data past the cutoff timestamp.
290+
291+
| Status | Method | Endpoint | Rust Function | Notes |
292+
|--------|--------|----------|---------------|-------|
293+
| 🔲 | GET | `/historical/cutoff` | `get_historical_cutoff()` | No auth required |
294+
| 🔲 | GET | `/historical/markets` | `get_historical_markets()` | No auth required |
295+
| 🔲 | GET | `/historical/markets/{ticker}` | `get_historical_market()` | No auth required |
296+
| 🔲 | GET | `/historical/markets/{ticker}/candlesticks` | `get_historical_candlesticks()` | No auth; returns `HistoricalCandlesticksResponse` |
297+
| 🔲 | GET | `/historical/fills` | `get_historical_fills()` | Requires auth |
298+
| 🔲 | GET | `/historical/orders` | `get_historical_orders()` | Requires auth |
299+
300+
**Source**: `src/api/historical.rs`
301+
302+
---
303+
286304
## Base URLs
287305

288306
| Environment | REST API | WebSocket |

src/api/historical.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//! Historical data API endpoints.
2+
//!
3+
//! These endpoints provide access to historical market data that has been
4+
//! archived past the cutoff timestamp. Cutoff, markets, and candlestick
5+
//! endpoints do not require authentication. Fills and orders require auth.
6+
7+
use url::form_urlencoded;
8+
9+
use crate::{
10+
client::HttpClient,
11+
error::Result,
12+
models::{
13+
FillsResponse, GetHistoricalCandlesticksParams, GetHistoricalFillsParams,
14+
GetHistoricalMarketsParams, GetHistoricalOrdersParams, HistoricalCandlesticksResponse,
15+
HistoricalCutoffResponse, MarketResponse, MarketsResponse, OrdersResponse,
16+
},
17+
};
18+
19+
fn encode_ticker(ticker: &str) -> String {
20+
form_urlencoded::byte_serialize(ticker.as_bytes()).collect()
21+
}
22+
23+
/// Returns the cutoff timestamps for historical data.
24+
pub async fn get_historical_cutoff(http: &HttpClient) -> Result<HistoricalCutoffResponse> {
25+
http.get("/historical/cutoff").await
26+
}
27+
28+
/// Returns historical markets matching the provided query parameters.
29+
pub async fn get_historical_markets(
30+
http: &HttpClient,
31+
params: GetHistoricalMarketsParams,
32+
) -> Result<MarketsResponse> {
33+
let path = format!("/historical/markets{}", params.to_query_string());
34+
http.get(&path).await
35+
}
36+
37+
/// Returns a specific historical market by ticker.
38+
pub async fn get_historical_market(http: &HttpClient, ticker: &str) -> Result<MarketResponse> {
39+
let path = format!("/historical/markets/{}", encode_ticker(ticker));
40+
http.get(&path).await
41+
}
42+
43+
/// Returns historical candlestick data for a market.
44+
pub async fn get_historical_candlesticks(
45+
http: &HttpClient,
46+
ticker: &str,
47+
params: GetHistoricalCandlesticksParams,
48+
) -> Result<HistoricalCandlesticksResponse> {
49+
let path = format!(
50+
"/historical/markets/{}/candlesticks{}",
51+
encode_ticker(ticker),
52+
params.to_query_string()
53+
);
54+
http.get(&path).await
55+
}
56+
57+
/// Returns historical fills.
58+
pub async fn get_historical_fills(
59+
http: &HttpClient,
60+
params: GetHistoricalFillsParams,
61+
) -> Result<FillsResponse> {
62+
let path = format!("/historical/fills{}", params.to_query_string());
63+
http.get(&path).await
64+
}
65+
66+
/// Returns historical orders.
67+
pub async fn get_historical_orders(
68+
http: &HttpClient,
69+
params: GetHistoricalOrdersParams,
70+
) -> Result<OrdersResponse> {
71+
let path = format!("/historical/orders{}", params.to_query_string());
72+
http.get(&path).await
73+
}

src/api/portfolio.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ use crate::{
77
client::HttpClient,
88
error::Result,
99
models::{
10-
BalanceResponse, FillsResponse, GetFillsParams, GetOrdersParams, GetPositionsParams,
11-
GetSettlementsParams, OrdersResponse, PositionsResponse, SettlementsResponse,
10+
BalanceResponse, FillsResponse, GetBalanceParams, GetFillsParams, GetOrdersParams,
11+
GetPositionsParams, GetSettlementsParams, OrdersResponse, PositionsResponse,
12+
SettlementsResponse,
1213
},
1314
};
1415

1516
/// Returns the available balance and portfolio value in cents.
16-
pub async fn get_balance(http: &HttpClient) -> Result<BalanceResponse> {
17-
http.get("/portfolio/balance").await
17+
pub async fn get_balance(http: &HttpClient, params: GetBalanceParams) -> Result<BalanceResponse> {
18+
let path = format!("/portfolio/balance{}", params.to_query_string());
19+
http.get(&path).await
1820
}
1921

2022
pub async fn get_positions(

0 commit comments

Comments
 (0)