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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **REST**: `PublicTrade::is_block_trade` — parsed from `GET /markets/trades`
(Kalshi changelog 2026-05-29: public trade responses now flag block
trades and support filtering by block status). Block trades are large
negotiated prints routed off the central order book; exposing the flag
lets trade-flow / microstructure consumers exclude them. The field is
absent on older payloads and defaults to `false` (forward/backward
compatible — no behavioural change for existing consumers).

### Changed

- **Internal**: extracted the inline `GET /markets/trades` body parser out
of `KalshiClient::get_trades` into a testable
`api_detail::parse_trades_response`, matching the existing
`parse_deposits_response` / `parse_withdrawals_response` convention. Adds
unit coverage for trade parsing (incl. the new `is_block_trade` flag).

## [0.4.8] - 2026-05-19

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions include/kalshi/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ struct PublicTrade {
std::int32_t count{0};
Side taker_side{Side::Yes};
std::int64_t created_time{0};
/// True when the trade was executed as a block trade (large negotiated
/// print routed off the central order book). Kalshi added this field on
/// 2026-05-29; absent on older payloads → defaults to false.
bool is_block_trade{false};
};

// ===== Phase 1: Exchange API Models =====
Expand Down
37 changes: 21 additions & 16 deletions src/api/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,26 @@ std::vector<Withdrawal> parse_withdrawals_response(std::string_view body) {
return parse_portfolio_movements<Withdrawal>(body, "withdrawals");
}

std::vector<PublicTrade> parse_trades_response(std::string_view body) {
const std::string buf{body};
const std::vector<std::string> trade_objects = extract_array_objects(buf, "trades");
std::vector<PublicTrade> trades;
trades.reserve(trade_objects.size());
for (const std::string& obj : trade_objects) {
PublicTrade t;
t.trade_id = extract_string(obj, "trade_id");
t.market_ticker = extract_string(obj, "ticker");
t.yes_price = static_cast<std::int32_t>(extract_int(obj, "yes_price"));
t.no_price = static_cast<std::int32_t>(extract_int(obj, "no_price"));
t.count = static_cast<std::int32_t>(extract_int(obj, "count"));
t.taker_side = parse_side(extract_string(obj, "taker_side"));
t.created_time = extract_int(obj, "created_time");
t.is_block_trade = extract_bool(obj, "is_block_trade");
trades.push_back(t);
}
return trades;
}

OrderCancelResult parse_order_cancel_result_response(std::string_view body) {
const std::string obj{body};
OrderCancelResult result;
Expand Down Expand Up @@ -1276,23 +1296,8 @@ Result<PaginatedResponse<PublicTrade>> KalshiClient::get_trades(const GetTradesP
response->status_code});
}

std::vector<PublicTrade> trades;
std::vector<std::string> trade_objects = extract_array_objects(response->body, "trades");

for (const std::string& obj : trade_objects) {
PublicTrade t;
t.trade_id = extract_string(obj, "trade_id");
t.market_ticker = extract_string(obj, "ticker");
t.yes_price = static_cast<std::int32_t>(extract_int(obj, "yes_price"));
t.no_price = static_cast<std::int32_t>(extract_int(obj, "no_price"));
t.count = static_cast<std::int32_t>(extract_int(obj, "count"));
t.taker_side = parse_side(extract_string(obj, "taker_side"));
t.created_time = extract_int(obj, "created_time");
trades.push_back(t);
}

PaginatedResponse<PublicTrade> result;
result.items = std::move(trades);
result.items = api_detail::parse_trades_response(response->body);

std::string cursor = extract_cursor(response->body);
if (!cursor.empty()) {
Expand Down
5 changes: 5 additions & 0 deletions src/api/response_parsers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ namespace kalshi::api_detail {
/// Parses the ``orderbooks`` array returned by ``GET /markets/orderbooks``.
[[nodiscard]] std::vector<OrderBook> parse_orderbooks_response(std::string_view body);

/// Parses the ``trades`` array from ``GET /markets/trades``. Returns an empty
/// vector when the array is missing or empty. The cursor field is read
/// separately by the client method.
[[nodiscard]] std::vector<PublicTrade> parse_trades_response(std::string_view body);

/// Parses the ``deposits`` array from ``GET /portfolio/deposits``. Returns
/// an empty vector when the array is missing or empty. The cursor field
/// is read separately by the client method.
Expand Down
27 changes: 27 additions & 0 deletions tests/test_response_parsers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,33 @@ TEST(ResponseParsers, MarketsParseUnopenedArray) {
EXPECT_EQ(markets[1].status, kalshi::MarketStatus::Open);
}

TEST(ResponseParsers, TradesParseBlockTradeFlag) {
// Kalshi 2026-05-29: public trade responses flag block trades via the
// new boolean ``is_block_trade``. First row carries it true; second row
// omits it (older/non-block print) and must default to false.
const std::string body = R"json({
"trades": [
{"trade_id": "t1", "ticker": "KXHIGHDEN-26JUN03-B80", "yes_price": 42,
"no_price": 58, "count": 10, "taker_side": "yes",
"created_time": 1780000000, "is_block_trade": true},
{"trade_id": "t2", "ticker": "KXHIGHDEN-26JUN03-B80", "yes_price": 40,
"no_price": 60, "count": 5, "taker_side": "no",
"created_time": 1780000100}
],
"cursor": ""
})json";

const std::vector<kalshi::PublicTrade> trades = kalshi::api_detail::parse_trades_response(body);

ASSERT_EQ(trades.size(), 2U);
EXPECT_EQ(trades[0].trade_id, "t1");
EXPECT_EQ(trades[0].count, 10);
EXPECT_EQ(trades[0].taker_side, kalshi::Side::Yes);
EXPECT_TRUE(trades[0].is_block_trade);
EXPECT_EQ(trades[1].trade_id, "t2");
EXPECT_FALSE(trades[1].is_block_trade);
}

TEST(ResponseParsers, DepositsParseFinalizedAndPending) {
const std::string body = R"json({
"deposits": [
Expand Down
Loading