Skip to content

Commit 1f8eb05

Browse files
committed
feat: add batch 4 API updates - UserOrders WS channel, subaccount filters, market_id
- Add UserOrders WebSocket channel with UserOrderData and UserOrderEventType for real-time order update streaming (created, updated, canceled, executed) - Add subaccount filter to ListRfqsParams for per-subaccount RFQ queries - Add subaccount parameter to order group read endpoints (GetOrderGroupsParams, get_order_group) and get_order_group_for_subaccount() client method - Add market_id field to IncentiveProgram
1 parent f16e9af commit 1f8eb05

File tree

9 files changed

+197
-85
lines changed

9 files changed

+197
-85
lines changed

API_CHANGES_TODO.md

Lines changed: 13 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -323,99 +323,31 @@ pub enum MarketResult {
323323

324324
---
325325

326-
## BATCH 4: January 30 – February 5, 2026
326+
## ~~BATCH 4: January 30 – February 5, 2026~~ ✅ DONE
327327

328-
### 4.1 — Queue Position Fixed-Point Field
328+
### ~~4.1 — Queue Position Fixed-Point Field~~
329329

330-
**Changelog**: Jan 30, 2026 — "Order queue position returns fixed point field"
330+
Already implemented in batch 1 (`queue_position_fp` on `QueuePosition` and `OrderQueuePositionResponse`).
331331

332-
**What changed**: `queue_position_fp` field added to queue position responses.
332+
### ~~4.2 — Subaccount Support for RFQs~~
333333

334-
**What to do**:
335-
- Add `queue_position_fp: Option<String>` to queue position response types in order-related models
336-
- Check `OrderQueuePositionResponse` and `QueuePositionsResponse`
337-
338-
**Files to modify**: `src/models/order.rs` (or wherever queue position types live)
339-
340-
---
341-
342-
### 4.2 — Subaccount Support for RFQs
343-
344-
**Changelog**: Feb 2, 2026 — "Subaccount support for RFQs"
345-
346-
**What changed**:
347-
- `POST /communications/rfqs` accepts optional `subaccount` parameter
348-
- `GET /communications/rfqs` accepts optional `subaccount` query parameter
349-
350-
**What to do**:
351-
- Add `subaccount: Option<i32>` to `CreateRfqRequest` in `src/models/communications.rs`
352-
- Add `subaccount: Option<i32>` to RFQ list query params
334+
`CreateRfqRequest.subaccount` was implemented in batch 2. Added `subaccount` filter to `ListRfqsParams`.
353335

354-
**Files to modify**: `src/models/communications.rs`, `src/api/communications.rs`
336+
### ~~4.3 — Subaccount Support for RFQ Quotes~~
355337

356-
---
357-
358-
### 4.3 — Subaccount Support for RFQ Quotes
359-
360-
**Changelog**: Jan 22, 2026 — "Subaccount support for RFQ quotes"
361-
362-
**What changed**: `POST /communications/quotes` accepts optional `subaccount` parameter.
363-
364-
**What to do**:
365-
- Add `subaccount: Option<i32>` to `CreateQuoteRequest` in `src/models/communications.rs`
338+
Already implemented in batch 2 (`CreateQuoteRequest.subaccount`).
366339

367-
**Files to modify**: `src/models/communications.rs`
340+
### ~~4.4 — User Orders WebSocket Channel~~
368341

369-
---
342+
Added `UserOrders` channel, `UserOrderData`, `UserOrderEventType`, message parsing.
370343

371-
### 4.4User Orders WebSocket Channel
344+
### ~~4.5Order Group Read Endpoints Subaccount Parameter~~
372345

373-
**Changelog**: Feb 3, 2026 — "User orders WebSocket channel"
346+
Added `subaccount` to `GetOrderGroupsParams` and `get_order_group()` API function. Added `get_order_group_for_subaccount()` to client.
374347

375-
**What changed**: New `user_orders` channel streams real-time order updates (created, updated, canceled, executed) for authenticated users. Supports optional `market_tickers` filter and dynamic `update_subscription` commands.
376-
377-
**What to do**:
378-
- Add `UserOrders` variant to `Channel` enum in `src/ws/channel.rs`:
379-
- Wire name: `"user_orders"`
380-
- Requires auth: yes
381-
- Does NOT require market ticker (supports optional tickers)
382-
- Add `UserOrderData` struct in `src/ws/message.rs`:
383-
- Should mirror the `Order` REST type or have relevant order fields
384-
- Include event_type (created, updated, canceled, executed)
385-
- Add `UserOrder(UserOrderData)` variant to `StreamMessage`
386-
- Update message parsing
387-
- Update `Channel::requires_auth()`
388-
- Export new types
389-
390-
**Files to modify**: `src/ws/channel.rs`, `src/ws/message.rs`, `src/lib.rs`
391-
392-
---
393-
394-
### 4.5 — Order Group Read Endpoints Subaccount Parameter
395-
396-
**Changelog**: Feb 3, 2026 — "Order group read endpoints support optional subaccount parameter"
397-
398-
**What changed**: `GET /portfolio/order_groups` and `GET /portfolio/order_groups/{id}` accept optional `subaccount` query parameter.
399-
400-
**What to do**:
401-
- Add `subaccount: Option<i32>` to order group list params
402-
- Add optional `subaccount` query param to single order group get
403-
- Update `src/api/order_groups.rs` and `src/client.rs`
404-
405-
**Files to modify**: `src/models/order_group.rs`, `src/api/order_groups.rs`, `src/client.rs`
406-
407-
---
408-
409-
### 4.6 — `market_id` on Incentive Programs API
410-
411-
**Changelog**: Feb 5, 2026 — "market_id added to Incentive Programs API"
412-
413-
**What changed**: `GET /incentive_programs` now returns `market_id` field.
414-
415-
**What to do**:
416-
- Add `market_id: Option<String>` to `IncentiveProgram` in `src/models/incentive_program.rs`
348+
### ~~4.6 — `market_id` on Incentive Programs API~~
417349

418-
**Files to modify**: `src/models/incentive_program.rs`
350+
Added `market_id: Option<String>` to `IncentiveProgram`.
419351

420352
---
421353

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8383
`with_selected_markets()` constructor on `CreateMarketInCollectionRequest` and
8484
`LookupTickersRequest`.
8585
- `lookback_seconds` filter on `GetLookupHistoryParams`.
86+
- New `UserOrders` WebSocket channel for real-time order update notifications
87+
(`UserOrderData`, `UserOrderEventType`). Requires authentication, supports
88+
optional `market_tickers` filtering.
89+
- `subaccount` filter on `ListRfqsParams` for per-subaccount RFQ queries.
90+
- `subaccount` filter on `GetOrderGroupsParams` for per-subaccount order group
91+
queries. New `get_order_group_for_subaccount()` method on `KalshiClient`.
92+
- `market_id` field on `IncentiveProgram`.
8693

8794
### Fixed
8895

src/api/order_groups.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,16 @@ pub async fn create_order_group(
2727
pub async fn get_order_group(
2828
http: &HttpClient,
2929
order_group_id: &str,
30+
subaccount: Option<i32>,
3031
) -> Result<GetOrderGroupResponse> {
31-
let path = format!("/portfolio/order_groups/{}", encode_id(order_group_id));
32+
let path = match subaccount {
33+
Some(sub) => format!(
34+
"/portfolio/order_groups/{}?subaccount={}",
35+
encode_id(order_group_id),
36+
sub
37+
),
38+
None => format!("/portfolio/order_groups/{}", encode_id(order_group_id)),
39+
};
3240
http.get(&path).await
3341
}
3442

src/client.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,21 @@ impl KalshiClient {
10211021
/// println!("Orders: {:?}", response.orders);
10221022
/// ```
10231023
pub async fn get_order_group(&self, order_group_id: &str) -> Result<GetOrderGroupResponse> {
1024-
order_groups::get_order_group(&self.http, order_group_id).await
1024+
order_groups::get_order_group(&self.http, order_group_id, None).await
1025+
}
1026+
1027+
/// Get an order group by ID for a specific subaccount.
1028+
///
1029+
/// # Arguments
1030+
///
1031+
/// * `order_group_id` - The ID of the order group to retrieve
1032+
/// * `subaccount` - Subaccount number (0 for primary, 1-32 for subaccounts)
1033+
pub async fn get_order_group_for_subaccount(
1034+
&self,
1035+
order_group_id: &str,
1036+
subaccount: i32,
1037+
) -> Result<GetOrderGroupResponse> {
1038+
order_groups::get_order_group(&self.http, order_group_id, Some(subaccount)).await
10251039
}
10261040

10271041
/// List all order groups with default parameters.

src/models/communications.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ pub struct ListRfqsParams {
417417
/// Filter by creator user ID.
418418
#[serde(skip_serializing_if = "Option::is_none")]
419419
pub creator_user_id: Option<String>,
420+
/// Filter by subaccount number. When omitted, returns RFQs across all subaccounts.
421+
/// When provided (including 0), filters to that specific subaccount.
422+
#[serde(skip_serializing_if = "Option::is_none")]
423+
pub subaccount: Option<i32>,
420424
}
421425

422426
impl ListRfqsParams {
@@ -462,6 +466,13 @@ impl ListRfqsParams {
462466
self
463467
}
464468

469+
/// Filter by subaccount number.
470+
#[must_use]
471+
pub fn subaccount(mut self, subaccount: i32) -> Self {
472+
self.subaccount = Some(subaccount);
473+
self
474+
}
475+
465476
#[must_use]
466477
pub fn to_query_string(&self) -> String {
467478
use crate::models::query::QueryBuilder;
@@ -472,6 +483,7 @@ impl ListRfqsParams {
472483
qb.push_opt("limit", self.limit);
473484
qb.push_opt("status", self.status.as_ref());
474485
qb.push_opt("creator_user_id", self.creator_user_id.as_ref());
486+
qb.push_opt("subaccount", self.subaccount);
475487
qb.build()
476488
}
477489
}

src/models/incentive_program.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub struct IncentiveProgram {
3636
/// Target size for the program.
3737
#[serde(default)]
3838
pub target_size: Option<i64>,
39+
/// The market ID associated with this incentive program.
40+
#[serde(default)]
41+
pub market_id: Option<String>,
3942
}
4043

4144
/// Response from GET /incentive_programs.

src/models/order_group.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ pub struct GetOrderGroupsParams {
196196
/// Number of results per page.
197197
#[serde(skip_serializing_if = "Option::is_none")]
198198
pub limit: Option<i32>,
199+
/// Filter by subaccount number. When omitted, returns across all subaccounts.
200+
/// When provided (including 0), filters to that specific subaccount.
201+
#[serde(skip_serializing_if = "Option::is_none")]
202+
pub subaccount: Option<i32>,
199203
}
200204

201205
impl GetOrderGroupsParams {
@@ -217,11 +221,19 @@ impl GetOrderGroupsParams {
217221
self
218222
}
219223

224+
/// Filter by subaccount number.
225+
#[must_use]
226+
pub fn subaccount(mut self, subaccount: i32) -> Self {
227+
self.subaccount = Some(subaccount);
228+
self
229+
}
230+
220231
#[must_use]
221232
pub fn to_query_string(&self) -> String {
222233
let mut qb = QueryBuilder::new();
223234
qb.push_opt("cursor", self.cursor.as_ref());
224235
qb.push_opt("limit", self.limit);
236+
qb.push_opt("subaccount", self.subaccount);
225237
qb.build()
226238
}
227239
}

src/ws/channel.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub enum Channel {
2323
MarketLifecycle,
2424
/// Order group lifecycle events (requires authentication)
2525
OrderGroupUpdates,
26+
/// Real-time user order updates (requires authentication)
27+
UserOrders,
2628
/// Multivariate collection lookup notifications
2729
Multivariate,
2830
}
@@ -32,7 +34,11 @@ impl Channel {
3234
pub fn requires_auth(&self) -> bool {
3335
matches!(
3436
self,
35-
Self::Fill | Self::MarketPositions | Self::Communications | Self::OrderGroupUpdates
37+
Self::Fill
38+
| Self::MarketPositions
39+
| Self::Communications
40+
| Self::OrderGroupUpdates
41+
| Self::UserOrders
3642
)
3743
}
3844

@@ -58,6 +64,7 @@ impl Channel {
5864
Self::Communications => "communications",
5965
Self::MarketLifecycle => "market_lifecycle_v2",
6066
Self::OrderGroupUpdates => "order_group_updates",
67+
Self::UserOrders => "user_orders",
6168
Self::Multivariate => "multivariate",
6269
}
6370
}

0 commit comments

Comments
 (0)