chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,457 @@
|
||||
import type { KalshiAuthParams, KalshiOrder } from '@/tools/kalshi/types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiAmendOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to amend (required)
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
clientOrderId: string // Original client order ID (required)
|
||||
updatedClientOrderId: string // New client order ID (required)
|
||||
count?: string // Updated quantity
|
||||
yesPrice?: string // Updated yes price in cents (1-99)
|
||||
noPrice?: string // Updated no price in cents (1-99)
|
||||
yesPriceDollars?: string // Updated yes price in dollars
|
||||
noPriceDollars?: string // Updated no price in dollars
|
||||
}
|
||||
|
||||
export interface KalshiAmendOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiAmendOrderTool: ToolConfig<KalshiAmendOrderParams, KalshiAmendOrderResponse> = {
|
||||
id: 'kalshi_amend_order',
|
||||
name: 'Amend Order on Kalshi',
|
||||
description: 'Modify the price or quantity of an existing order on Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to amend (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Side of the order: "yes" or "no"',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action type: "buy" or "sell"',
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Original client-specified order ID',
|
||||
},
|
||||
updatedClientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New client-specified order ID after amendment',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated quantity for the order (e.g., "10", "100")',
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated no price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated no price in dollars (e.g., "0.56")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}/amend`),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}/amend`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
client_order_id: params.clientOrderId,
|
||||
updated_client_order_id: params.updatedClientOrderId,
|
||||
}
|
||||
|
||||
if (params.count) body.count = Number.parseInt(params.count, 10)
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'amend_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The amended order object',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export interface KalshiAmendOrderV2Params extends KalshiAuthParams {
|
||||
orderId: string // Order ID to amend (required)
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
clientOrderId?: string // Original client order ID (optional in V2)
|
||||
updatedClientOrderId?: string // New client order ID (optional in V2)
|
||||
count?: string // Updated quantity
|
||||
yesPrice?: string // Updated yes price in cents (1-99)
|
||||
noPrice?: string // Updated no price in cents (1-99)
|
||||
yesPriceDollars?: string // Updated yes price in dollars
|
||||
noPriceDollars?: string // Updated no price in dollars
|
||||
countFp?: string // Count in fixed-point for fractional contracts
|
||||
}
|
||||
|
||||
interface KalshiAmendOrderV2Order {
|
||||
order_id: string
|
||||
user_id: string | null
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
status: string
|
||||
side: string
|
||||
type: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
action: string
|
||||
count: number
|
||||
remaining_count: number
|
||||
created_time: string
|
||||
expiration_time: string | null
|
||||
order_group_id: string | null
|
||||
client_order_id: string | null
|
||||
place_count: number | null
|
||||
decrease_count: number | null
|
||||
queue_position: number | null
|
||||
maker_fill_count: number | null
|
||||
taker_fill_count: number | null
|
||||
maker_fees: number | null
|
||||
taker_fees: number | null
|
||||
last_update_time: string | null
|
||||
take_profit_order_id: string | null
|
||||
stop_loss_order_id: string | null
|
||||
amend_count: number | null
|
||||
amend_taker_fill_count: number | null
|
||||
}
|
||||
|
||||
export interface KalshiAmendOrderV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
old_order: KalshiAmendOrderV2Order
|
||||
order: KalshiAmendOrderV2Order
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiAmendOrderV2Tool: ToolConfig<
|
||||
KalshiAmendOrderV2Params,
|
||||
KalshiAmendOrderV2Response
|
||||
> = {
|
||||
id: 'kalshi_amend_order_v2',
|
||||
name: 'Amend Order on Kalshi V2',
|
||||
description:
|
||||
'Modify the price or quantity of an existing order on Kalshi (V2 with full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to amend (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Side of the order: "yes" or "no"',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action type: "buy" or "sell"',
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Original client-specified order ID',
|
||||
},
|
||||
updatedClientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New client-specified order ID after amendment',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated quantity for the order (e.g., "10", "100")',
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated no price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated no price in dollars (e.g., "0.56")',
|
||||
},
|
||||
countFp: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Count in fixed-point for fractional contracts',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}/amend`),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}/amend`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
}
|
||||
|
||||
if (params.clientOrderId) body.client_order_id = params.clientOrderId
|
||||
if (params.updatedClientOrderId) body.updated_client_order_id = params.updatedClientOrderId
|
||||
if (params.count) body.count = Number.parseInt(params.count, 10)
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
if (params.countFp) body.count_fp = params.countFp
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'amend_order_v2')
|
||||
}
|
||||
|
||||
const mapOrder = (order: any): KalshiAmendOrderV2Order => ({
|
||||
order_id: order.order_id ?? null,
|
||||
user_id: order.user_id ?? null,
|
||||
ticker: order.ticker ?? null,
|
||||
event_ticker: order.event_ticker ?? null,
|
||||
status: order.status ?? null,
|
||||
side: order.side ?? null,
|
||||
type: order.type ?? null,
|
||||
yes_price: order.yes_price ?? null,
|
||||
no_price: order.no_price ?? null,
|
||||
action: order.action ?? null,
|
||||
count: order.count ?? null,
|
||||
remaining_count: order.remaining_count ?? null,
|
||||
created_time: order.created_time ?? null,
|
||||
expiration_time: order.expiration_time ?? null,
|
||||
order_group_id: order.order_group_id ?? null,
|
||||
client_order_id: order.client_order_id ?? null,
|
||||
place_count: order.place_count ?? null,
|
||||
decrease_count: order.decrease_count ?? null,
|
||||
queue_position: order.queue_position ?? null,
|
||||
maker_fill_count: order.maker_fill_count ?? null,
|
||||
taker_fill_count: order.taker_fill_count ?? null,
|
||||
maker_fees: order.maker_fees ?? null,
|
||||
taker_fees: order.taker_fees ?? null,
|
||||
last_update_time: order.last_update_time ?? null,
|
||||
take_profit_order_id: order.take_profit_order_id ?? null,
|
||||
stop_loss_order_id: order.stop_loss_order_id ?? null,
|
||||
amend_count: order.amend_count ?? null,
|
||||
amend_taker_fill_count: order.amend_taker_fill_count ?? null,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
old_order: mapOrder(data.old_order || {}),
|
||||
order: mapOrder(data.order || {}),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
old_order: {
|
||||
type: 'object',
|
||||
description: 'The original order object before amendment',
|
||||
properties: {
|
||||
order_id: { type: 'string', description: 'Order ID' },
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
status: { type: 'string', description: 'Order status' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
action: { type: 'string', description: 'Action (buy/sell)' },
|
||||
count: { type: 'number', description: 'Number of contracts' },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts' },
|
||||
created_time: { type: 'string', description: 'Order creation time' },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time' },
|
||||
order_group_id: { type: 'string', description: 'Order group ID' },
|
||||
client_order_id: { type: 'string', description: 'Client order ID' },
|
||||
place_count: { type: 'number', description: 'Place count' },
|
||||
decrease_count: { type: 'number', description: 'Decrease count' },
|
||||
queue_position: { type: 'number', description: 'Queue position' },
|
||||
maker_fill_count: { type: 'number', description: 'Maker fill count' },
|
||||
taker_fill_count: { type: 'number', description: 'Taker fill count' },
|
||||
maker_fees: { type: 'number', description: 'Maker fees' },
|
||||
taker_fees: { type: 'number', description: 'Taker fees' },
|
||||
last_update_time: { type: 'string', description: 'Last update time' },
|
||||
take_profit_order_id: { type: 'string', description: 'Take profit order ID' },
|
||||
stop_loss_order_id: { type: 'string', description: 'Stop loss order ID' },
|
||||
amend_count: { type: 'number', description: 'Amend count' },
|
||||
amend_taker_fill_count: { type: 'number', description: 'Amend taker fill count' },
|
||||
},
|
||||
},
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The amended order object with full API response fields',
|
||||
properties: {
|
||||
order_id: { type: 'string', description: 'Order ID' },
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
status: { type: 'string', description: 'Order status' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
action: { type: 'string', description: 'Action (buy/sell)' },
|
||||
count: { type: 'number', description: 'Number of contracts' },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts' },
|
||||
created_time: { type: 'string', description: 'Order creation time' },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time' },
|
||||
order_group_id: { type: 'string', description: 'Order group ID' },
|
||||
client_order_id: { type: 'string', description: 'Client order ID' },
|
||||
place_count: { type: 'number', description: 'Place count' },
|
||||
decrease_count: { type: 'number', description: 'Decrease count' },
|
||||
queue_position: { type: 'number', description: 'Queue position' },
|
||||
maker_fill_count: { type: 'number', description: 'Maker fill count' },
|
||||
taker_fill_count: { type: 'number', description: 'Taker fill count' },
|
||||
maker_fees: { type: 'number', description: 'Maker fees' },
|
||||
taker_fees: { type: 'number', description: 'Taker fees' },
|
||||
last_update_time: { type: 'string', description: 'Last update time' },
|
||||
take_profit_order_id: { type: 'string', description: 'Take profit order ID' },
|
||||
stop_loss_order_id: { type: 'string', description: 'Stop loss order ID' },
|
||||
amend_count: { type: 'number', description: 'Amend count' },
|
||||
amend_taker_fill_count: { type: 'number', description: 'Amend taker fill count' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { KalshiAuthParams, KalshiOrder } from '@/tools/kalshi/types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiCancelOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to cancel (required)
|
||||
}
|
||||
|
||||
export interface KalshiCancelOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
reducedBy: number
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCancelOrderTool: ToolConfig<KalshiCancelOrderParams, KalshiCancelOrderResponse> =
|
||||
{
|
||||
id: 'kalshi_cancel_order',
|
||||
name: 'Cancel Order on Kalshi',
|
||||
description: 'Cancel an existing order on Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to cancel (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'DELETE', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'cancel_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
reducedBy: data.reduced_by || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The canceled order object',
|
||||
},
|
||||
reducedBy: {
|
||||
type: 'number',
|
||||
description: 'Number of contracts canceled',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export interface KalshiCancelOrderV2Params extends KalshiAuthParams {
|
||||
orderId: string // Order ID to cancel (required)
|
||||
}
|
||||
|
||||
export interface KalshiCancelOrderV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
order: {
|
||||
order_id: string
|
||||
user_id: string | null
|
||||
client_order_id: string | null
|
||||
ticker: string
|
||||
side: string
|
||||
action: string
|
||||
type: string
|
||||
status: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
yes_price_dollars: string | null
|
||||
no_price_dollars: string | null
|
||||
fill_count: number | null
|
||||
fill_count_fp: string | null
|
||||
remaining_count: number | null
|
||||
remaining_count_fp: string | null
|
||||
initial_count: number | null
|
||||
initial_count_fp: string | null
|
||||
taker_fees: number | null
|
||||
maker_fees: number | null
|
||||
taker_fees_dollars: string | null
|
||||
maker_fees_dollars: string | null
|
||||
taker_fill_cost: number | null
|
||||
maker_fill_cost: number | null
|
||||
taker_fill_cost_dollars: string | null
|
||||
maker_fill_cost_dollars: string | null
|
||||
queue_position: number | null
|
||||
expiration_time: string | null
|
||||
created_time: string | null
|
||||
last_update_time: string | null
|
||||
self_trade_prevention_type: string | null
|
||||
order_group_id: string | null
|
||||
cancel_order_on_pause: boolean | null
|
||||
}
|
||||
reduced_by: number
|
||||
reduced_by_fp: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCancelOrderV2Tool: ToolConfig<
|
||||
KalshiCancelOrderV2Params,
|
||||
KalshiCancelOrderV2Response
|
||||
> = {
|
||||
id: 'kalshi_cancel_order_v2',
|
||||
name: 'Cancel Order on Kalshi V2',
|
||||
description: 'Cancel an existing order on Kalshi (V2 with full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to cancel (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'DELETE', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'cancel_order_v2')
|
||||
}
|
||||
|
||||
const order = data.order || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: {
|
||||
order_id: order.order_id ?? null,
|
||||
user_id: order.user_id ?? null,
|
||||
client_order_id: order.client_order_id ?? null,
|
||||
ticker: order.ticker ?? null,
|
||||
side: order.side ?? null,
|
||||
action: order.action ?? null,
|
||||
type: order.type ?? null,
|
||||
status: order.status ?? null,
|
||||
yes_price: order.yes_price ?? null,
|
||||
no_price: order.no_price ?? null,
|
||||
yes_price_dollars: order.yes_price_dollars ?? null,
|
||||
no_price_dollars: order.no_price_dollars ?? null,
|
||||
fill_count: order.fill_count ?? null,
|
||||
fill_count_fp: order.fill_count_fp ?? null,
|
||||
remaining_count: order.remaining_count ?? null,
|
||||
remaining_count_fp: order.remaining_count_fp ?? null,
|
||||
initial_count: order.initial_count ?? null,
|
||||
initial_count_fp: order.initial_count_fp ?? null,
|
||||
taker_fees: order.taker_fees ?? null,
|
||||
maker_fees: order.maker_fees ?? null,
|
||||
taker_fees_dollars: order.taker_fees_dollars ?? null,
|
||||
maker_fees_dollars: order.maker_fees_dollars ?? null,
|
||||
taker_fill_cost: order.taker_fill_cost ?? null,
|
||||
maker_fill_cost: order.maker_fill_cost ?? null,
|
||||
taker_fill_cost_dollars: order.taker_fill_cost_dollars ?? null,
|
||||
maker_fill_cost_dollars: order.maker_fill_cost_dollars ?? null,
|
||||
queue_position: order.queue_position ?? null,
|
||||
expiration_time: order.expiration_time ?? null,
|
||||
created_time: order.created_time ?? null,
|
||||
last_update_time: order.last_update_time ?? null,
|
||||
self_trade_prevention_type: order.self_trade_prevention_type ?? null,
|
||||
order_group_id: order.order_group_id ?? null,
|
||||
cancel_order_on_pause: order.cancel_order_on_pause ?? null,
|
||||
},
|
||||
reduced_by: data.reduced_by ?? 0,
|
||||
reduced_by_fp: data.reduced_by_fp ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The canceled order object with full API response fields',
|
||||
properties: {
|
||||
order_id: { type: 'string', description: 'Order ID' },
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
client_order_id: { type: 'string', description: 'Client order ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
action: { type: 'string', description: 'Action (buy/sell)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
status: { type: 'string', description: 'Order status (resting/canceled/executed)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
yes_price_dollars: { type: 'string', description: 'Yes price in dollars' },
|
||||
no_price_dollars: { type: 'string', description: 'No price in dollars' },
|
||||
fill_count: { type: 'number', description: 'Filled contract count' },
|
||||
fill_count_fp: { type: 'string', description: 'Filled count (fixed-point)' },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts' },
|
||||
remaining_count_fp: { type: 'string', description: 'Remaining count (fixed-point)' },
|
||||
initial_count: { type: 'number', description: 'Initial contract count' },
|
||||
initial_count_fp: { type: 'string', description: 'Initial count (fixed-point)' },
|
||||
taker_fees: { type: 'number', description: 'Taker fees in cents' },
|
||||
maker_fees: { type: 'number', description: 'Maker fees in cents' },
|
||||
taker_fees_dollars: { type: 'string', description: 'Taker fees in dollars' },
|
||||
maker_fees_dollars: { type: 'string', description: 'Maker fees in dollars' },
|
||||
taker_fill_cost: { type: 'number', description: 'Taker fill cost in cents' },
|
||||
maker_fill_cost: { type: 'number', description: 'Maker fill cost in cents' },
|
||||
taker_fill_cost_dollars: { type: 'string', description: 'Taker fill cost in dollars' },
|
||||
maker_fill_cost_dollars: { type: 'string', description: 'Maker fill cost in dollars' },
|
||||
queue_position: { type: 'number', description: 'Queue position (deprecated)' },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time' },
|
||||
created_time: { type: 'string', description: 'Order creation time' },
|
||||
last_update_time: { type: 'string', description: 'Last update time' },
|
||||
self_trade_prevention_type: { type: 'string', description: 'Self-trade prevention type' },
|
||||
order_group_id: { type: 'string', description: 'Order group ID' },
|
||||
cancel_order_on_pause: { type: 'boolean', description: 'Cancel on market pause' },
|
||||
},
|
||||
},
|
||||
reduced_by: {
|
||||
type: 'number',
|
||||
description: 'Number of contracts canceled',
|
||||
},
|
||||
reduced_by_fp: {
|
||||
type: 'string',
|
||||
description: 'Number of contracts canceled in fixed-point format',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
import type { KalshiAuthParams, KalshiOrder } from '@/tools/kalshi/types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiCreateOrderParams extends KalshiAuthParams {
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
count: string // Number of contracts (required)
|
||||
type?: string // 'limit' or 'market' (default: limit)
|
||||
yesPrice?: string // Yes price in cents (1-99)
|
||||
noPrice?: string // No price in cents (1-99)
|
||||
yesPriceDollars?: string // Yes price in dollars (e.g., "0.56")
|
||||
noPriceDollars?: string // No price in dollars (e.g., "0.56")
|
||||
clientOrderId?: string // Custom order identifier
|
||||
expirationTs?: string // Unix timestamp expiration
|
||||
timeInForce?: string // 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'
|
||||
buyMaxCost?: string // Maximum cost in cents
|
||||
postOnly?: string // 'true' or 'false' - maker-only orders
|
||||
reduceOnly?: string // 'true' or 'false' - position reduction only
|
||||
selfTradePreventionType?: string // 'taker_at_cross' or 'maker'
|
||||
orderGroupId?: string // Associated order group
|
||||
}
|
||||
|
||||
export interface KalshiCreateOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCreateOrderTool: ToolConfig<KalshiCreateOrderParams, KalshiCreateOrderResponse> =
|
||||
{
|
||||
id: 'kalshi_create_order',
|
||||
name: 'Create Order on Kalshi',
|
||||
description: 'Create a new order on a Kalshi prediction market',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Side of the order: "yes" or "no"',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action type: "buy" or "sell"',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of contracts to trade (e.g., "10", "100")',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order type: "limit" or "market" (default: "limit")',
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'No price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'No price in dollars (e.g., "0.56")',
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom order identifier',
|
||||
},
|
||||
expirationTs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unix timestamp for order expiration',
|
||||
},
|
||||
timeInForce: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Time in force: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'",
|
||||
},
|
||||
buyMaxCost: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum cost in cents (auto-enables fill_or_kill)',
|
||||
},
|
||||
postOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Set to 'true' for maker-only orders",
|
||||
},
|
||||
reduceOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Set to 'true' for position reduction only",
|
||||
},
|
||||
selfTradePreventionType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Self-trade prevention: 'taker_at_cross' or 'maker'",
|
||||
},
|
||||
orderGroupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Associated order group ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/portfolio/orders'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/orders'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
count: Number.parseInt(params.count, 10),
|
||||
}
|
||||
|
||||
if (params.type) body.type = params.type.toLowerCase()
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
if (params.clientOrderId) body.client_order_id = params.clientOrderId
|
||||
if (params.expirationTs) body.expiration_ts = Number.parseInt(params.expirationTs, 10)
|
||||
if (params.timeInForce) body.time_in_force = params.timeInForce
|
||||
if (params.buyMaxCost) body.buy_max_cost = Number.parseInt(params.buyMaxCost, 10)
|
||||
if (params.postOnly) body.post_only = params.postOnly === 'true'
|
||||
if (params.reduceOnly) body.reduce_only = params.reduceOnly === 'true'
|
||||
if (params.selfTradePreventionType)
|
||||
body.self_trade_prevention_type = params.selfTradePreventionType
|
||||
if (params.orderGroupId) body.order_group_id = params.orderGroupId
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'create_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The created order object',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export interface KalshiCreateOrderV2Params extends KalshiAuthParams {
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
count?: string // Number of contracts (optional - provide count or countFp)
|
||||
type?: string // 'limit' or 'market' (default: limit)
|
||||
yesPrice?: string // Yes price in cents (1-99)
|
||||
noPrice?: string // No price in cents (1-99)
|
||||
yesPriceDollars?: string // Yes price in dollars (e.g., "0.56")
|
||||
noPriceDollars?: string // No price in dollars (e.g., "0.56")
|
||||
clientOrderId?: string // Custom order identifier
|
||||
expirationTs?: string // Unix timestamp expiration
|
||||
timeInForce?: string // 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'
|
||||
buyMaxCost?: string // Maximum cost in cents
|
||||
postOnly?: string // 'true' or 'false' - maker-only orders
|
||||
reduceOnly?: string // 'true' or 'false' - position reduction only
|
||||
selfTradePreventionType?: string // 'taker_at_cross' or 'maker'
|
||||
orderGroupId?: string // Associated order group
|
||||
countFp?: string // Count in fixed-point (for fractional contracts)
|
||||
cancelOrderOnPause?: string // 'true' or 'false' - cancel on market pause
|
||||
subaccount?: string // Subaccount to use for the order
|
||||
}
|
||||
|
||||
export interface KalshiCreateOrderV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
order: {
|
||||
order_id: string
|
||||
user_id: string | null
|
||||
client_order_id: string | null
|
||||
ticker: string
|
||||
side: string
|
||||
action: string
|
||||
type: string
|
||||
status: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
yes_price_dollars: string | null
|
||||
no_price_dollars: string | null
|
||||
fill_count: number | null
|
||||
fill_count_fp: string | null
|
||||
remaining_count: number | null
|
||||
remaining_count_fp: string | null
|
||||
initial_count: number | null
|
||||
initial_count_fp: string | null
|
||||
taker_fees: number | null
|
||||
maker_fees: number | null
|
||||
taker_fees_dollars: string | null
|
||||
maker_fees_dollars: string | null
|
||||
taker_fill_cost: number | null
|
||||
maker_fill_cost: number | null
|
||||
taker_fill_cost_dollars: string | null
|
||||
maker_fill_cost_dollars: string | null
|
||||
queue_position: number | null
|
||||
expiration_time: string | null
|
||||
created_time: string | null
|
||||
last_update_time: string | null
|
||||
self_trade_prevention_type: string | null
|
||||
order_group_id: string | null
|
||||
cancel_order_on_pause: boolean | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCreateOrderV2Tool: ToolConfig<
|
||||
KalshiCreateOrderV2Params,
|
||||
KalshiCreateOrderV2Response
|
||||
> = {
|
||||
id: 'kalshi_create_order_v2',
|
||||
name: 'Create Order on Kalshi V2',
|
||||
description: 'Create a new order on a Kalshi prediction market (V2 with full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Side of the order: "yes" or "no"',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action type: "buy" or "sell"',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of contracts to trade (e.g., "10", "100"). Provide count or countFp',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order type: "limit" or "market" (default: "limit")',
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'No price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'No price in dollars (e.g., "0.56")',
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom order identifier',
|
||||
},
|
||||
expirationTs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unix timestamp for order expiration',
|
||||
},
|
||||
timeInForce: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Time in force: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'",
|
||||
},
|
||||
buyMaxCost: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum cost in cents (auto-enables fill_or_kill)',
|
||||
},
|
||||
postOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Set to 'true' for maker-only orders",
|
||||
},
|
||||
reduceOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Set to 'true' for position reduction only",
|
||||
},
|
||||
selfTradePreventionType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Self-trade prevention: 'taker_at_cross' or 'maker'",
|
||||
},
|
||||
orderGroupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Associated order group ID',
|
||||
},
|
||||
countFp: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Count in fixed-point for fractional contracts',
|
||||
},
|
||||
cancelOrderOnPause: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Set to 'true' to cancel order on market pause",
|
||||
},
|
||||
subaccount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Subaccount to use for the order',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/portfolio/orders'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/orders'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
}
|
||||
|
||||
// count or count_fp must be provided (but not both required)
|
||||
if (params.count) body.count = Number.parseInt(params.count, 10)
|
||||
if (params.countFp) body.count_fp = params.countFp
|
||||
if (params.type) body.type = params.type.toLowerCase()
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
if (params.clientOrderId) body.client_order_id = params.clientOrderId
|
||||
if (params.expirationTs) body.expiration_ts = Number.parseInt(params.expirationTs, 10)
|
||||
if (params.timeInForce) body.time_in_force = params.timeInForce
|
||||
if (params.buyMaxCost) body.buy_max_cost = Number.parseInt(params.buyMaxCost, 10)
|
||||
if (params.postOnly) body.post_only = params.postOnly === 'true'
|
||||
if (params.reduceOnly) body.reduce_only = params.reduceOnly === 'true'
|
||||
if (params.selfTradePreventionType)
|
||||
body.self_trade_prevention_type = params.selfTradePreventionType
|
||||
if (params.orderGroupId) body.order_group_id = params.orderGroupId
|
||||
if (params.cancelOrderOnPause)
|
||||
body.cancel_order_on_pause = params.cancelOrderOnPause === 'true'
|
||||
if (params.subaccount) body.subaccount = params.subaccount
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'create_order_v2')
|
||||
}
|
||||
|
||||
const order = data.order || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: {
|
||||
order_id: order.order_id ?? null,
|
||||
user_id: order.user_id ?? null,
|
||||
client_order_id: order.client_order_id ?? null,
|
||||
ticker: order.ticker ?? null,
|
||||
side: order.side ?? null,
|
||||
action: order.action ?? null,
|
||||
type: order.type ?? null,
|
||||
status: order.status ?? null,
|
||||
yes_price: order.yes_price ?? null,
|
||||
no_price: order.no_price ?? null,
|
||||
yes_price_dollars: order.yes_price_dollars ?? null,
|
||||
no_price_dollars: order.no_price_dollars ?? null,
|
||||
fill_count: order.fill_count ?? null,
|
||||
fill_count_fp: order.fill_count_fp ?? null,
|
||||
remaining_count: order.remaining_count ?? null,
|
||||
remaining_count_fp: order.remaining_count_fp ?? null,
|
||||
initial_count: order.initial_count ?? null,
|
||||
initial_count_fp: order.initial_count_fp ?? null,
|
||||
taker_fees: order.taker_fees ?? null,
|
||||
maker_fees: order.maker_fees ?? null,
|
||||
taker_fees_dollars: order.taker_fees_dollars ?? null,
|
||||
maker_fees_dollars: order.maker_fees_dollars ?? null,
|
||||
taker_fill_cost: order.taker_fill_cost ?? null,
|
||||
maker_fill_cost: order.maker_fill_cost ?? null,
|
||||
taker_fill_cost_dollars: order.taker_fill_cost_dollars ?? null,
|
||||
maker_fill_cost_dollars: order.maker_fill_cost_dollars ?? null,
|
||||
queue_position: order.queue_position ?? null,
|
||||
expiration_time: order.expiration_time ?? null,
|
||||
created_time: order.created_time ?? null,
|
||||
last_update_time: order.last_update_time ?? null,
|
||||
self_trade_prevention_type: order.self_trade_prevention_type ?? null,
|
||||
order_group_id: order.order_group_id ?? null,
|
||||
cancel_order_on_pause: order.cancel_order_on_pause ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'The created order object with full API response fields',
|
||||
properties: {
|
||||
order_id: { type: 'string', description: 'Order ID' },
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
client_order_id: { type: 'string', description: 'Client order ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
action: { type: 'string', description: 'Action (buy/sell)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
status: { type: 'string', description: 'Order status (resting/canceled/executed)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
yes_price_dollars: { type: 'string', description: 'Yes price in dollars' },
|
||||
no_price_dollars: { type: 'string', description: 'No price in dollars' },
|
||||
fill_count: { type: 'number', description: 'Filled contract count' },
|
||||
fill_count_fp: { type: 'string', description: 'Filled count (fixed-point)' },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts' },
|
||||
remaining_count_fp: { type: 'string', description: 'Remaining count (fixed-point)' },
|
||||
initial_count: { type: 'number', description: 'Initial contract count' },
|
||||
initial_count_fp: { type: 'string', description: 'Initial count (fixed-point)' },
|
||||
taker_fees: { type: 'number', description: 'Taker fees in cents' },
|
||||
maker_fees: { type: 'number', description: 'Maker fees in cents' },
|
||||
taker_fees_dollars: { type: 'string', description: 'Taker fees in dollars' },
|
||||
maker_fees_dollars: { type: 'string', description: 'Maker fees in dollars' },
|
||||
taker_fill_cost: { type: 'number', description: 'Taker fill cost in cents' },
|
||||
maker_fill_cost: { type: 'number', description: 'Maker fill cost in cents' },
|
||||
taker_fill_cost_dollars: { type: 'string', description: 'Taker fill cost in dollars' },
|
||||
maker_fill_cost_dollars: { type: 'string', description: 'Maker fill cost in dollars' },
|
||||
queue_position: { type: 'number', description: 'Queue position (deprecated)' },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time' },
|
||||
created_time: { type: 'string', description: 'Order creation time' },
|
||||
last_update_time: { type: 'string', description: 'Last update time' },
|
||||
self_trade_prevention_type: { type: 'string', description: 'Self-trade prevention type' },
|
||||
order_group_id: { type: 'string', description: 'Order group ID' },
|
||||
cancel_order_on_pause: { type: 'boolean', description: 'Cancel on market pause' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import type { KalshiAuthParams } from '@/tools/kalshi/types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetBalanceParams extends KalshiAuthParams {}
|
||||
|
||||
export interface KalshiGetBalanceResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
balance: number // In cents
|
||||
portfolioValue: number // In cents
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetBalanceTool: ToolConfig<KalshiGetBalanceParams, KalshiGetBalanceResponse> = {
|
||||
id: 'kalshi_get_balance',
|
||||
name: 'Get Balance from Kalshi',
|
||||
description: 'Retrieve your account balance and portfolio value from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/portfolio/balance'),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/balance'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_balance')
|
||||
}
|
||||
|
||||
const balance = data.balance ?? 0
|
||||
const portfolioValue = data.portfolio_value ?? 0
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
balance,
|
||||
portfolioValue,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
balance: { type: 'number', description: 'Account balance in cents' },
|
||||
portfolioValue: { type: 'number', description: 'Portfolio value in cents' },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Balance
|
||||
*/
|
||||
export interface KalshiGetBalanceV2Params extends KalshiAuthParams {}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetBalanceV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
balance: number
|
||||
portfolio_value: number
|
||||
updated_ts: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetBalanceV2Tool: ToolConfig<
|
||||
KalshiGetBalanceV2Params,
|
||||
KalshiGetBalanceV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_balance_v2',
|
||||
name: 'Get Balance from Kalshi V2',
|
||||
description:
|
||||
'Retrieve your account balance and portfolio value from Kalshi (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/portfolio/balance'),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/balance'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_balance_v2')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
balance: data.balance ?? 0,
|
||||
portfolio_value: data.portfolio_value ?? 0,
|
||||
updated_ts: data.updated_ts ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
balance: { type: 'number', description: 'Account balance in cents' },
|
||||
portfolio_value: { type: 'number', description: 'Portfolio value in cents' },
|
||||
updated_ts: { type: 'number', description: 'Unix timestamp of last update (seconds)' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
import type { KalshiCandlestick } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetCandlesticksParams {
|
||||
seriesTicker: string
|
||||
ticker: string
|
||||
startTs: number
|
||||
endTs: number
|
||||
periodInterval: number // 1, 60, or 1440 (1min, 1hour, 1day)
|
||||
}
|
||||
|
||||
export interface KalshiGetCandlesticksResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
candlesticks: KalshiCandlestick[]
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetCandlesticksTool: ToolConfig<
|
||||
KalshiGetCandlesticksParams,
|
||||
KalshiGetCandlesticksResponse
|
||||
> = {
|
||||
id: 'kalshi_get_candlesticks',
|
||||
name: 'Get Market Candlesticks from Kalshi',
|
||||
description: 'Retrieve OHLC candlestick data for a specific market',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
startTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
endTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
periodInterval: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period interval: 1 (1 minute), 60 (1 hour), or 1440 (1 day)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('start_ts', params.startTs.toString())
|
||||
queryParams.append('end_ts', params.endTs.toString())
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(
|
||||
`/series/${params.seriesTicker.trim()}/markets/${params.ticker.trim()}/candlesticks`
|
||||
)
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_candlesticks')
|
||||
}
|
||||
|
||||
const candlesticks = data.candlesticks || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
candlesticks,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
candlesticks: {
|
||||
type: 'array',
|
||||
description: 'Array of OHLC candlestick data',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* BidAskDistribution - OHLC data for yes_bid and yes_ask
|
||||
*/
|
||||
interface BidAskDistribution {
|
||||
open: number | null
|
||||
open_dollars: string | null
|
||||
low: number | null
|
||||
low_dollars: string | null
|
||||
high: number | null
|
||||
high_dollars: string | null
|
||||
close: number | null
|
||||
close_dollars: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* PriceDistribution - Extended OHLC data for price field
|
||||
*/
|
||||
interface PriceDistribution {
|
||||
open: number | null
|
||||
open_dollars: string | null
|
||||
low: number | null
|
||||
low_dollars: string | null
|
||||
high: number | null
|
||||
high_dollars: string | null
|
||||
close: number | null
|
||||
close_dollars: string | null
|
||||
mean: number | null
|
||||
mean_dollars: string | null
|
||||
previous: number | null
|
||||
previous_dollars: string | null
|
||||
min: number | null
|
||||
min_dollars: string | null
|
||||
max: number | null
|
||||
max_dollars: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Candlesticks Tool - Returns exact Kalshi API response structure
|
||||
*/
|
||||
export interface KalshiGetCandlesticksV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
ticker: string
|
||||
candlesticks: Array<{
|
||||
end_period_ts: number | null
|
||||
yes_bid: BidAskDistribution
|
||||
yes_ask: BidAskDistribution
|
||||
price: PriceDistribution
|
||||
volume: number | null
|
||||
volume_fp: string | null
|
||||
open_interest: number | null
|
||||
open_interest_fp: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetCandlesticksV2Tool: ToolConfig<
|
||||
KalshiGetCandlesticksParams,
|
||||
KalshiGetCandlesticksV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_candlesticks_v2',
|
||||
name: 'Get Market Candlesticks from Kalshi V2',
|
||||
description: 'Retrieve OHLC candlestick data for a specific market (V2 - full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
startTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
endTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
periodInterval: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period interval: 1 (1 minute), 60 (1 hour), or 1440 (1 day)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('start_ts', params.startTs.toString())
|
||||
queryParams.append('end_ts', params.endTs.toString())
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(
|
||||
`/series/${params.seriesTicker.trim()}/markets/${params.ticker.trim()}/candlesticks`
|
||||
)
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_candlesticks_v2')
|
||||
}
|
||||
|
||||
const mapBidAsk = (obj: Record<string, unknown> | null): BidAskDistribution => ({
|
||||
open: (obj?.open as number) ?? null,
|
||||
open_dollars: (obj?.open_dollars as string) ?? null,
|
||||
low: (obj?.low as number) ?? null,
|
||||
low_dollars: (obj?.low_dollars as string) ?? null,
|
||||
high: (obj?.high as number) ?? null,
|
||||
high_dollars: (obj?.high_dollars as string) ?? null,
|
||||
close: (obj?.close as number) ?? null,
|
||||
close_dollars: (obj?.close_dollars as string) ?? null,
|
||||
})
|
||||
|
||||
const mapPrice = (obj: Record<string, unknown> | null): PriceDistribution => ({
|
||||
open: (obj?.open as number) ?? null,
|
||||
open_dollars: (obj?.open_dollars as string) ?? null,
|
||||
low: (obj?.low as number) ?? null,
|
||||
low_dollars: (obj?.low_dollars as string) ?? null,
|
||||
high: (obj?.high as number) ?? null,
|
||||
high_dollars: (obj?.high_dollars as string) ?? null,
|
||||
close: (obj?.close as number) ?? null,
|
||||
close_dollars: (obj?.close_dollars as string) ?? null,
|
||||
mean: (obj?.mean as number) ?? null,
|
||||
mean_dollars: (obj?.mean_dollars as string) ?? null,
|
||||
previous: (obj?.previous as number) ?? null,
|
||||
previous_dollars: (obj?.previous_dollars as string) ?? null,
|
||||
min: (obj?.min as number) ?? null,
|
||||
min_dollars: (obj?.min_dollars as string) ?? null,
|
||||
max: (obj?.max as number) ?? null,
|
||||
max_dollars: (obj?.max_dollars as string) ?? null,
|
||||
})
|
||||
|
||||
const candlesticks = (data.candlesticks || []).map((c: Record<string, unknown>) => ({
|
||||
end_period_ts: (c.end_period_ts as number) ?? null,
|
||||
yes_bid: mapBidAsk(c.yes_bid as Record<string, unknown> | null),
|
||||
yes_ask: mapBidAsk(c.yes_ask as Record<string, unknown> | null),
|
||||
price: mapPrice(c.price as Record<string, unknown> | null),
|
||||
volume: (c.volume as number) ?? null,
|
||||
volume_fp: (c.volume_fp as string) ?? null,
|
||||
open_interest: (c.open_interest as number) ?? null,
|
||||
open_interest_fp: (c.open_interest_fp as string) ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ticker: data.ticker ?? null,
|
||||
candlesticks,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
description: 'Market ticker',
|
||||
},
|
||||
candlesticks: {
|
||||
type: 'array',
|
||||
description: 'Array of OHLC candlestick data with nested bid/ask/price objects',
|
||||
properties: {
|
||||
end_period_ts: { type: 'number', description: 'End period timestamp (Unix)' },
|
||||
yes_bid: {
|
||||
type: 'object',
|
||||
description: 'Yes bid OHLC data',
|
||||
properties: {
|
||||
open: { type: 'number', description: 'Open price (cents)' },
|
||||
open_dollars: { type: 'string', description: 'Open price (dollars)' },
|
||||
low: { type: 'number', description: 'Low price (cents)' },
|
||||
low_dollars: { type: 'string', description: 'Low price (dollars)' },
|
||||
high: { type: 'number', description: 'High price (cents)' },
|
||||
high_dollars: { type: 'string', description: 'High price (dollars)' },
|
||||
close: { type: 'number', description: 'Close price (cents)' },
|
||||
close_dollars: { type: 'string', description: 'Close price (dollars)' },
|
||||
},
|
||||
},
|
||||
yes_ask: {
|
||||
type: 'object',
|
||||
description: 'Yes ask OHLC data',
|
||||
properties: {
|
||||
open: { type: 'number', description: 'Open price (cents)' },
|
||||
open_dollars: { type: 'string', description: 'Open price (dollars)' },
|
||||
low: { type: 'number', description: 'Low price (cents)' },
|
||||
low_dollars: { type: 'string', description: 'Low price (dollars)' },
|
||||
high: { type: 'number', description: 'High price (cents)' },
|
||||
high_dollars: { type: 'string', description: 'High price (dollars)' },
|
||||
close: { type: 'number', description: 'Close price (cents)' },
|
||||
close_dollars: { type: 'string', description: 'Close price (dollars)' },
|
||||
},
|
||||
},
|
||||
price: {
|
||||
type: 'object',
|
||||
description: 'Trade price OHLC data with additional statistics',
|
||||
properties: {
|
||||
open: { type: 'number', description: 'Open price (cents)' },
|
||||
open_dollars: { type: 'string', description: 'Open price (dollars)' },
|
||||
low: { type: 'number', description: 'Low price (cents)' },
|
||||
low_dollars: { type: 'string', description: 'Low price (dollars)' },
|
||||
high: { type: 'number', description: 'High price (cents)' },
|
||||
high_dollars: { type: 'string', description: 'High price (dollars)' },
|
||||
close: { type: 'number', description: 'Close price (cents)' },
|
||||
close_dollars: { type: 'string', description: 'Close price (dollars)' },
|
||||
mean: { type: 'number', description: 'Mean price (cents)' },
|
||||
mean_dollars: { type: 'string', description: 'Mean price (dollars)' },
|
||||
previous: { type: 'number', description: 'Previous price (cents)' },
|
||||
previous_dollars: { type: 'string', description: 'Previous price (dollars)' },
|
||||
min: { type: 'number', description: 'Min price (cents)' },
|
||||
min_dollars: { type: 'string', description: 'Min price (dollars)' },
|
||||
max: { type: 'number', description: 'Max price (cents)' },
|
||||
max_dollars: { type: 'string', description: 'Max price (dollars)' },
|
||||
},
|
||||
},
|
||||
volume: { type: 'number', description: 'Volume (contracts)' },
|
||||
volume_fp: { type: 'string', description: 'Volume (fixed-point string)' },
|
||||
open_interest: { type: 'number', description: 'Open interest (contracts)' },
|
||||
open_interest_fp: { type: 'string', description: 'Open interest (fixed-point string)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
import type { KalshiEvent } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetEventParams {
|
||||
eventTicker: string // Event ticker
|
||||
withNestedMarkets?: string // 'true' or 'false'
|
||||
}
|
||||
|
||||
export interface KalshiGetEventResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
event: KalshiEvent
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventTool: ToolConfig<KalshiGetEventParams, KalshiGetEventResponse> = {
|
||||
id: 'kalshi_get_event',
|
||||
name: 'Get Event from Kalshi',
|
||||
description: 'Retrieve details of a specific event by ticker',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
withNestedMarkets: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include nested markets in response (true/false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.withNestedMarkets)
|
||||
queryParams.append('with_nested_markets', params.withNestedMarkets)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(`/events/${params.eventTicker.trim()}`)
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_event')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
event: data.event,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
event: {
|
||||
type: 'object',
|
||||
description: 'Event object with details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Event
|
||||
*/
|
||||
export interface KalshiGetEventV2Params {
|
||||
eventTicker: string
|
||||
withNestedMarkets?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetEventV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
event: {
|
||||
event_ticker: string
|
||||
series_ticker: string
|
||||
title: string
|
||||
sub_title: string | null
|
||||
mutually_exclusive: boolean
|
||||
category: string
|
||||
collateral_return_type: string | null
|
||||
strike_date: string | null
|
||||
strike_period: string | null
|
||||
available_on_brokers: boolean | null
|
||||
product_metadata: Record<string, unknown> | null
|
||||
markets: Array<{
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
market_type: string
|
||||
title: string
|
||||
subtitle: string | null
|
||||
yes_sub_title: string | null
|
||||
no_sub_title: string | null
|
||||
open_time: string
|
||||
close_time: string
|
||||
expiration_time: string
|
||||
status: string
|
||||
yes_bid: number
|
||||
yes_ask: number
|
||||
no_bid: number
|
||||
no_ask: number
|
||||
last_price: number
|
||||
previous_yes_bid: number | null
|
||||
previous_yes_ask: number | null
|
||||
previous_price: number | null
|
||||
volume: number
|
||||
volume_24h: number
|
||||
liquidity: number | null
|
||||
open_interest: number | null
|
||||
result: string | null
|
||||
cap_strike: number | null
|
||||
floor_strike: number | null
|
||||
}> | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventV2Tool: ToolConfig<KalshiGetEventV2Params, KalshiGetEventV2Response> = {
|
||||
id: 'kalshi_get_event_v2',
|
||||
name: 'Get Event from Kalshi V2',
|
||||
description: 'Retrieve details of a specific event by ticker (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
withNestedMarkets: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include nested markets in response (true/false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.withNestedMarkets)
|
||||
queryParams.append('with_nested_markets', params.withNestedMarkets)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(`/events/${params.eventTicker.trim()}`)
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_event_v2')
|
||||
}
|
||||
|
||||
const event = data.event || {}
|
||||
const markets =
|
||||
event.markets?.map((m: Record<string, unknown>) => ({
|
||||
ticker: m.ticker ?? null,
|
||||
event_ticker: m.event_ticker ?? null,
|
||||
market_type: m.market_type ?? null,
|
||||
title: m.title ?? null,
|
||||
subtitle: m.subtitle ?? null,
|
||||
yes_sub_title: m.yes_sub_title ?? null,
|
||||
no_sub_title: m.no_sub_title ?? null,
|
||||
open_time: m.open_time ?? null,
|
||||
close_time: m.close_time ?? null,
|
||||
expiration_time: m.expiration_time ?? null,
|
||||
status: m.status ?? null,
|
||||
yes_bid: m.yes_bid ?? 0,
|
||||
yes_ask: m.yes_ask ?? 0,
|
||||
no_bid: m.no_bid ?? 0,
|
||||
no_ask: m.no_ask ?? 0,
|
||||
last_price: m.last_price ?? 0,
|
||||
previous_yes_bid: m.previous_yes_bid ?? null,
|
||||
previous_yes_ask: m.previous_yes_ask ?? null,
|
||||
previous_price: m.previous_price ?? null,
|
||||
volume: m.volume ?? 0,
|
||||
volume_24h: m.volume_24h ?? 0,
|
||||
liquidity: m.liquidity ?? null,
|
||||
open_interest: m.open_interest ?? null,
|
||||
result: m.result ?? null,
|
||||
cap_strike: m.cap_strike ?? null,
|
||||
floor_strike: m.floor_strike ?? null,
|
||||
})) ?? null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
event: {
|
||||
event_ticker: event.event_ticker ?? null,
|
||||
series_ticker: event.series_ticker ?? null,
|
||||
title: event.title ?? null,
|
||||
sub_title: event.sub_title ?? null,
|
||||
mutually_exclusive: event.mutually_exclusive ?? false,
|
||||
category: event.category ?? null,
|
||||
collateral_return_type: event.collateral_return_type ?? null,
|
||||
strike_date: event.strike_date ?? null,
|
||||
strike_period: event.strike_period ?? null,
|
||||
available_on_brokers: event.available_on_brokers ?? null,
|
||||
product_metadata: event.product_metadata ?? null,
|
||||
markets,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
event: {
|
||||
type: 'object',
|
||||
description: 'Event object with full details matching Kalshi API response',
|
||||
properties: {
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
series_ticker: { type: 'string', description: 'Series ticker' },
|
||||
title: { type: 'string', description: 'Event title' },
|
||||
sub_title: { type: 'string', description: 'Event subtitle' },
|
||||
mutually_exclusive: { type: 'boolean', description: 'Mutually exclusive markets' },
|
||||
category: { type: 'string', description: 'Event category' },
|
||||
collateral_return_type: { type: 'string', description: 'Collateral return type' },
|
||||
strike_date: { type: 'string', description: 'Strike date' },
|
||||
strike_period: { type: 'string', description: 'Strike period' },
|
||||
available_on_brokers: { type: 'boolean', description: 'Available on brokers' },
|
||||
product_metadata: { type: 'object', description: 'Product metadata' },
|
||||
markets: { type: 'array', description: 'Nested markets (if requested)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetEventCandlesticksParams {
|
||||
seriesTicker: string
|
||||
eventTicker: string
|
||||
startTs: number
|
||||
endTs: number
|
||||
periodInterval: number // 1, 60, or 1440 (1min, 1hour, 1day)
|
||||
}
|
||||
|
||||
export interface KalshiGetEventCandlesticksResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
market_candlesticks: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventCandlesticksTool: ToolConfig<
|
||||
KalshiGetEventCandlesticksParams,
|
||||
KalshiGetEventCandlesticksResponse
|
||||
> = {
|
||||
id: 'kalshi_get_event_candlesticks',
|
||||
name: 'Get Event Candlesticks from Kalshi',
|
||||
description: 'Retrieve OHLC candlestick data aggregated across all markets in an event',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
startTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
endTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
periodInterval: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period interval: 1 (1 minute), 60 (1 hour), or 1440 (1 day)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('start_ts', params.startTs.toString())
|
||||
queryParams.append('end_ts', params.endTs.toString())
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
|
||||
const url = buildKalshiUrl(
|
||||
`/series/${params.seriesTicker.trim()}/events/${params.eventTicker.trim()}/candlesticks`
|
||||
)
|
||||
return `${url}?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_event_candlesticks')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
market_candlesticks: data.market_candlesticks || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
market_candlesticks: {
|
||||
type: 'array',
|
||||
description: 'Array of event-level aggregated OHLC candlestick data',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* BidAskDistribution - OHLC data for yes_bid and yes_ask
|
||||
*/
|
||||
interface BidAskDistribution {
|
||||
open: number | null
|
||||
open_dollars: string | null
|
||||
low: number | null
|
||||
low_dollars: string | null
|
||||
high: number | null
|
||||
high_dollars: string | null
|
||||
close: number | null
|
||||
close_dollars: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* PriceDistribution - Extended OHLC data for the price field
|
||||
*/
|
||||
interface PriceDistribution {
|
||||
open: number | null
|
||||
open_dollars: string | null
|
||||
low: number | null
|
||||
low_dollars: string | null
|
||||
high: number | null
|
||||
high_dollars: string | null
|
||||
close: number | null
|
||||
close_dollars: string | null
|
||||
mean: number | null
|
||||
mean_dollars: string | null
|
||||
previous: number | null
|
||||
previous_dollars: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Event Candlesticks Tool - Returns exact Kalshi API response structure
|
||||
*/
|
||||
export interface KalshiGetEventCandlesticksV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
market_tickers: string[] | null
|
||||
adjusted_end_ts: number | null
|
||||
market_candlesticks: Array<{
|
||||
end_period_ts: number | null
|
||||
yes_bid: BidAskDistribution
|
||||
yes_ask: BidAskDistribution
|
||||
price: PriceDistribution
|
||||
volume_fp: string | null
|
||||
open_interest_fp: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventCandlesticksV2Tool: ToolConfig<
|
||||
KalshiGetEventCandlesticksParams,
|
||||
KalshiGetEventCandlesticksV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_event_candlesticks_v2',
|
||||
name: 'Get Event Candlesticks from Kalshi V2',
|
||||
description:
|
||||
'Retrieve OHLC candlestick data aggregated across all markets in an event (V2 - full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
startTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
endTs: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
periodInterval: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period interval: 1 (1 minute), 60 (1 hour), or 1440 (1 day)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('start_ts', params.startTs.toString())
|
||||
queryParams.append('end_ts', params.endTs.toString())
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
|
||||
const url = buildKalshiUrl(
|
||||
`/series/${params.seriesTicker.trim()}/events/${params.eventTicker.trim()}/candlesticks`
|
||||
)
|
||||
return `${url}?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_event_candlesticks_v2')
|
||||
}
|
||||
|
||||
const mapBidAsk = (obj: Record<string, unknown> | null): BidAskDistribution => ({
|
||||
open: (obj?.open as number) ?? null,
|
||||
open_dollars: (obj?.open_dollars as string) ?? null,
|
||||
low: (obj?.low as number) ?? null,
|
||||
low_dollars: (obj?.low_dollars as string) ?? null,
|
||||
high: (obj?.high as number) ?? null,
|
||||
high_dollars: (obj?.high_dollars as string) ?? null,
|
||||
close: (obj?.close as number) ?? null,
|
||||
close_dollars: (obj?.close_dollars as string) ?? null,
|
||||
})
|
||||
|
||||
const mapPrice = (obj: Record<string, unknown> | null): PriceDistribution => ({
|
||||
open: (obj?.open as number) ?? null,
|
||||
open_dollars: (obj?.open_dollars as string) ?? null,
|
||||
low: (obj?.low as number) ?? null,
|
||||
low_dollars: (obj?.low_dollars as string) ?? null,
|
||||
high: (obj?.high as number) ?? null,
|
||||
high_dollars: (obj?.high_dollars as string) ?? null,
|
||||
close: (obj?.close as number) ?? null,
|
||||
close_dollars: (obj?.close_dollars as string) ?? null,
|
||||
mean: (obj?.mean as number) ?? null,
|
||||
mean_dollars: (obj?.mean_dollars as string) ?? null,
|
||||
previous: (obj?.previous as number) ?? null,
|
||||
previous_dollars: (obj?.previous_dollars as string) ?? null,
|
||||
})
|
||||
|
||||
const candlesticks = (data.market_candlesticks || []).map((c: Record<string, unknown>) => ({
|
||||
end_period_ts: (c.end_period_ts as number) ?? null,
|
||||
yes_bid: mapBidAsk(c.yes_bid as Record<string, unknown> | null),
|
||||
yes_ask: mapBidAsk(c.yes_ask as Record<string, unknown> | null),
|
||||
price: mapPrice(c.price as Record<string, unknown> | null),
|
||||
volume_fp: (c.volume_fp as string) ?? null,
|
||||
open_interest_fp: (c.open_interest_fp as string) ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
market_tickers: data.market_tickers ?? null,
|
||||
adjusted_end_ts: data.adjusted_end_ts ?? null,
|
||||
market_candlesticks: candlesticks,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
market_tickers: {
|
||||
type: 'array',
|
||||
description: 'Market tickers included in the aggregated candlesticks',
|
||||
},
|
||||
adjusted_end_ts: {
|
||||
type: 'number',
|
||||
description: 'Adjusted end timestamp used for the candlestick range (Unix seconds)',
|
||||
},
|
||||
market_candlesticks: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Array of event-level aggregated OHLC candlestick data with nested bid/ask/price',
|
||||
properties: {
|
||||
end_period_ts: { type: 'number', description: 'End period timestamp (Unix)' },
|
||||
yes_bid: { type: 'object', description: 'Yes bid OHLC data' },
|
||||
yes_ask: { type: 'object', description: 'Yes ask OHLC data' },
|
||||
price: { type: 'object', description: 'Trade price OHLC data with statistics' },
|
||||
volume_fp: { type: 'string', description: 'Volume (fixed-point string)' },
|
||||
open_interest_fp: { type: 'string', description: 'Open interest (fixed-point string)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import type { KalshiEvent, KalshiPaginationParams, KalshiPagingInfo } from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_EVENT_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetEventsParams extends KalshiPaginationParams {
|
||||
status?: string // open, closed, settled
|
||||
seriesTicker?: string
|
||||
withNestedMarkets?: string // 'true' or 'false'
|
||||
}
|
||||
|
||||
export interface KalshiGetEventsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
events: KalshiEvent[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventsTool: ToolConfig<KalshiGetEventsParams, KalshiGetEventsResponse> = {
|
||||
id: 'kalshi_get_events',
|
||||
name: 'Get Events from Kalshi',
|
||||
description: 'Retrieve a list of events from Kalshi with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event status: "open", "closed", or "settled"',
|
||||
},
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by series ticker (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
withNestedMarkets: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include nested markets in response: "true" or "false"',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-200, default: 200)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.seriesTicker) queryParams.append('series_ticker', params.seriesTicker)
|
||||
if (params.withNestedMarkets)
|
||||
queryParams.append('with_nested_markets', params.withNestedMarkets)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/events')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_events')
|
||||
}
|
||||
|
||||
const events = data.events || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'Array of event objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Events
|
||||
*/
|
||||
export interface KalshiGetEventsV2Params extends KalshiPaginationParams {
|
||||
status?: string
|
||||
seriesTicker?: string
|
||||
withNestedMarkets?: string
|
||||
withMilestones?: string
|
||||
minCloseTs?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetEventsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
events: Array<{
|
||||
event_ticker: string
|
||||
series_ticker: string
|
||||
title: string
|
||||
sub_title: string | null
|
||||
mutually_exclusive: boolean
|
||||
category: string
|
||||
collateral_return_type: string | null
|
||||
strike_date: string | null
|
||||
strike_period: string | null
|
||||
available_on_brokers: boolean | null
|
||||
product_metadata: Record<string, unknown> | null
|
||||
markets: Array<Record<string, unknown>> | null
|
||||
}>
|
||||
milestones: Array<{
|
||||
id: string | null
|
||||
category: string | null
|
||||
type: string | null
|
||||
title: string | null
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
notification_message: string | null
|
||||
primary_event_tickers: string[] | null
|
||||
related_event_tickers: string[] | null
|
||||
last_updated_ts: string | null
|
||||
}> | null
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetEventsV2Tool: ToolConfig<KalshiGetEventsV2Params, KalshiGetEventsV2Response> =
|
||||
{
|
||||
id: 'kalshi_get_events_v2',
|
||||
name: 'Get Events from Kalshi V2',
|
||||
description:
|
||||
'Retrieve a list of events from Kalshi with optional filtering (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event status: "open", "closed", or "settled"',
|
||||
},
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by series ticker (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
withNestedMarkets: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include nested markets in response: "true" or "false"',
|
||||
},
|
||||
withMilestones: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include milestones in response: "true" or "false"',
|
||||
},
|
||||
minCloseTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum close timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-200, default: 200)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.seriesTicker) queryParams.append('series_ticker', params.seriesTicker)
|
||||
if (params.withNestedMarkets)
|
||||
queryParams.append('with_nested_markets', params.withNestedMarkets)
|
||||
if (params.withMilestones) queryParams.append('with_milestones', params.withMilestones)
|
||||
if (params.minCloseTs !== undefined)
|
||||
queryParams.append('min_close_ts', params.minCloseTs.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/events')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_events_v2')
|
||||
}
|
||||
|
||||
const events = (data.events || []).map((e: Record<string, unknown>) => ({
|
||||
event_ticker: e.event_ticker ?? null,
|
||||
series_ticker: e.series_ticker ?? null,
|
||||
title: e.title ?? null,
|
||||
sub_title: e.sub_title ?? null,
|
||||
mutually_exclusive: e.mutually_exclusive ?? false,
|
||||
category: e.category ?? null,
|
||||
collateral_return_type: e.collateral_return_type ?? null,
|
||||
strike_date: e.strike_date ?? null,
|
||||
strike_period: e.strike_period ?? null,
|
||||
available_on_brokers: e.available_on_brokers ?? null,
|
||||
product_metadata: e.product_metadata ?? null,
|
||||
markets: e.markets ?? null,
|
||||
}))
|
||||
|
||||
const milestones = data.milestones
|
||||
? (data.milestones as Array<Record<string, unknown>>).map((m) => ({
|
||||
id: (m.id as string) ?? null,
|
||||
category: (m.category as string) ?? null,
|
||||
type: (m.type as string) ?? null,
|
||||
title: (m.title as string | null) ?? null,
|
||||
start_date: (m.start_date as string) ?? null,
|
||||
end_date: (m.end_date as string) ?? null,
|
||||
notification_message: (m.notification_message as string | null) ?? null,
|
||||
primary_event_tickers: (m.primary_event_tickers as string[]) ?? null,
|
||||
related_event_tickers: (m.related_event_tickers as string[]) ?? null,
|
||||
last_updated_ts: (m.last_updated_ts as string) ?? null,
|
||||
}))
|
||||
: null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
milestones,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'Array of event objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
milestones: {
|
||||
type: 'array',
|
||||
description: 'Array of milestone objects (if requested)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Milestone ID' },
|
||||
category: { type: 'string', description: 'Milestone category' },
|
||||
type: { type: 'string', description: 'Milestone type' },
|
||||
title: { type: 'string', description: 'Milestone title' },
|
||||
start_date: { type: 'string', description: 'Milestone start date (ISO 8601)' },
|
||||
end_date: { type: 'string', description: 'Milestone end date (ISO 8601)' },
|
||||
notification_message: { type: 'string', description: 'Notification message' },
|
||||
primary_event_tickers: { type: 'array', description: 'Primary event tickers' },
|
||||
related_event_tickers: { type: 'array', description: 'Related event tickers' },
|
||||
last_updated_ts: { type: 'string', description: 'Last updated time (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_ANNOUNCEMENT_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export type KalshiGetExchangeAnnouncementsParams = Record<string, never>
|
||||
|
||||
export interface KalshiGetExchangeAnnouncementsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
announcements: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeAnnouncementsTool: ToolConfig<
|
||||
KalshiGetExchangeAnnouncementsParams,
|
||||
KalshiGetExchangeAnnouncementsResponse
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_announcements',
|
||||
name: 'Get Exchange Announcements from Kalshi',
|
||||
description: 'Retrieve exchange-wide announcements from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/exchange/announcements'),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_announcements')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
announcements: data.announcements || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
announcements: {
|
||||
type: 'array',
|
||||
description: 'Array of exchange announcement objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_ANNOUNCEMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetExchangeAnnouncementsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
announcements: Array<{
|
||||
type: string | null
|
||||
message: string | null
|
||||
delivery_time: string | null
|
||||
status: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeAnnouncementsV2Tool: ToolConfig<
|
||||
KalshiGetExchangeAnnouncementsParams,
|
||||
KalshiGetExchangeAnnouncementsV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_announcements_v2',
|
||||
name: 'Get Exchange Announcements from Kalshi V2',
|
||||
description: 'Retrieve exchange-wide announcements from Kalshi (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/exchange/announcements'),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_announcements_v2')
|
||||
}
|
||||
|
||||
const announcements = (data.announcements || []).map((a: Record<string, unknown>) => ({
|
||||
type: a.type ?? null,
|
||||
message: a.message ?? null,
|
||||
delivery_time: a.delivery_time ?? null,
|
||||
status: a.status ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
announcements,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
announcements: {
|
||||
type: 'array',
|
||||
description: 'Array of exchange announcement objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_ANNOUNCEMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export type KalshiGetExchangeScheduleParams = Record<string, never>
|
||||
|
||||
export interface KalshiGetExchangeScheduleResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
schedule: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeScheduleTool: ToolConfig<
|
||||
KalshiGetExchangeScheduleParams,
|
||||
KalshiGetExchangeScheduleResponse
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_schedule',
|
||||
name: 'Get Exchange Schedule from Kalshi',
|
||||
description: 'Retrieve the Kalshi exchange trading schedule and maintenance windows',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/exchange/schedule'),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_schedule')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
schedule: data.schedule || {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
schedule: {
|
||||
type: 'object',
|
||||
description: 'Exchange schedule with standard_hours and maintenance_windows',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetExchangeScheduleV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
schedule: {
|
||||
standard_hours: Array<Record<string, unknown>>
|
||||
maintenance_windows: Array<{
|
||||
start_datetime: string | null
|
||||
end_datetime: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeScheduleV2Tool: ToolConfig<
|
||||
KalshiGetExchangeScheduleParams,
|
||||
KalshiGetExchangeScheduleV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_schedule_v2',
|
||||
name: 'Get Exchange Schedule from Kalshi V2',
|
||||
description:
|
||||
'Retrieve the Kalshi exchange trading schedule and maintenance windows (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/exchange/schedule'),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_schedule_v2')
|
||||
}
|
||||
|
||||
const schedule = data.schedule || {}
|
||||
const maintenanceWindows = (schedule.maintenance_windows || []).map(
|
||||
(w: Record<string, unknown>) => ({
|
||||
start_datetime: (w.start_datetime as string) ?? null,
|
||||
end_datetime: (w.end_datetime as string) ?? null,
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
schedule: {
|
||||
standard_hours: schedule.standard_hours ?? [],
|
||||
maintenance_windows: maintenanceWindows,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
schedule: {
|
||||
type: 'object',
|
||||
description: 'Exchange schedule (all times in ET)',
|
||||
properties: {
|
||||
standard_hours: {
|
||||
type: 'array',
|
||||
description: 'Weekly schedules with per-day open/close trading sessions',
|
||||
},
|
||||
maintenance_windows: {
|
||||
type: 'array',
|
||||
description: 'Scheduled maintenance windows with start_datetime and end_datetime',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { KalshiExchangeStatus } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export type KalshiGetExchangeStatusParams = Record<string, never>
|
||||
|
||||
export interface KalshiGetExchangeStatusResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
status: KalshiExchangeStatus
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeStatusTool: ToolConfig<
|
||||
KalshiGetExchangeStatusParams,
|
||||
KalshiGetExchangeStatusResponse
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_status',
|
||||
name: 'Get Exchange Status from Kalshi',
|
||||
description: 'Retrieve the current status of the Kalshi exchange (trading and exchange activity)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => {
|
||||
return buildKalshiUrl('/exchange/status')
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_status')
|
||||
}
|
||||
|
||||
const status = {
|
||||
trading_active: data.trading_active ?? false,
|
||||
exchange_active: data.exchange_active ?? false,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: {
|
||||
type: 'object',
|
||||
description: 'Exchange status with trading_active and exchange_active flags',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Exchange Status
|
||||
*/
|
||||
export type KalshiGetExchangeStatusV2Params = Record<string, never>
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetExchangeStatusV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
exchange_active: boolean
|
||||
trading_active: boolean
|
||||
exchange_estimated_resume_time: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetExchangeStatusV2Tool: ToolConfig<
|
||||
KalshiGetExchangeStatusV2Params,
|
||||
KalshiGetExchangeStatusV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_exchange_status_v2',
|
||||
name: 'Get Exchange Status from Kalshi V2',
|
||||
description: 'Retrieve the current status of the Kalshi exchange (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {},
|
||||
|
||||
request: {
|
||||
url: () => {
|
||||
return buildKalshiUrl('/exchange/status')
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_exchange_status_v2')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
exchange_active: data.exchange_active ?? false,
|
||||
trading_active: data.trading_active ?? false,
|
||||
exchange_estimated_resume_time: data.exchange_estimated_resume_time ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
exchange_active: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the exchange is active',
|
||||
},
|
||||
trading_active: {
|
||||
type: 'boolean',
|
||||
description: 'Whether trading is active',
|
||||
},
|
||||
exchange_estimated_resume_time: {
|
||||
type: 'string',
|
||||
description: 'Estimated time when exchange will resume (if inactive)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import type {
|
||||
KalshiAuthParams,
|
||||
KalshiFill,
|
||||
KalshiPaginationParams,
|
||||
KalshiPagingInfo,
|
||||
} from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiAuthHeaders,
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_FILL_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetFillsParams extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
orderId?: string
|
||||
minTs?: number
|
||||
maxTs?: number
|
||||
}
|
||||
|
||||
export interface KalshiGetFillsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
fills: KalshiFill[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetFillsTool: ToolConfig<KalshiGetFillsParams, KalshiGetFillsResponse> = {
|
||||
id: 'kalshi_get_fills',
|
||||
name: 'Get Fills from Kalshi',
|
||||
description: "Retrieve your portfolio's fills/trades from Kalshi",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by order ID (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.orderId) queryParams.append('order_id', params.orderId)
|
||||
if (params.minTs !== undefined) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs !== undefined) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/fills')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/fills'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_fills')
|
||||
}
|
||||
|
||||
const fills = data.fills || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
fills,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
fills: {
|
||||
type: 'array',
|
||||
description: 'Array of fill/trade objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_FILL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Fills - fixes limit max to 200, adds subaccount
|
||||
*/
|
||||
export interface KalshiGetFillsV2Params extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
orderId?: string
|
||||
minTs?: number
|
||||
maxTs?: number
|
||||
subaccount?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetFillsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
fills: Array<{
|
||||
fill_id: string
|
||||
trade_id: string
|
||||
order_id: string
|
||||
client_order_id: string | null
|
||||
ticker: string
|
||||
market_ticker: string
|
||||
side: string
|
||||
action: string
|
||||
count: number
|
||||
count_fp: string | null
|
||||
price: number | null
|
||||
yes_price: number
|
||||
no_price: number
|
||||
yes_price_fixed: string | null
|
||||
no_price_fixed: string | null
|
||||
is_taker: boolean
|
||||
created_time: string | null
|
||||
ts: number | null
|
||||
}>
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetFillsV2Tool: ToolConfig<KalshiGetFillsV2Params, KalshiGetFillsV2Response> = {
|
||||
id: 'kalshi_get_fills_v2',
|
||||
name: 'Get Fills from Kalshi V2',
|
||||
description: "Retrieve your portfolio's fills/trades from Kalshi (V2 - exact API response)",
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by order ID (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
subaccount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Subaccount identifier to get fills for',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.orderId) queryParams.append('order_id', params.orderId)
|
||||
if (params.minTs !== undefined) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs !== undefined) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.subaccount) queryParams.append('subaccount', params.subaccount)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/fills')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/fills'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_fills_v2')
|
||||
}
|
||||
|
||||
const fills = (data.fills || []).map((f: Record<string, unknown>) => ({
|
||||
fill_id: f.fill_id ?? null,
|
||||
trade_id: f.trade_id ?? null,
|
||||
order_id: f.order_id ?? null,
|
||||
client_order_id: f.client_order_id ?? null,
|
||||
ticker: f.ticker ?? null,
|
||||
market_ticker: f.market_ticker ?? null,
|
||||
side: f.side ?? null,
|
||||
action: f.action ?? null,
|
||||
count: f.count ?? 0,
|
||||
count_fp: f.count_fp ?? null,
|
||||
price: f.price ?? null,
|
||||
yes_price: f.yes_price ?? 0,
|
||||
no_price: f.no_price ?? 0,
|
||||
yes_price_fixed: f.yes_price_fixed ?? null,
|
||||
no_price_fixed: f.no_price_fixed ?? null,
|
||||
is_taker: f.is_taker ?? false,
|
||||
created_time: f.created_time ?? null,
|
||||
ts: f.ts ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
fills,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
fills: {
|
||||
type: 'array',
|
||||
description: 'Array of fill/trade objects with all API fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_FILL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import type { KalshiMarket } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetMarketParams {
|
||||
ticker: string // Market ticker
|
||||
}
|
||||
|
||||
export interface KalshiGetMarketResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
market: KalshiMarket
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetMarketTool: ToolConfig<KalshiGetMarketParams, KalshiGetMarketResponse> = {
|
||||
id: 'kalshi_get_market',
|
||||
name: 'Get Market from Kalshi',
|
||||
description: 'Retrieve details of a specific prediction market by ticker',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/markets/${params.ticker.trim()}`),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_market')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
market: data.market,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
market: {
|
||||
type: 'object',
|
||||
description: 'Market object with details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Market Tool - Returns exact Kalshi API response structure
|
||||
*/
|
||||
export interface KalshiGetMarketV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
market: {
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
market_type: string
|
||||
title: string
|
||||
subtitle: string | null
|
||||
yes_sub_title: string | null
|
||||
no_sub_title: string | null
|
||||
open_time: string | null
|
||||
close_time: string | null
|
||||
expected_expiration_time: string | null
|
||||
expiration_time: string | null
|
||||
latest_expiration_time: string | null
|
||||
settlement_timer_seconds: number | null
|
||||
status: string
|
||||
response_price_units: string | null
|
||||
notional_value: number | null
|
||||
tick_size: number | null
|
||||
yes_bid: number | null
|
||||
yes_ask: number | null
|
||||
no_bid: number | null
|
||||
no_ask: number | null
|
||||
last_price: number | null
|
||||
previous_yes_bid: number | null
|
||||
previous_yes_ask: number | null
|
||||
previous_price: number | null
|
||||
volume: number | null
|
||||
volume_24h: number | null
|
||||
liquidity: number | null
|
||||
open_interest: number | null
|
||||
result: string | null
|
||||
cap_strike: number | null
|
||||
floor_strike: number | null
|
||||
can_close_early: boolean | null
|
||||
expiration_value: string | null
|
||||
category: string | null
|
||||
risk_limit_cents: number | null
|
||||
strike_type: string | null
|
||||
rules_primary: string | null
|
||||
rules_secondary: string | null
|
||||
settlement_source_url: string | null
|
||||
custom_strike: object | null
|
||||
underlying: string | null
|
||||
settlement_value: number | null
|
||||
cfd_contract_size: number | null
|
||||
yes_fee_fp: number | null
|
||||
no_fee_fp: number | null
|
||||
last_price_fp: number | null
|
||||
yes_bid_fp: number | null
|
||||
yes_ask_fp: number | null
|
||||
no_bid_fp: number | null
|
||||
no_ask_fp: number | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetMarketV2Tool: ToolConfig<KalshiGetMarketParams, KalshiGetMarketV2Response> = {
|
||||
id: 'kalshi_get_market_v2',
|
||||
name: 'Get Market from Kalshi V2',
|
||||
description:
|
||||
'Retrieve details of a specific prediction market by ticker (V2 - full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/markets/${params.ticker.trim()}`),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_market_v2')
|
||||
}
|
||||
|
||||
const m = data.market || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
market: {
|
||||
ticker: m.ticker ?? null,
|
||||
event_ticker: m.event_ticker ?? null,
|
||||
market_type: m.market_type ?? null,
|
||||
title: m.title ?? null,
|
||||
subtitle: m.subtitle ?? null,
|
||||
yes_sub_title: m.yes_sub_title ?? null,
|
||||
no_sub_title: m.no_sub_title ?? null,
|
||||
open_time: m.open_time ?? null,
|
||||
close_time: m.close_time ?? null,
|
||||
expected_expiration_time: m.expected_expiration_time ?? null,
|
||||
expiration_time: m.expiration_time ?? null,
|
||||
latest_expiration_time: m.latest_expiration_time ?? null,
|
||||
settlement_timer_seconds: m.settlement_timer_seconds ?? null,
|
||||
status: m.status ?? null,
|
||||
response_price_units: m.response_price_units ?? null,
|
||||
notional_value: m.notional_value ?? null,
|
||||
tick_size: m.tick_size ?? null,
|
||||
yes_bid: m.yes_bid ?? null,
|
||||
yes_ask: m.yes_ask ?? null,
|
||||
no_bid: m.no_bid ?? null,
|
||||
no_ask: m.no_ask ?? null,
|
||||
last_price: m.last_price ?? null,
|
||||
previous_yes_bid: m.previous_yes_bid ?? null,
|
||||
previous_yes_ask: m.previous_yes_ask ?? null,
|
||||
previous_price: m.previous_price ?? null,
|
||||
volume: m.volume ?? null,
|
||||
volume_24h: m.volume_24h ?? null,
|
||||
liquidity: m.liquidity ?? null,
|
||||
open_interest: m.open_interest ?? null,
|
||||
result: m.result ?? null,
|
||||
cap_strike: m.cap_strike ?? null,
|
||||
floor_strike: m.floor_strike ?? null,
|
||||
can_close_early: m.can_close_early ?? null,
|
||||
expiration_value: m.expiration_value ?? null,
|
||||
category: m.category ?? null,
|
||||
risk_limit_cents: m.risk_limit_cents ?? null,
|
||||
strike_type: m.strike_type ?? null,
|
||||
rules_primary: m.rules_primary ?? null,
|
||||
rules_secondary: m.rules_secondary ?? null,
|
||||
settlement_source_url: m.settlement_source_url ?? null,
|
||||
custom_strike: m.custom_strike ?? null,
|
||||
underlying: m.underlying ?? null,
|
||||
settlement_value: m.settlement_value ?? null,
|
||||
cfd_contract_size: m.cfd_contract_size ?? null,
|
||||
yes_fee_fp: m.yes_fee_fp ?? null,
|
||||
no_fee_fp: m.no_fee_fp ?? null,
|
||||
last_price_fp: m.last_price_fp ?? null,
|
||||
yes_bid_fp: m.yes_bid_fp ?? null,
|
||||
yes_ask_fp: m.yes_ask_fp ?? null,
|
||||
no_bid_fp: m.no_bid_fp ?? null,
|
||||
no_ask_fp: m.no_ask_fp ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
market: {
|
||||
type: 'object',
|
||||
description: 'Market object with all API fields',
|
||||
properties: {
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
market_type: { type: 'string', description: 'Market type' },
|
||||
title: { type: 'string', description: 'Market title' },
|
||||
subtitle: { type: 'string', description: 'Market subtitle' },
|
||||
yes_sub_title: { type: 'string', description: 'Yes outcome subtitle' },
|
||||
no_sub_title: { type: 'string', description: 'No outcome subtitle' },
|
||||
open_time: { type: 'string', description: 'Market open time' },
|
||||
close_time: { type: 'string', description: 'Market close time' },
|
||||
expected_expiration_time: { type: 'string', description: 'Expected expiration time' },
|
||||
expiration_time: { type: 'string', description: 'Expiration time' },
|
||||
latest_expiration_time: { type: 'string', description: 'Latest expiration time' },
|
||||
settlement_timer_seconds: { type: 'number', description: 'Settlement timer in seconds' },
|
||||
status: { type: 'string', description: 'Market status' },
|
||||
response_price_units: { type: 'string', description: 'Response price units' },
|
||||
notional_value: { type: 'number', description: 'Notional value' },
|
||||
tick_size: { type: 'number', description: 'Tick size' },
|
||||
yes_bid: { type: 'number', description: 'Current yes bid price' },
|
||||
yes_ask: { type: 'number', description: 'Current yes ask price' },
|
||||
no_bid: { type: 'number', description: 'Current no bid price' },
|
||||
no_ask: { type: 'number', description: 'Current no ask price' },
|
||||
last_price: { type: 'number', description: 'Last trade price' },
|
||||
previous_yes_bid: { type: 'number', description: 'Previous yes bid' },
|
||||
previous_yes_ask: { type: 'number', description: 'Previous yes ask' },
|
||||
previous_price: { type: 'number', description: 'Previous price' },
|
||||
volume: { type: 'number', description: 'Total volume' },
|
||||
volume_24h: { type: 'number', description: '24-hour volume' },
|
||||
liquidity: { type: 'number', description: 'Market liquidity' },
|
||||
open_interest: { type: 'number', description: 'Open interest' },
|
||||
result: { type: 'string', description: 'Market result' },
|
||||
cap_strike: { type: 'number', description: 'Cap strike' },
|
||||
floor_strike: { type: 'number', description: 'Floor strike' },
|
||||
can_close_early: { type: 'boolean', description: 'Can close early' },
|
||||
expiration_value: { type: 'string', description: 'Expiration value' },
|
||||
category: { type: 'string', description: 'Market category' },
|
||||
risk_limit_cents: { type: 'number', description: 'Risk limit in cents' },
|
||||
strike_type: { type: 'string', description: 'Strike type' },
|
||||
rules_primary: { type: 'string', description: 'Primary rules' },
|
||||
rules_secondary: { type: 'string', description: 'Secondary rules' },
|
||||
settlement_source_url: { type: 'string', description: 'Settlement source URL' },
|
||||
custom_strike: { type: 'object', description: 'Custom strike object' },
|
||||
underlying: { type: 'string', description: 'Underlying asset' },
|
||||
settlement_value: { type: 'number', description: 'Settlement value' },
|
||||
cfd_contract_size: { type: 'number', description: 'CFD contract size' },
|
||||
yes_fee_fp: { type: 'number', description: 'Yes fee (fixed-point)' },
|
||||
no_fee_fp: { type: 'number', description: 'No fee (fixed-point)' },
|
||||
last_price_fp: { type: 'number', description: 'Last price (fixed-point)' },
|
||||
yes_bid_fp: { type: 'number', description: 'Yes bid (fixed-point)' },
|
||||
yes_ask_fp: { type: 'number', description: 'Yes ask (fixed-point)' },
|
||||
no_bid_fp: { type: 'number', description: 'No bid (fixed-point)' },
|
||||
no_ask_fp: { type: 'number', description: 'No ask (fixed-point)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import type { KalshiMarket, KalshiPaginationParams, KalshiPagingInfo } from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_MARKET_OUTPUT_PROPERTIES,
|
||||
KALSHI_PAGING_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetMarketsParams extends KalshiPaginationParams {
|
||||
status?: string // unopened, open, closed, settled
|
||||
seriesTicker?: string
|
||||
eventTicker?: string
|
||||
}
|
||||
|
||||
export interface KalshiGetMarketsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
markets: KalshiMarket[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetMarketsTool: ToolConfig<KalshiGetMarketsParams, KalshiGetMarketsResponse> = {
|
||||
id: 'kalshi_get_markets',
|
||||
name: 'Get Markets from Kalshi',
|
||||
description: 'Retrieve a list of prediction markets from Kalshi with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market status: "unopened", "open", "closed", or "settled"',
|
||||
},
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by series ticker (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event ticker (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.seriesTicker) queryParams.append('series_ticker', params.seriesTicker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/markets')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_markets')
|
||||
}
|
||||
|
||||
const markets = data.markets || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
markets,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
markets: {
|
||||
type: 'array',
|
||||
description: 'Array of market objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_MARKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
properties: KALSHI_PAGING_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Markets Tool - Returns exact Kalshi API response structure with all params
|
||||
*/
|
||||
export interface KalshiGetMarketsV2Params extends KalshiPaginationParams {
|
||||
status?: string // unopened, open, closed, settled
|
||||
seriesTicker?: string
|
||||
eventTicker?: string
|
||||
minCreatedTs?: number
|
||||
maxCreatedTs?: number
|
||||
minUpdatedTs?: number
|
||||
minCloseTs?: number
|
||||
maxCloseTs?: number
|
||||
minSettledTs?: number
|
||||
maxSettledTs?: number
|
||||
tickers?: string // comma-separated list
|
||||
mveFilter?: string // only or exclude
|
||||
}
|
||||
|
||||
export interface KalshiGetMarketsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
markets: Array<{
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
market_type: string
|
||||
title: string
|
||||
subtitle: string | null
|
||||
yes_sub_title: string | null
|
||||
no_sub_title: string | null
|
||||
open_time: string | null
|
||||
close_time: string | null
|
||||
expected_expiration_time: string | null
|
||||
expiration_time: string | null
|
||||
latest_expiration_time: string | null
|
||||
settlement_timer_seconds: number | null
|
||||
status: string
|
||||
response_price_units: string | null
|
||||
notional_value: number | null
|
||||
tick_size: number | null
|
||||
yes_bid: number | null
|
||||
yes_ask: number | null
|
||||
no_bid: number | null
|
||||
no_ask: number | null
|
||||
last_price: number | null
|
||||
previous_yes_bid: number | null
|
||||
previous_yes_ask: number | null
|
||||
previous_price: number | null
|
||||
volume: number | null
|
||||
volume_24h: number | null
|
||||
liquidity: number | null
|
||||
open_interest: number | null
|
||||
result: string | null
|
||||
cap_strike: number | null
|
||||
floor_strike: number | null
|
||||
can_close_early: boolean | null
|
||||
expiration_value: string | null
|
||||
category: string | null
|
||||
risk_limit_cents: number | null
|
||||
strike_type: string | null
|
||||
rules_primary: string | null
|
||||
rules_secondary: string | null
|
||||
settlement_source_url: string | null
|
||||
custom_strike: object | null
|
||||
underlying: string | null
|
||||
settlement_value: number | null
|
||||
cfd_contract_size: number | null
|
||||
yes_fee_fp: number | null
|
||||
no_fee_fp: number | null
|
||||
last_price_fp: number | null
|
||||
yes_bid_fp: number | null
|
||||
yes_ask_fp: number | null
|
||||
no_bid_fp: number | null
|
||||
no_ask_fp: number | null
|
||||
}>
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetMarketsV2Tool: ToolConfig<
|
||||
KalshiGetMarketsV2Params,
|
||||
KalshiGetMarketsV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_markets_v2',
|
||||
name: 'Get Markets from Kalshi V2',
|
||||
description:
|
||||
'Retrieve a list of prediction markets from Kalshi with all filtering options (V2 - full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market status: "unopened", "open", "closed", or "settled"',
|
||||
},
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by series ticker (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event ticker (e.g., "KXBTC-24DEC31", "INX-25JAN03")',
|
||||
},
|
||||
minCreatedTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum created timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxCreatedTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum created timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
minUpdatedTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum updated timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
minCloseTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum close timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxCloseTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum close timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
minSettledTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum settled timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxSettledTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum settled timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
tickers: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tickers (e.g., "KXBTC-24DEC31,INX-25JAN03")',
|
||||
},
|
||||
mveFilter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Multivariate event filter: "only" or "exclude"',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.seriesTicker) queryParams.append('series_ticker', params.seriesTicker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.minCreatedTs) queryParams.append('min_created_ts', params.minCreatedTs.toString())
|
||||
if (params.maxCreatedTs) queryParams.append('max_created_ts', params.maxCreatedTs.toString())
|
||||
if (params.minUpdatedTs) queryParams.append('min_updated_ts', params.minUpdatedTs.toString())
|
||||
if (params.minCloseTs) queryParams.append('min_close_ts', params.minCloseTs.toString())
|
||||
if (params.maxCloseTs) queryParams.append('max_close_ts', params.maxCloseTs.toString())
|
||||
if (params.minSettledTs) queryParams.append('min_settled_ts', params.minSettledTs.toString())
|
||||
if (params.maxSettledTs) queryParams.append('max_settled_ts', params.maxSettledTs.toString())
|
||||
if (params.tickers) queryParams.append('tickers', params.tickers)
|
||||
if (params.mveFilter) queryParams.append('mve_filter', params.mveFilter)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/markets')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_markets_v2')
|
||||
}
|
||||
|
||||
const markets = (data.markets || []).map((m: Record<string, unknown>) => ({
|
||||
ticker: m.ticker ?? null,
|
||||
event_ticker: m.event_ticker ?? null,
|
||||
market_type: m.market_type ?? null,
|
||||
title: m.title ?? null,
|
||||
subtitle: m.subtitle ?? null,
|
||||
yes_sub_title: m.yes_sub_title ?? null,
|
||||
no_sub_title: m.no_sub_title ?? null,
|
||||
open_time: m.open_time ?? null,
|
||||
close_time: m.close_time ?? null,
|
||||
expected_expiration_time: m.expected_expiration_time ?? null,
|
||||
expiration_time: m.expiration_time ?? null,
|
||||
latest_expiration_time: m.latest_expiration_time ?? null,
|
||||
settlement_timer_seconds: m.settlement_timer_seconds ?? null,
|
||||
status: m.status ?? null,
|
||||
response_price_units: m.response_price_units ?? null,
|
||||
notional_value: m.notional_value ?? null,
|
||||
tick_size: m.tick_size ?? null,
|
||||
yes_bid: m.yes_bid ?? null,
|
||||
yes_ask: m.yes_ask ?? null,
|
||||
no_bid: m.no_bid ?? null,
|
||||
no_ask: m.no_ask ?? null,
|
||||
last_price: m.last_price ?? null,
|
||||
previous_yes_bid: m.previous_yes_bid ?? null,
|
||||
previous_yes_ask: m.previous_yes_ask ?? null,
|
||||
previous_price: m.previous_price ?? null,
|
||||
volume: m.volume ?? null,
|
||||
volume_24h: m.volume_24h ?? null,
|
||||
liquidity: m.liquidity ?? null,
|
||||
open_interest: m.open_interest ?? null,
|
||||
result: m.result ?? null,
|
||||
cap_strike: m.cap_strike ?? null,
|
||||
floor_strike: m.floor_strike ?? null,
|
||||
can_close_early: m.can_close_early ?? null,
|
||||
expiration_value: m.expiration_value ?? null,
|
||||
category: m.category ?? null,
|
||||
risk_limit_cents: m.risk_limit_cents ?? null,
|
||||
strike_type: m.strike_type ?? null,
|
||||
rules_primary: m.rules_primary ?? null,
|
||||
rules_secondary: m.rules_secondary ?? null,
|
||||
settlement_source_url: m.settlement_source_url ?? null,
|
||||
custom_strike: m.custom_strike ?? null,
|
||||
underlying: m.underlying ?? null,
|
||||
settlement_value: m.settlement_value ?? null,
|
||||
cfd_contract_size: m.cfd_contract_size ?? null,
|
||||
yes_fee_fp: m.yes_fee_fp ?? null,
|
||||
no_fee_fp: m.no_fee_fp ?? null,
|
||||
last_price_fp: m.last_price_fp ?? null,
|
||||
yes_bid_fp: m.yes_bid_fp ?? null,
|
||||
yes_ask_fp: m.yes_ask_fp ?? null,
|
||||
no_bid_fp: m.no_bid_fp ?? null,
|
||||
no_ask_fp: m.no_ask_fp ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
markets,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
markets: {
|
||||
type: 'array',
|
||||
description: 'Array of market objects with all API fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_MARKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
import type { KalshiAuthParams, KalshiOrder } from '@/tools/kalshi/types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to retrieve (required)
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrderTool: ToolConfig<KalshiGetOrderParams, KalshiGetOrderResponse> = {
|
||||
id: 'kalshi_get_order',
|
||||
name: 'Get Order from Kalshi',
|
||||
description: 'Retrieve details of a specific order by ID from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to retrieve (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'Order object with details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderV2Params extends KalshiAuthParams {
|
||||
orderId: string // Order ID to retrieve (required)
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
order: {
|
||||
order_id: string
|
||||
user_id: string | null
|
||||
client_order_id: string | null
|
||||
ticker: string
|
||||
side: string
|
||||
action: string
|
||||
type: string
|
||||
status: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
yes_price_dollars: string | null
|
||||
no_price_dollars: string | null
|
||||
fill_count: number | null
|
||||
fill_count_fp: string | null
|
||||
remaining_count: number | null
|
||||
remaining_count_fp: string | null
|
||||
initial_count: number | null
|
||||
initial_count_fp: string | null
|
||||
taker_fees: number | null
|
||||
maker_fees: number | null
|
||||
taker_fees_dollars: string | null
|
||||
maker_fees_dollars: string | null
|
||||
taker_fill_cost: number | null
|
||||
maker_fill_cost: number | null
|
||||
taker_fill_cost_dollars: string | null
|
||||
maker_fill_cost_dollars: string | null
|
||||
queue_position: number | null
|
||||
expiration_time: string | null
|
||||
created_time: string | null
|
||||
last_update_time: string | null
|
||||
self_trade_prevention_type: string | null
|
||||
order_group_id: string | null
|
||||
cancel_order_on_pause: boolean | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrderV2Tool: ToolConfig<KalshiGetOrderV2Params, KalshiGetOrderV2Response> = {
|
||||
id: 'kalshi_get_order_v2',
|
||||
name: 'Get Order from Kalshi V2',
|
||||
description: 'Retrieve details of a specific order by ID from Kalshi (V2 with full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Order ID to retrieve (e.g., "abc123-def456-ghi789")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId.trim()}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId.trim()}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_order_v2')
|
||||
}
|
||||
|
||||
const order = data.order || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: {
|
||||
order_id: order.order_id ?? null,
|
||||
user_id: order.user_id ?? null,
|
||||
client_order_id: order.client_order_id ?? null,
|
||||
ticker: order.ticker ?? null,
|
||||
side: order.side ?? null,
|
||||
action: order.action ?? null,
|
||||
type: order.type ?? null,
|
||||
status: order.status ?? null,
|
||||
yes_price: order.yes_price ?? null,
|
||||
no_price: order.no_price ?? null,
|
||||
yes_price_dollars: order.yes_price_dollars ?? null,
|
||||
no_price_dollars: order.no_price_dollars ?? null,
|
||||
fill_count: order.fill_count ?? null,
|
||||
fill_count_fp: order.fill_count_fp ?? null,
|
||||
remaining_count: order.remaining_count ?? null,
|
||||
remaining_count_fp: order.remaining_count_fp ?? null,
|
||||
initial_count: order.initial_count ?? null,
|
||||
initial_count_fp: order.initial_count_fp ?? null,
|
||||
taker_fees: order.taker_fees ?? null,
|
||||
maker_fees: order.maker_fees ?? null,
|
||||
taker_fees_dollars: order.taker_fees_dollars ?? null,
|
||||
maker_fees_dollars: order.maker_fees_dollars ?? null,
|
||||
taker_fill_cost: order.taker_fill_cost ?? null,
|
||||
maker_fill_cost: order.maker_fill_cost ?? null,
|
||||
taker_fill_cost_dollars: order.taker_fill_cost_dollars ?? null,
|
||||
maker_fill_cost_dollars: order.maker_fill_cost_dollars ?? null,
|
||||
queue_position: order.queue_position ?? null,
|
||||
expiration_time: order.expiration_time ?? null,
|
||||
created_time: order.created_time ?? null,
|
||||
last_update_time: order.last_update_time ?? null,
|
||||
self_trade_prevention_type: order.self_trade_prevention_type ?? null,
|
||||
order_group_id: order.order_group_id ?? null,
|
||||
cancel_order_on_pause: order.cancel_order_on_pause ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
order: {
|
||||
type: 'object',
|
||||
description: 'Order object with full API response fields',
|
||||
properties: {
|
||||
order_id: { type: 'string', description: 'Order ID' },
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
client_order_id: { type: 'string', description: 'Client order ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
action: { type: 'string', description: 'Action (buy/sell)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
status: { type: 'string', description: 'Order status (resting/canceled/executed)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
yes_price_dollars: { type: 'string', description: 'Yes price in dollars' },
|
||||
no_price_dollars: { type: 'string', description: 'No price in dollars' },
|
||||
fill_count: { type: 'number', description: 'Filled contract count' },
|
||||
fill_count_fp: { type: 'string', description: 'Filled count (fixed-point)' },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts' },
|
||||
remaining_count_fp: { type: 'string', description: 'Remaining count (fixed-point)' },
|
||||
initial_count: { type: 'number', description: 'Initial contract count' },
|
||||
initial_count_fp: { type: 'string', description: 'Initial count (fixed-point)' },
|
||||
taker_fees: { type: 'number', description: 'Taker fees in cents' },
|
||||
maker_fees: { type: 'number', description: 'Maker fees in cents' },
|
||||
taker_fees_dollars: { type: 'string', description: 'Taker fees in dollars' },
|
||||
maker_fees_dollars: { type: 'string', description: 'Maker fees in dollars' },
|
||||
taker_fill_cost: { type: 'number', description: 'Taker fill cost in cents' },
|
||||
maker_fill_cost: { type: 'number', description: 'Maker fill cost in cents' },
|
||||
taker_fill_cost_dollars: { type: 'string', description: 'Taker fill cost in dollars' },
|
||||
maker_fill_cost_dollars: { type: 'string', description: 'Maker fill cost in dollars' },
|
||||
queue_position: { type: 'number', description: 'Queue position (deprecated)' },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time' },
|
||||
created_time: { type: 'string', description: 'Order creation time' },
|
||||
last_update_time: { type: 'string', description: 'Last update time' },
|
||||
self_trade_prevention_type: { type: 'string', description: 'Self-trade prevention type' },
|
||||
order_group_id: { type: 'string', description: 'Order group ID' },
|
||||
cancel_order_on_pause: { type: 'boolean', description: 'Cancel on market pause' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import type { KalshiOrderbook } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetOrderbookParams {
|
||||
ticker: string
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderbookResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
orderbook: KalshiOrderbook
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrderbookTool: ToolConfig<
|
||||
KalshiGetOrderbookParams,
|
||||
KalshiGetOrderbookResponse
|
||||
> = {
|
||||
id: 'kalshi_get_orderbook',
|
||||
name: 'Get Market Orderbook from Kalshi',
|
||||
description: 'Retrieve the orderbook (yes and no bids) for a specific market',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/markets/${params.ticker.trim()}/orderbook`),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_orderbook')
|
||||
}
|
||||
|
||||
const orderbook = data.orderbook || { yes: [], no: [] }
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
orderbook,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
orderbook: {
|
||||
type: 'object',
|
||||
description: 'Orderbook with yes/no bids and asks',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Orderbook Tool - Returns exact Kalshi API response structure with depth param
|
||||
* API returns tuple arrays: [price, count] for orderbook, [dollars_string, count] for _dollars variants
|
||||
*/
|
||||
export interface KalshiGetOrderbookV2Params {
|
||||
ticker: string
|
||||
depth?: number // Number of price levels to return
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderbookV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
orderbook: {
|
||||
yes: Array<[number, number]> // [price_in_cents, count]
|
||||
no: Array<[number, number]> // [price_in_cents, count]
|
||||
yes_dollars: Array<[string, number]> // [dollars_string, count]
|
||||
no_dollars: Array<[string, number]> // [dollars_string, count]
|
||||
}
|
||||
orderbook_fp: {
|
||||
yes_dollars: Array<[string, string]> // [dollars_string, fp_count_string]
|
||||
no_dollars: Array<[string, string]> // [dollars_string, fp_count_string]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrderbookV2Tool: ToolConfig<
|
||||
KalshiGetOrderbookV2Params,
|
||||
KalshiGetOrderbookV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_orderbook_v2',
|
||||
name: 'Get Market Orderbook from Kalshi V2',
|
||||
description:
|
||||
'Retrieve the orderbook (yes and no bids) for a specific market (V2 - includes depth and fp fields)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Market ticker identifier (e.g., "KXBTC-24DEC31", "INX-25JAN03-T4485.99")',
|
||||
},
|
||||
depth: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of price levels to return (e.g., 10, 20). Default: all levels',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.depth) queryParams.append('depth', params.depth.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(`/markets/${params.ticker.trim()}/orderbook`)
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_orderbook_v2')
|
||||
}
|
||||
|
||||
const orderbook = data.orderbook || {}
|
||||
const orderbookFp = data.orderbook_fp || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
orderbook: {
|
||||
yes: orderbook.yes ?? [],
|
||||
no: orderbook.no ?? [],
|
||||
yes_dollars: orderbook.yes_dollars ?? [],
|
||||
no_dollars: orderbook.no_dollars ?? [],
|
||||
},
|
||||
orderbook_fp: {
|
||||
yes_dollars: orderbookFp.yes_dollars ?? [],
|
||||
no_dollars: orderbookFp.no_dollars ?? [],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
orderbook: {
|
||||
type: 'object',
|
||||
description: 'Orderbook with yes/no bids (legacy integer counts)',
|
||||
properties: {
|
||||
yes: {
|
||||
type: 'array',
|
||||
description: 'Yes side bids as tuples [price_cents, count]',
|
||||
},
|
||||
no: {
|
||||
type: 'array',
|
||||
description: 'No side bids as tuples [price_cents, count]',
|
||||
},
|
||||
yes_dollars: {
|
||||
type: 'array',
|
||||
description: 'Yes side bids as tuples [dollars_string, count]',
|
||||
},
|
||||
no_dollars: {
|
||||
type: 'array',
|
||||
description: 'No side bids as tuples [dollars_string, count]',
|
||||
},
|
||||
},
|
||||
},
|
||||
orderbook_fp: {
|
||||
type: 'object',
|
||||
description: 'Orderbook with fixed-point counts (preferred)',
|
||||
properties: {
|
||||
yes_dollars: {
|
||||
type: 'array',
|
||||
description: 'Yes side bids as tuples [dollars_string, fp_count_string]',
|
||||
},
|
||||
no_dollars: {
|
||||
type: 'array',
|
||||
description: 'No side bids as tuples [dollars_string, fp_count_string]',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import type {
|
||||
KalshiAuthParams,
|
||||
KalshiOrder,
|
||||
KalshiPaginationParams,
|
||||
KalshiPagingInfo,
|
||||
} from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiAuthHeaders,
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_ORDER_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetOrdersParams extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
status?: string // resting, canceled, executed
|
||||
}
|
||||
|
||||
export interface KalshiGetOrdersResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
orders: KalshiOrder[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrdersTool: ToolConfig<KalshiGetOrdersParams, KalshiGetOrdersResponse> = {
|
||||
id: 'kalshi_get_orders',
|
||||
name: 'Get Orders from Kalshi',
|
||||
description: 'Retrieve your orders from Kalshi with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by event ticker, max 10 comma-separated (e.g., "KXBTC-24DEC31,INX-25JAN03")',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by order status: "resting", "canceled", or "executed"',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/orders')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/orders'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_orders')
|
||||
}
|
||||
|
||||
const orders = data.orders || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
orders,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
orders: {
|
||||
type: 'array',
|
||||
description: 'Array of order objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_ORDER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export interface KalshiGetOrdersV2Params extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
status?: string // resting, canceled, executed
|
||||
minTs?: string // Minimum timestamp filter (Unix timestamp)
|
||||
maxTs?: string // Maximum timestamp filter (Unix timestamp)
|
||||
subaccount?: string // Subaccount to filter orders
|
||||
}
|
||||
|
||||
interface KalshiOrderV2 {
|
||||
order_id: string
|
||||
user_id: string | null
|
||||
client_order_id: string | null
|
||||
ticker: string
|
||||
side: string
|
||||
action: string
|
||||
type: string
|
||||
status: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
yes_price_dollars: string | null
|
||||
no_price_dollars: string | null
|
||||
fill_count: number | null
|
||||
fill_count_fp: string | null
|
||||
remaining_count: number | null
|
||||
remaining_count_fp: string | null
|
||||
initial_count: number | null
|
||||
initial_count_fp: string | null
|
||||
taker_fees: number | null
|
||||
maker_fees: number | null
|
||||
taker_fees_dollars: string | null
|
||||
maker_fees_dollars: string | null
|
||||
taker_fill_cost: number | null
|
||||
maker_fill_cost: number | null
|
||||
taker_fill_cost_dollars: string | null
|
||||
maker_fill_cost_dollars: string | null
|
||||
queue_position: number | null
|
||||
expiration_time: string | null
|
||||
created_time: string | null
|
||||
last_update_time: string | null
|
||||
self_trade_prevention_type: string | null
|
||||
order_group_id: string | null
|
||||
cancel_order_on_pause: boolean | null
|
||||
}
|
||||
|
||||
export interface KalshiGetOrdersV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
orders: KalshiOrderV2[]
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrdersV2Tool: ToolConfig<KalshiGetOrdersV2Params, KalshiGetOrdersV2Response> =
|
||||
{
|
||||
id: 'kalshi_get_orders_v2',
|
||||
name: 'Get Orders from Kalshi V2',
|
||||
description:
|
||||
'Retrieve your orders from Kalshi with optional filtering (V2 with full API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by event ticker, max 10 comma-separated (e.g., "KXBTC-24DEC31,INX-25JAN03")',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by order status: "resting", "canceled", or "executed"',
|
||||
},
|
||||
minTs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum timestamp filter (Unix timestamp, e.g., "1704067200")',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum timestamp filter (Unix timestamp, e.g., "1704153600")',
|
||||
},
|
||||
subaccount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Subaccount identifier to filter orders',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.minTs) queryParams.append('min_ts', params.minTs)
|
||||
if (params.maxTs) queryParams.append('max_ts', params.maxTs)
|
||||
if (params.subaccount) queryParams.append('subaccount', params.subaccount)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/orders')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/orders'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_orders_v2')
|
||||
}
|
||||
|
||||
const rawOrders = data.orders || []
|
||||
const orders: KalshiOrderV2[] = rawOrders.map((order: any) => ({
|
||||
order_id: order.order_id ?? null,
|
||||
user_id: order.user_id ?? null,
|
||||
client_order_id: order.client_order_id ?? null,
|
||||
ticker: order.ticker ?? null,
|
||||
side: order.side ?? null,
|
||||
action: order.action ?? null,
|
||||
type: order.type ?? null,
|
||||
status: order.status ?? null,
|
||||
yes_price: order.yes_price ?? null,
|
||||
no_price: order.no_price ?? null,
|
||||
yes_price_dollars: order.yes_price_dollars ?? null,
|
||||
no_price_dollars: order.no_price_dollars ?? null,
|
||||
fill_count: order.fill_count ?? null,
|
||||
fill_count_fp: order.fill_count_fp ?? null,
|
||||
remaining_count: order.remaining_count ?? null,
|
||||
remaining_count_fp: order.remaining_count_fp ?? null,
|
||||
initial_count: order.initial_count ?? null,
|
||||
initial_count_fp: order.initial_count_fp ?? null,
|
||||
taker_fees: order.taker_fees ?? null,
|
||||
maker_fees: order.maker_fees ?? null,
|
||||
taker_fees_dollars: order.taker_fees_dollars ?? null,
|
||||
maker_fees_dollars: order.maker_fees_dollars ?? null,
|
||||
taker_fill_cost: order.taker_fill_cost ?? null,
|
||||
maker_fill_cost: order.maker_fill_cost ?? null,
|
||||
taker_fill_cost_dollars: order.taker_fill_cost_dollars ?? null,
|
||||
maker_fill_cost_dollars: order.maker_fill_cost_dollars ?? null,
|
||||
queue_position: order.queue_position ?? null,
|
||||
expiration_time: order.expiration_time ?? null,
|
||||
created_time: order.created_time ?? null,
|
||||
last_update_time: order.last_update_time ?? null,
|
||||
self_trade_prevention_type: order.self_trade_prevention_type ?? null,
|
||||
order_group_id: order.order_group_id ?? null,
|
||||
cancel_order_on_pause: order.cancel_order_on_pause ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
orders,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
orders: {
|
||||
type: 'array',
|
||||
description: 'Array of order objects with full API response fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_ORDER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import type {
|
||||
KalshiAuthParams,
|
||||
KalshiPaginationParams,
|
||||
KalshiPagingInfo,
|
||||
KalshiPosition,
|
||||
} from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiAuthHeaders,
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES,
|
||||
KALSHI_POSITION_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetPositionsParams extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
}
|
||||
|
||||
export interface KalshiGetPositionsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
positions: KalshiPosition[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetPositionsTool: ToolConfig<
|
||||
KalshiGetPositionsParams,
|
||||
KalshiGetPositionsResponse
|
||||
> = {
|
||||
id: 'kalshi_get_positions',
|
||||
name: 'Get Positions from Kalshi',
|
||||
description: 'Retrieve your open positions from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by event ticker, max 10 comma-separated (e.g., "KXBTC-24DEC31,INX-25JAN03")',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/positions')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/positions'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_positions')
|
||||
}
|
||||
|
||||
const positions = data.market_positions || data.positions || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
positions,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
positions: {
|
||||
type: 'array',
|
||||
description: 'Array of position objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_POSITION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Positions - removes invalid settlementStatus, adds countFilter and subaccount
|
||||
*/
|
||||
export interface KalshiGetPositionsV2Params extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
countFilter?: string
|
||||
subaccount?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetPositionsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
market_positions: Array<{
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
event_title: string | null
|
||||
market_title: string | null
|
||||
position: number
|
||||
market_exposure: number | null
|
||||
realized_pnl: number | null
|
||||
total_traded: number | null
|
||||
resting_orders_count: number | null
|
||||
fees_paid: number | null
|
||||
}>
|
||||
event_positions: Array<{
|
||||
event_ticker: string
|
||||
event_exposure: number
|
||||
realized_pnl: number | null
|
||||
total_cost: number | null
|
||||
}> | null
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetPositionsV2Tool: ToolConfig<
|
||||
KalshiGetPositionsV2Params,
|
||||
KalshiGetPositionsV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_positions_v2',
|
||||
name: 'Get Positions from Kalshi V2',
|
||||
description: 'Retrieve your open positions from Kalshi (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by event ticker, max 10 comma-separated (e.g., "KXBTC-24DEC31,INX-25JAN03")',
|
||||
},
|
||||
countFilter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Restrict to positions with non-zero values for the given fields (comma-separated): "position", "total_traded"',
|
||||
},
|
||||
subaccount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Subaccount identifier to get positions for',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.countFilter) queryParams.append('count_filter', params.countFilter)
|
||||
if (params.subaccount) queryParams.append('subaccount', params.subaccount)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/positions')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/positions'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_positions_v2')
|
||||
}
|
||||
|
||||
const marketPositions = (data.market_positions || []).map((p: Record<string, unknown>) => ({
|
||||
ticker: p.ticker ?? null,
|
||||
event_ticker: p.event_ticker ?? null,
|
||||
event_title: p.event_title ?? null,
|
||||
market_title: p.market_title ?? null,
|
||||
position: p.position ?? 0,
|
||||
market_exposure: p.market_exposure ?? null,
|
||||
realized_pnl: p.realized_pnl ?? null,
|
||||
total_traded: p.total_traded ?? null,
|
||||
resting_orders_count: p.resting_orders_count ?? null,
|
||||
fees_paid: p.fees_paid ?? null,
|
||||
}))
|
||||
|
||||
const eventPositions = data.event_positions
|
||||
? (data.event_positions as Array<Record<string, unknown>>).map((p) => ({
|
||||
event_ticker: (p.event_ticker as string) ?? '',
|
||||
event_exposure: (p.event_exposure as number) ?? 0,
|
||||
realized_pnl: (p.realized_pnl as number | null) ?? null,
|
||||
total_cost: (p.total_cost as number | null) ?? null,
|
||||
}))
|
||||
: null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
market_positions: marketPositions,
|
||||
event_positions: eventPositions,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
market_positions: {
|
||||
type: 'array',
|
||||
description: 'Array of market position objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_POSITION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
event_positions: {
|
||||
type: 'array',
|
||||
description: 'Array of event position objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import type { KalshiSeries } from '@/tools/kalshi/types'
|
||||
import { buildKalshiUrl, handleKalshiError } from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetSeriesByTickerParams {
|
||||
seriesTicker: string
|
||||
}
|
||||
|
||||
export interface KalshiGetSeriesByTickerResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
series: KalshiSeries
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSeriesByTickerTool: ToolConfig<
|
||||
KalshiGetSeriesByTickerParams,
|
||||
KalshiGetSeriesByTickerResponse
|
||||
> = {
|
||||
id: 'kalshi_get_series_by_ticker',
|
||||
name: 'Get Series by Ticker from Kalshi',
|
||||
description: 'Retrieve details of a specific market series by ticker',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
return buildKalshiUrl(`/series/${params.seriesTicker.trim()}`)
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_series_by_ticker')
|
||||
}
|
||||
|
||||
const series = data.series || data
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'object',
|
||||
description: 'Series object with details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Series by Ticker
|
||||
*/
|
||||
export interface KalshiGetSeriesByTickerV2Params {
|
||||
seriesTicker: string
|
||||
includeVolume?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetSeriesByTickerV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
series: {
|
||||
ticker: string
|
||||
title: string
|
||||
frequency: string
|
||||
category: string
|
||||
tags: string[] | null
|
||||
settlement_sources: Array<{
|
||||
name: string
|
||||
url: string
|
||||
}> | null
|
||||
contract_url: string | null
|
||||
contract_terms_url: string | null
|
||||
fee_type: string | null
|
||||
fee_multiplier: number | null
|
||||
additional_prohibitions: string[] | null
|
||||
product_metadata: Record<string, unknown> | null
|
||||
volume: number | null
|
||||
volume_fp: number | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSeriesByTickerV2Tool: ToolConfig<
|
||||
KalshiGetSeriesByTickerV2Params,
|
||||
KalshiGetSeriesByTickerV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_series_by_ticker_v2',
|
||||
name: 'Get Series by Ticker from Kalshi V2',
|
||||
description: 'Retrieve details of a specific market series by ticker (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
seriesTicker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Series ticker identifier (e.g., "KXBTC", "INX", "FED-RATE")',
|
||||
},
|
||||
includeVolume: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include volume data in response (true/false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.includeVolume) queryParams.append('include_volume', params.includeVolume)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(`/series/${params.seriesTicker.trim()}`)
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_series_by_ticker_v2')
|
||||
}
|
||||
|
||||
const series = data.series || data
|
||||
|
||||
const settlementSources = series.settlement_sources
|
||||
? (series.settlement_sources as Array<Record<string, unknown>>).map((s) => ({
|
||||
name: (s.name as string) ?? null,
|
||||
url: (s.url as string) ?? null,
|
||||
}))
|
||||
: null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: {
|
||||
ticker: series.ticker ?? null,
|
||||
title: series.title ?? null,
|
||||
frequency: series.frequency ?? null,
|
||||
category: series.category ?? null,
|
||||
tags: series.tags ?? null,
|
||||
settlement_sources: settlementSources,
|
||||
contract_url: series.contract_url ?? null,
|
||||
contract_terms_url: series.contract_terms_url ?? null,
|
||||
fee_type: series.fee_type ?? null,
|
||||
fee_multiplier: series.fee_multiplier ?? null,
|
||||
additional_prohibitions: series.additional_prohibitions ?? null,
|
||||
product_metadata: series.product_metadata ?? null,
|
||||
volume: series.volume ?? null,
|
||||
volume_fp: series.volume_fp ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'object',
|
||||
description: 'Series object with full details matching Kalshi API response',
|
||||
properties: {
|
||||
ticker: { type: 'string', description: 'Series ticker' },
|
||||
title: { type: 'string', description: 'Series title' },
|
||||
frequency: { type: 'string', description: 'Event frequency' },
|
||||
category: { type: 'string', description: 'Series category' },
|
||||
tags: { type: 'array', description: 'Series tags' },
|
||||
settlement_sources: { type: 'array', description: 'Settlement sources' },
|
||||
contract_url: { type: 'string', description: 'Contract URL' },
|
||||
contract_terms_url: { type: 'string', description: 'Contract terms URL' },
|
||||
fee_type: { type: 'string', description: 'Fee type' },
|
||||
fee_multiplier: { type: 'number', description: 'Fee multiplier' },
|
||||
additional_prohibitions: { type: 'array', description: 'Additional prohibitions' },
|
||||
product_metadata: { type: 'object', description: 'Product metadata' },
|
||||
volume: { type: 'number', description: 'Series volume' },
|
||||
volume_fp: { type: 'number', description: 'Volume (fixed-point)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import type { KalshiSeries } from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_SERIES_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetSeriesListParams {
|
||||
category?: string
|
||||
tags?: string
|
||||
}
|
||||
|
||||
export interface KalshiGetSeriesListResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
series: KalshiSeries[]
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSeriesListTool: ToolConfig<
|
||||
KalshiGetSeriesListParams,
|
||||
KalshiGetSeriesListResponse
|
||||
> = {
|
||||
id: 'kalshi_get_series_list',
|
||||
name: 'Get Series List from Kalshi',
|
||||
description: 'Retrieve a list of market series from Kalshi with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
category: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by category (e.g., "Economics", "Politics", "Crypto")',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by comma-separated tags',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.category) queryParams.append('category', params.category)
|
||||
if (params.tags) queryParams.append('tags', params.tags)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/series')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_series_list')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: data.series || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'array',
|
||||
description: 'Array of series objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_SERIES_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Series List - adds metadata/volume flags and exact response mapping
|
||||
*/
|
||||
export interface KalshiGetSeriesListV2Params {
|
||||
category?: string
|
||||
tags?: string
|
||||
includeProductMetadata?: string
|
||||
includeVolume?: string
|
||||
minUpdatedTs?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetSeriesListV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
series: Array<{
|
||||
ticker: string
|
||||
frequency: string
|
||||
title: string
|
||||
category: string
|
||||
tags: string[] | null
|
||||
settlement_sources: Array<{ name: string; url: string }> | null
|
||||
contract_url: string | null
|
||||
contract_terms_url: string | null
|
||||
fee_type: string | null
|
||||
fee_multiplier: number | null
|
||||
additional_prohibitions: string[] | null
|
||||
product_metadata: Record<string, unknown> | null
|
||||
volume_fp: string | null
|
||||
last_updated_ts: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSeriesListV2Tool: ToolConfig<
|
||||
KalshiGetSeriesListV2Params,
|
||||
KalshiGetSeriesListV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_series_list_v2',
|
||||
name: 'Get Series List from Kalshi V2',
|
||||
description:
|
||||
'Retrieve a list of market series from Kalshi with optional filtering (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
category: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by category (e.g., "Economics", "Politics", "Crypto")',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by comma-separated tags',
|
||||
},
|
||||
includeProductMetadata: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include product metadata in response (true/false)',
|
||||
},
|
||||
includeVolume: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include volume data in response (true/false)',
|
||||
},
|
||||
minUpdatedTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum updated timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.category) queryParams.append('category', params.category)
|
||||
if (params.tags) queryParams.append('tags', params.tags)
|
||||
if (params.includeProductMetadata)
|
||||
queryParams.append('include_product_metadata', params.includeProductMetadata)
|
||||
if (params.includeVolume) queryParams.append('include_volume', params.includeVolume)
|
||||
if (params.minUpdatedTs !== undefined)
|
||||
queryParams.append('min_updated_ts', params.minUpdatedTs.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/series')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_series_list_v2')
|
||||
}
|
||||
|
||||
const series = (data.series || []).map((s: Record<string, unknown>) => {
|
||||
const settlementSources = s.settlement_sources
|
||||
? (s.settlement_sources as Array<Record<string, unknown>>).map((src) => ({
|
||||
name: (src.name as string) ?? null,
|
||||
url: (src.url as string) ?? null,
|
||||
}))
|
||||
: null
|
||||
|
||||
return {
|
||||
ticker: s.ticker ?? null,
|
||||
frequency: s.frequency ?? null,
|
||||
title: s.title ?? null,
|
||||
category: s.category ?? null,
|
||||
tags: s.tags ?? null,
|
||||
settlement_sources: settlementSources,
|
||||
contract_url: s.contract_url ?? null,
|
||||
contract_terms_url: s.contract_terms_url ?? null,
|
||||
fee_type: s.fee_type ?? null,
|
||||
fee_multiplier: s.fee_multiplier ?? null,
|
||||
additional_prohibitions: s.additional_prohibitions ?? null,
|
||||
product_metadata: s.product_metadata ?? null,
|
||||
volume_fp: s.volume_fp ?? null,
|
||||
last_updated_ts: s.last_updated_ts ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'array',
|
||||
description: 'Array of series objects with all API fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_SERIES_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
import type {
|
||||
KalshiAuthParams,
|
||||
KalshiPaginationParams,
|
||||
KalshiPagingInfo,
|
||||
} from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiAuthHeaders,
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_SETTLEMENT_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetSettlementsParams extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
minTs?: number
|
||||
maxTs?: number
|
||||
}
|
||||
|
||||
export interface KalshiGetSettlementsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
settlements: Array<Record<string, unknown>>
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSettlementsTool: ToolConfig<
|
||||
KalshiGetSettlementsParams,
|
||||
KalshiGetSettlementsResponse
|
||||
> = {
|
||||
id: 'kalshi_get_settlements',
|
||||
name: 'Get Settlements from Kalshi',
|
||||
description: 'Retrieve your portfolio settlement history from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum settled timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum settled timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.minTs !== undefined) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs !== undefined) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/settlements')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/settlements'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_settlements')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
settlements: data.settlements || [],
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
settlements: {
|
||||
type: 'array',
|
||||
description: 'Array of settlement objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_SETTLEMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Params for Get Settlements - adds subaccount and exact response mapping
|
||||
*/
|
||||
export interface KalshiGetSettlementsV2Params extends KalshiAuthParams, KalshiPaginationParams {
|
||||
ticker?: string
|
||||
eventTicker?: string
|
||||
minTs?: number
|
||||
maxTs?: number
|
||||
subaccount?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Response matching Kalshi API exactly
|
||||
*/
|
||||
export interface KalshiGetSettlementsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
settlements: Array<{
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
market_result: string | null
|
||||
yes_count_fp: string | null
|
||||
yes_total_cost_dollars: string | null
|
||||
no_count_fp: string | null
|
||||
no_total_cost_dollars: string | null
|
||||
revenue: number | null
|
||||
settled_time: string | null
|
||||
fee_cost: string | null
|
||||
value: number | null
|
||||
}>
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetSettlementsV2Tool: ToolConfig<
|
||||
KalshiGetSettlementsV2Params,
|
||||
KalshiGetSettlementsV2Response
|
||||
> = {
|
||||
id: 'kalshi_get_settlements_v2',
|
||||
name: 'Get Settlements from Kalshi V2',
|
||||
description: 'Retrieve your portfolio settlement history from Kalshi (V2 - exact API response)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
eventTicker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum settled timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum settled timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
subaccount: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Subaccount number (0 for primary, 1-63 for subaccounts)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.eventTicker) queryParams.append('event_ticker', params.eventTicker)
|
||||
if (params.minTs !== undefined) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs !== undefined) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.subaccount) queryParams.append('subaccount', params.subaccount)
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/portfolio/settlements')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/settlements'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_settlements_v2')
|
||||
}
|
||||
|
||||
const settlements = (data.settlements || []).map((s: Record<string, unknown>) => ({
|
||||
ticker: s.ticker ?? null,
|
||||
event_ticker: s.event_ticker ?? null,
|
||||
market_result: s.market_result ?? null,
|
||||
yes_count_fp: s.yes_count_fp ?? null,
|
||||
yes_total_cost_dollars: s.yes_total_cost_dollars ?? null,
|
||||
no_count_fp: s.no_count_fp ?? null,
|
||||
no_total_cost_dollars: s.no_total_cost_dollars ?? null,
|
||||
revenue: s.revenue ?? null,
|
||||
settled_time: s.settled_time ?? null,
|
||||
fee_cost: s.fee_cost ?? null,
|
||||
value: s.value ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
settlements,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
settlements: {
|
||||
type: 'array',
|
||||
description: 'Array of settlement objects with all API fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_SETTLEMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import type { KalshiPaginationParams, KalshiPagingInfo, KalshiTrade } from '@/tools/kalshi/types'
|
||||
import {
|
||||
buildKalshiUrl,
|
||||
handleKalshiError,
|
||||
KALSHI_TRADE_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/kalshi/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface KalshiGetTradesParams extends KalshiPaginationParams {}
|
||||
|
||||
export interface KalshiGetTradesResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
trades: KalshiTrade[]
|
||||
paging?: KalshiPagingInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetTradesTool: ToolConfig<KalshiGetTradesParams, KalshiGetTradesResponse> = {
|
||||
id: 'kalshi_get_trades',
|
||||
name: 'Get Trades from Kalshi',
|
||||
description: 'Retrieve recent trades across all markets',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/markets/trades')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_trades')
|
||||
}
|
||||
|
||||
const trades = data.trades || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
trades,
|
||||
paging: {
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
trades: {
|
||||
type: 'array',
|
||||
description: 'Array of trade objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_TRADE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 Get Trades Tool - Returns exact Kalshi API response structure with additional params
|
||||
*/
|
||||
export interface KalshiGetTradesV2Params extends KalshiPaginationParams {
|
||||
ticker?: string // Filter by market ticker
|
||||
minTs?: number // Minimum timestamp (Unix seconds)
|
||||
maxTs?: number // Maximum timestamp (Unix seconds)
|
||||
}
|
||||
|
||||
export interface KalshiGetTradesV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
trades: Array<{
|
||||
trade_id: string | null
|
||||
ticker: string
|
||||
yes_price: number | null
|
||||
no_price: number | null
|
||||
count: number | null
|
||||
count_fp: number | null
|
||||
created_time: string | null
|
||||
taker_side: string | null
|
||||
}>
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetTradesV2Tool: ToolConfig<KalshiGetTradesV2Params, KalshiGetTradesV2Response> =
|
||||
{
|
||||
id: 'kalshi_get_trades_v2',
|
||||
name: 'Get Trades from Kalshi V2',
|
||||
description:
|
||||
'Retrieve recent trades with additional filtering options (V2 - includes trade_id and count_fp)',
|
||||
version: '2.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by market ticker (e.g., "KXBTC-24DEC31")',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum timestamp in Unix seconds (e.g., 1704067200)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum timestamp in Unix seconds (e.g., 1704153600)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (1-1000, default: 100)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from previous response for fetching next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.minTs) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl('/markets/trades')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_trades_v2')
|
||||
}
|
||||
|
||||
const trades = (data.trades || []).map((t: Record<string, unknown>) => ({
|
||||
trade_id: t.trade_id ?? null,
|
||||
ticker: t.ticker ?? null,
|
||||
yes_price: t.yes_price ?? null,
|
||||
no_price: t.no_price ?? null,
|
||||
count: t.count ?? null,
|
||||
count_fp: t.count_fp ?? null,
|
||||
created_time: t.created_time ?? null,
|
||||
taker_side: t.taker_side ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
trades,
|
||||
cursor: data.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
trades: {
|
||||
type: 'array',
|
||||
description: 'Array of trade objects with trade_id and count_fp',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: KALSHI_TRADE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for fetching more results',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export { kalshiAmendOrderTool, kalshiAmendOrderV2Tool } from './amend_order'
|
||||
export { kalshiCancelOrderTool, kalshiCancelOrderV2Tool } from './cancel_order'
|
||||
export { kalshiCreateOrderTool, kalshiCreateOrderV2Tool } from './create_order'
|
||||
export { kalshiGetBalanceTool, kalshiGetBalanceV2Tool } from './get_balance'
|
||||
export { kalshiGetCandlesticksTool, kalshiGetCandlesticksV2Tool } from './get_candlesticks'
|
||||
export { kalshiGetEventTool, kalshiGetEventV2Tool } from './get_event'
|
||||
export {
|
||||
kalshiGetEventCandlesticksTool,
|
||||
kalshiGetEventCandlesticksV2Tool,
|
||||
} from './get_event_candlesticks'
|
||||
export { kalshiGetEventsTool, kalshiGetEventsV2Tool } from './get_events'
|
||||
export {
|
||||
kalshiGetExchangeAnnouncementsTool,
|
||||
kalshiGetExchangeAnnouncementsV2Tool,
|
||||
} from './get_exchange_announcements'
|
||||
export {
|
||||
kalshiGetExchangeScheduleTool,
|
||||
kalshiGetExchangeScheduleV2Tool,
|
||||
} from './get_exchange_schedule'
|
||||
export { kalshiGetExchangeStatusTool, kalshiGetExchangeStatusV2Tool } from './get_exchange_status'
|
||||
export { kalshiGetFillsTool, kalshiGetFillsV2Tool } from './get_fills'
|
||||
export { kalshiGetMarketTool, kalshiGetMarketV2Tool } from './get_market'
|
||||
export { kalshiGetMarketsTool, kalshiGetMarketsV2Tool } from './get_markets'
|
||||
export { kalshiGetOrderTool, kalshiGetOrderV2Tool } from './get_order'
|
||||
export { kalshiGetOrderbookTool, kalshiGetOrderbookV2Tool } from './get_orderbook'
|
||||
export { kalshiGetOrdersTool, kalshiGetOrdersV2Tool } from './get_orders'
|
||||
export { kalshiGetPositionsTool, kalshiGetPositionsV2Tool } from './get_positions'
|
||||
export { kalshiGetSeriesByTickerTool, kalshiGetSeriesByTickerV2Tool } from './get_series_by_ticker'
|
||||
export { kalshiGetSeriesListTool, kalshiGetSeriesListV2Tool } from './get_series_list'
|
||||
export { kalshiGetSettlementsTool, kalshiGetSettlementsV2Tool } from './get_settlements'
|
||||
export { kalshiGetTradesTool, kalshiGetTradesV2Tool } from './get_trades'
|
||||
@@ -0,0 +1,630 @@
|
||||
import crypto from 'crypto'
|
||||
import type { OutputProperty } from '@/tools/types'
|
||||
|
||||
// Base URL for Kalshi API
|
||||
export const KALSHI_BASE_URL = 'https://api.elections.kalshi.com/trade-api/v2'
|
||||
|
||||
/**
|
||||
* Output property definitions for Kalshi Trade API responses.
|
||||
* @see https://trading-api.readme.io/reference/introduction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Output definition for market objects.
|
||||
* @see https://trading-api.readme.io/reference/getmarkets
|
||||
*/
|
||||
export const KALSHI_MARKET_OUTPUT_PROPERTIES = {
|
||||
ticker: { type: 'string', description: 'Unique market ticker identifier' },
|
||||
event_ticker: { type: 'string', description: 'Parent event ticker' },
|
||||
market_type: { type: 'string', description: 'Market type (binary, etc.)' },
|
||||
title: { type: 'string', description: 'Market title/question' },
|
||||
subtitle: { type: 'string', description: 'Market subtitle', optional: true },
|
||||
yes_sub_title: { type: 'string', description: 'Yes outcome subtitle', optional: true },
|
||||
no_sub_title: { type: 'string', description: 'No outcome subtitle', optional: true },
|
||||
open_time: { type: 'string', description: 'Market open time (ISO 8601)', optional: true },
|
||||
close_time: { type: 'string', description: 'Market close time (ISO 8601)', optional: true },
|
||||
expiration_time: { type: 'string', description: 'Contract expiration time', optional: true },
|
||||
status: { type: 'string', description: 'Market status (open, closed, settled, etc.)' },
|
||||
yes_bid: { type: 'number', description: 'Current best yes bid price in cents', optional: true },
|
||||
yes_ask: { type: 'number', description: 'Current best yes ask price in cents', optional: true },
|
||||
no_bid: { type: 'number', description: 'Current best no bid price in cents', optional: true },
|
||||
no_ask: { type: 'number', description: 'Current best no ask price in cents', optional: true },
|
||||
last_price: { type: 'number', description: 'Last trade price in cents', optional: true },
|
||||
previous_yes_bid: { type: 'number', description: 'Previous yes bid', optional: true },
|
||||
previous_yes_ask: { type: 'number', description: 'Previous yes ask', optional: true },
|
||||
previous_price: { type: 'number', description: 'Previous last price', optional: true },
|
||||
volume: { type: 'number', description: 'Total volume (contracts traded)', optional: true },
|
||||
volume_24h: { type: 'number', description: '24-hour trading volume', optional: true },
|
||||
liquidity: { type: 'number', description: 'Market liquidity measure', optional: true },
|
||||
open_interest: {
|
||||
type: 'number',
|
||||
description: 'Open interest (outstanding contracts)',
|
||||
optional: true,
|
||||
},
|
||||
result: { type: 'string', description: 'Settlement result (yes, no, null)', optional: true },
|
||||
cap_strike: { type: 'number', description: 'Cap strike for ranged markets', optional: true },
|
||||
floor_strike: { type: 'number', description: 'Floor strike for ranged markets', optional: true },
|
||||
category: { type: 'string', description: 'Market category', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete market output definition
|
||||
*/
|
||||
export const KALSHI_MARKET_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi market object',
|
||||
properties: KALSHI_MARKET_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for event objects.
|
||||
* @see https://trading-api.readme.io/reference/getevents
|
||||
*/
|
||||
export const KALSHI_EVENT_OUTPUT_PROPERTIES = {
|
||||
event_ticker: { type: 'string', description: 'Unique event ticker identifier' },
|
||||
series_ticker: { type: 'string', description: 'Parent series ticker' },
|
||||
title: { type: 'string', description: 'Event title' },
|
||||
sub_title: { type: 'string', description: 'Event subtitle', optional: true },
|
||||
mutually_exclusive: { type: 'boolean', description: 'Whether markets are mutually exclusive' },
|
||||
category: { type: 'string', description: 'Event category' },
|
||||
strike_date: { type: 'string', description: 'Strike/settlement date', optional: true },
|
||||
status: { type: 'string', description: 'Event status', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete event output definition
|
||||
*/
|
||||
export const KALSHI_EVENT_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi event object',
|
||||
properties: KALSHI_EVENT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for order objects.
|
||||
* @see https://trading-api.readme.io/reference/getorders
|
||||
*/
|
||||
export const KALSHI_ORDER_OUTPUT_PROPERTIES = {
|
||||
order_id: { type: 'string', description: 'Unique order identifier' },
|
||||
user_id: { type: 'string', description: 'User ID', optional: true },
|
||||
client_order_id: { type: 'string', description: 'Client-provided order ID', optional: true },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
side: { type: 'string', description: 'Order side (yes/no)' },
|
||||
action: { type: 'string', description: 'Order action (buy/sell)' },
|
||||
type: { type: 'string', description: 'Order type (limit/market)' },
|
||||
status: { type: 'string', description: 'Order status (resting, canceled, executed)' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents', optional: true },
|
||||
no_price: { type: 'number', description: 'No price in cents', optional: true },
|
||||
fill_count: { type: 'number', description: 'Number of contracts filled', optional: true },
|
||||
remaining_count: { type: 'number', description: 'Remaining contracts to fill', optional: true },
|
||||
initial_count: { type: 'number', description: 'Initial order size', optional: true },
|
||||
taker_fees: { type: 'number', description: 'Taker fees paid in cents', optional: true },
|
||||
maker_fees: { type: 'number', description: 'Maker fees paid in cents', optional: true },
|
||||
created_time: { type: 'string', description: 'Order creation time (ISO 8601)', optional: true },
|
||||
expiration_time: { type: 'string', description: 'Order expiration time', optional: true },
|
||||
last_update_time: { type: 'string', description: 'Last order update time', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete order output definition
|
||||
*/
|
||||
export const KALSHI_ORDER_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi order object',
|
||||
properties: KALSHI_ORDER_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for position objects.
|
||||
* @see https://trading-api.readme.io/reference/getpositions
|
||||
*/
|
||||
export const KALSHI_POSITION_OUTPUT_PROPERTIES = {
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
event_title: { type: 'string', description: 'Event title', optional: true },
|
||||
market_title: { type: 'string', description: 'Market title', optional: true },
|
||||
position: { type: 'number', description: 'Net position (positive=yes, negative=no)' },
|
||||
market_exposure: {
|
||||
type: 'number',
|
||||
description: 'Maximum potential loss in cents',
|
||||
optional: true,
|
||||
},
|
||||
realized_pnl: { type: 'number', description: 'Realized profit/loss in cents', optional: true },
|
||||
total_traded: { type: 'number', description: 'Total contracts traded', optional: true },
|
||||
resting_orders_count: { type: 'number', description: 'Number of resting orders', optional: true },
|
||||
fees_paid: { type: 'number', description: 'Total fees paid in cents', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete position output definition
|
||||
*/
|
||||
export const KALSHI_POSITION_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi market position object',
|
||||
properties: KALSHI_POSITION_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for event position objects.
|
||||
* @see https://trading-api.readme.io/reference/getpositions
|
||||
*/
|
||||
export const KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES = {
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
event_exposure: { type: 'number', description: 'Event-level exposure in cents' },
|
||||
realized_pnl: { type: 'number', description: 'Realized P&L in cents', optional: true },
|
||||
total_cost: { type: 'number', description: 'Total cost basis in cents', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete event position output definition
|
||||
*/
|
||||
export const KALSHI_EVENT_POSITION_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi event position object',
|
||||
properties: KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for fill/trade objects.
|
||||
* @see https://trading-api.readme.io/reference/getfills
|
||||
*/
|
||||
export const KALSHI_FILL_OUTPUT_PROPERTIES = {
|
||||
trade_id: { type: 'string', description: 'Unique trade identifier' },
|
||||
order_id: { type: 'string', description: 'Associated order ID' },
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
side: { type: 'string', description: 'Trade side (yes/no)' },
|
||||
action: { type: 'string', description: 'Trade action (buy/sell)' },
|
||||
count: { type: 'number', description: 'Number of contracts' },
|
||||
yes_price: { type: 'number', description: 'Yes price in cents' },
|
||||
no_price: { type: 'number', description: 'No price in cents' },
|
||||
is_taker: { type: 'boolean', description: 'Whether this was a taker trade' },
|
||||
created_time: { type: 'string', description: 'Trade execution time (ISO 8601)' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete fill output definition
|
||||
*/
|
||||
export const KALSHI_FILL_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi trade fill object',
|
||||
properties: KALSHI_FILL_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for trade objects (public trades).
|
||||
* @see https://trading-api.readme.io/reference/gettrades
|
||||
*/
|
||||
export const KALSHI_TRADE_OUTPUT_PROPERTIES = {
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
yes_price: { type: 'number', description: 'Trade price for yes in cents' },
|
||||
no_price: { type: 'number', description: 'Trade price for no in cents' },
|
||||
count: { type: 'number', description: 'Number of contracts traded' },
|
||||
taker_side: { type: 'string', description: 'Taker side (yes/no)' },
|
||||
created_time: { type: 'string', description: 'Trade time (ISO 8601)' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete trade output definition
|
||||
*/
|
||||
export const KALSHI_TRADE_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi public trade object',
|
||||
properties: KALSHI_TRADE_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for candlestick/OHLC objects.
|
||||
* @see https://trading-api.readme.io/reference/getmarketshistory
|
||||
*/
|
||||
export const KALSHI_CANDLESTICK_OUTPUT_PROPERTIES = {
|
||||
open_time: { type: 'string', description: 'Candle open time (ISO 8601)' },
|
||||
close_time: { type: 'string', description: 'Candle close time (ISO 8601)' },
|
||||
open: { type: 'number', description: 'Opening price in cents' },
|
||||
high: { type: 'number', description: 'High price in cents' },
|
||||
low: { type: 'number', description: 'Low price in cents' },
|
||||
close: { type: 'number', description: 'Closing price in cents' },
|
||||
volume: { type: 'number', description: 'Volume during period' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete candlestick output definition
|
||||
*/
|
||||
export const KALSHI_CANDLESTICK_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi price candlestick/OHLC data',
|
||||
properties: KALSHI_CANDLESTICK_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for orderbook level objects.
|
||||
* @see https://trading-api.readme.io/reference/getmarketorderbook
|
||||
*/
|
||||
export const KALSHI_ORDERBOOK_LEVEL_OUTPUT_PROPERTIES = {
|
||||
price: { type: 'number', description: 'Price level in cents' },
|
||||
quantity: { type: 'number', description: 'Quantity at this price level' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete orderbook level output definition
|
||||
*/
|
||||
export const KALSHI_ORDERBOOK_LEVEL_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Orderbook price level',
|
||||
properties: KALSHI_ORDERBOOK_LEVEL_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for series objects.
|
||||
* @see https://trading-api.readme.io/reference/getseries
|
||||
*/
|
||||
export const KALSHI_SERIES_OUTPUT_PROPERTIES = {
|
||||
ticker: { type: 'string', description: 'Unique series ticker' },
|
||||
title: { type: 'string', description: 'Series title' },
|
||||
frequency: { type: 'string', description: 'Event frequency (daily, weekly, etc.)' },
|
||||
category: { type: 'string', description: 'Series category' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Series tags',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
optional: true,
|
||||
},
|
||||
contract_url: { type: 'string', description: 'Contract rules URL', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete series output definition
|
||||
*/
|
||||
export const KALSHI_SERIES_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi series object',
|
||||
properties: KALSHI_SERIES_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for balance objects.
|
||||
* @see https://trading-api.readme.io/reference/getbalance
|
||||
*/
|
||||
export const KALSHI_BALANCE_OUTPUT_PROPERTIES = {
|
||||
balance: { type: 'number', description: 'Available balance in cents' },
|
||||
portfolio_value: { type: 'number', description: 'Total portfolio value in cents' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete balance output definition
|
||||
*/
|
||||
export const KALSHI_BALANCE_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Kalshi account balance',
|
||||
properties: KALSHI_BALANCE_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for settlement objects.
|
||||
* @see https://docs.kalshi.com/api-reference/portfolio/get-settlements
|
||||
*/
|
||||
export const KALSHI_SETTLEMENT_OUTPUT_PROPERTIES = {
|
||||
ticker: { type: 'string', description: 'Market ticker' },
|
||||
event_ticker: { type: 'string', description: 'Event ticker' },
|
||||
market_result: { type: 'string', description: 'Settlement outcome (yes, no, scalar)' },
|
||||
yes_count_fp: { type: 'string', description: 'Yes contracts owned (fixed-point)' },
|
||||
yes_total_cost_dollars: { type: 'string', description: 'Yes cost basis in dollars' },
|
||||
no_count_fp: { type: 'string', description: 'No contracts owned (fixed-point)' },
|
||||
no_total_cost_dollars: { type: 'string', description: 'No cost basis in dollars' },
|
||||
revenue: { type: 'number', description: 'Payout in cents' },
|
||||
settled_time: { type: 'string', description: 'Settlement timestamp (ISO 8601)' },
|
||||
fee_cost: { type: 'string', description: 'Fees in fixed-point dollars' },
|
||||
value: { type: 'number', description: 'Single yes contract payout in cents', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Output definition for exchange announcement objects.
|
||||
* @see https://docs.kalshi.com/api-reference/exchange/get-exchange-announcements
|
||||
*/
|
||||
export const KALSHI_ANNOUNCEMENT_OUTPUT_PROPERTIES = {
|
||||
type: { type: 'string', description: 'Announcement severity (info, warning, error)' },
|
||||
message: { type: 'string', description: 'Announcement message' },
|
||||
delivery_time: { type: 'string', description: 'Delivery time (ISO 8601)' },
|
||||
status: { type: 'string', description: 'Announcement status (active, inactive)' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Pagination output properties
|
||||
*/
|
||||
export const KALSHI_PAGING_OUTPUT_PROPERTIES = {
|
||||
cursor: { type: 'string', description: 'Cursor for fetching next page', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete paging output definition
|
||||
*/
|
||||
export const KALSHI_PAGING_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Pagination information',
|
||||
properties: KALSHI_PAGING_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
// Base params for authenticated endpoints
|
||||
export interface KalshiAuthParams {
|
||||
keyId: string // API Key ID
|
||||
privateKey: string // RSA Private Key (PEM format)
|
||||
}
|
||||
|
||||
// Pagination params
|
||||
export interface KalshiPaginationParams {
|
||||
limit?: string // 1-1000, default 100
|
||||
cursor?: string // Pagination cursor
|
||||
}
|
||||
|
||||
// Pagination info in response
|
||||
export interface KalshiPagingInfo {
|
||||
cursor?: string | null
|
||||
}
|
||||
|
||||
// Generic response type
|
||||
interface KalshiResponse<T> {
|
||||
success: boolean
|
||||
output: T & {
|
||||
paging?: KalshiPagingInfo
|
||||
metadata: {
|
||||
operation: string
|
||||
[key: string]: any
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Market type
|
||||
export interface KalshiMarket {
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
market_type: string
|
||||
title: string
|
||||
subtitle?: string
|
||||
yes_sub_title?: string
|
||||
no_sub_title?: string
|
||||
open_time: string
|
||||
close_time: string
|
||||
expiration_time: string
|
||||
status: string
|
||||
yes_bid: number
|
||||
yes_ask: number
|
||||
no_bid: number
|
||||
no_ask: number
|
||||
last_price: number
|
||||
previous_yes_bid?: number
|
||||
previous_yes_ask?: number
|
||||
previous_price?: number
|
||||
volume: number
|
||||
volume_24h: number
|
||||
liquidity?: number
|
||||
open_interest?: number
|
||||
result?: string
|
||||
cap_strike?: number
|
||||
floor_strike?: number
|
||||
}
|
||||
|
||||
// Event type
|
||||
export interface KalshiEvent {
|
||||
event_ticker: string
|
||||
series_ticker: string
|
||||
sub_title?: string
|
||||
title: string
|
||||
mutually_exclusive: boolean
|
||||
category: string
|
||||
markets?: KalshiMarket[]
|
||||
strike_date?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
// Balance type
|
||||
interface KalshiBalance {
|
||||
balance: number // In cents
|
||||
portfolio_value: number // In cents
|
||||
}
|
||||
|
||||
// Position type
|
||||
export interface KalshiPosition {
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
event_title?: string
|
||||
market_title?: string
|
||||
position: number
|
||||
market_exposure?: number
|
||||
realized_pnl?: number
|
||||
total_traded?: number
|
||||
resting_orders_count?: number
|
||||
}
|
||||
|
||||
// Order type
|
||||
export interface KalshiOrder {
|
||||
order_id: string
|
||||
ticker: string
|
||||
event_ticker: string
|
||||
status: string
|
||||
side: string
|
||||
type: string
|
||||
yes_price?: number
|
||||
no_price?: number
|
||||
action: string
|
||||
count: number
|
||||
remaining_count: number
|
||||
created_time: string
|
||||
expiration_time?: string
|
||||
place_count?: number
|
||||
decrease_count?: number
|
||||
maker_fill_count?: number
|
||||
taker_fill_count?: number
|
||||
taker_fees?: number
|
||||
}
|
||||
|
||||
// Orderbook type
|
||||
interface KalshiOrderbookLevel {
|
||||
price: number
|
||||
quantity: number
|
||||
}
|
||||
|
||||
export interface KalshiOrderbook {
|
||||
yes: KalshiOrderbookLevel[]
|
||||
no: KalshiOrderbookLevel[]
|
||||
}
|
||||
|
||||
// Trade type
|
||||
export interface KalshiTrade {
|
||||
ticker: string
|
||||
yes_price: number
|
||||
no_price: number
|
||||
count: number
|
||||
created_time: string
|
||||
taker_side: string
|
||||
}
|
||||
|
||||
// Candlestick type
|
||||
export interface KalshiCandlestick {
|
||||
open_time: string
|
||||
close_time: string
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
volume: number
|
||||
}
|
||||
|
||||
// Fill type
|
||||
export interface KalshiFill {
|
||||
created_time: string
|
||||
ticker: string
|
||||
is_taker: boolean
|
||||
side: string
|
||||
yes_price: number
|
||||
no_price: number
|
||||
count: number
|
||||
order_id: string
|
||||
trade_id: string
|
||||
}
|
||||
|
||||
// Settlement source type
|
||||
interface KalshiSettlementSource {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
// Series type
|
||||
export interface KalshiSeries {
|
||||
ticker: string
|
||||
title: string
|
||||
frequency: string
|
||||
category: string
|
||||
tags?: string[]
|
||||
settlement_sources?: KalshiSettlementSource[]
|
||||
contract_url?: string
|
||||
contract_terms_url?: string
|
||||
fee_type?: string // 'quadratic' | 'quadratic_with_maker_fees' | 'flat'
|
||||
fee_multiplier?: number
|
||||
additional_prohibitions?: string[]
|
||||
product_metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
// Exchange status type
|
||||
export interface KalshiExchangeStatus {
|
||||
trading_active: boolean
|
||||
exchange_active: boolean
|
||||
}
|
||||
|
||||
// Helper function to build Kalshi API URLs
|
||||
export function buildKalshiUrl(path: string): string {
|
||||
return `${KALSHI_BASE_URL}${path}`
|
||||
}
|
||||
|
||||
// Helper to normalize PEM key format
|
||||
// Handles: literal \n strings, missing line breaks, various PEM formats
|
||||
function normalizePemKey(privateKey: string): string {
|
||||
let key = privateKey.trim()
|
||||
|
||||
// Convert literal \n strings to actual newlines
|
||||
key = key.replace(/\\n/g, '\n')
|
||||
|
||||
// Extract the key type and base64 content
|
||||
const beginMatch = key.match(/-----BEGIN ([A-Z\s]+)-----/)
|
||||
const endMatch = key.match(/-----END ([A-Z\s]+)-----/)
|
||||
|
||||
if (beginMatch && endMatch) {
|
||||
// Extract the key type (e.g., "RSA PRIVATE KEY" or "PRIVATE KEY")
|
||||
const keyType = beginMatch[1]
|
||||
|
||||
// Extract base64 content between headers
|
||||
const startIdx = key.indexOf('-----', key.indexOf('-----') + 5) + 5
|
||||
const endIdx = key.lastIndexOf('-----END')
|
||||
let base64Content = key.substring(startIdx, endIdx)
|
||||
|
||||
// Remove all whitespace from base64 content
|
||||
base64Content = base64Content.replace(/\s/g, '')
|
||||
|
||||
// Reconstruct PEM with proper 64-character line breaks
|
||||
const lines: string[] = []
|
||||
for (let i = 0; i < base64Content.length; i += 64) {
|
||||
lines.push(base64Content.substring(i, i + 64))
|
||||
}
|
||||
|
||||
return `-----BEGIN ${keyType}-----\n${lines.join('\n')}\n-----END ${keyType}-----`
|
||||
}
|
||||
|
||||
// No PEM headers found - assume raw base64, wrap in PKCS#8 format
|
||||
const cleanKey = key.replace(/\s/g, '')
|
||||
const lines: string[] = []
|
||||
for (let i = 0; i < cleanKey.length; i += 64) {
|
||||
lines.push(cleanKey.substring(i, i + 64))
|
||||
}
|
||||
|
||||
return `-----BEGIN PRIVATE KEY-----\n${lines.join('\n')}\n-----END PRIVATE KEY-----`
|
||||
}
|
||||
|
||||
// RSA-PSS signature generation for authenticated requests
|
||||
// Kalshi requires RSA-PSS with SHA256, not plain PKCS#1 v1.5
|
||||
export function generateKalshiSignature(
|
||||
privateKey: string,
|
||||
timestamp: string,
|
||||
method: string,
|
||||
path: string
|
||||
): string {
|
||||
// Sign: timestamp + method + path (without query params)
|
||||
// Strip query params from path for signing
|
||||
const pathWithoutQuery = path.split('?')[0]
|
||||
const message = timestamp + method.toUpperCase() + pathWithoutQuery
|
||||
|
||||
// Normalize PEM key format (handles literal \n, missing line breaks, etc.)
|
||||
const pemKey = normalizePemKey(privateKey)
|
||||
|
||||
// Use RSA-PSS padding with SHA256 (required by Kalshi API)
|
||||
const signature = crypto.sign('sha256', Buffer.from(message, 'utf-8'), {
|
||||
key: pemKey,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
|
||||
})
|
||||
|
||||
return signature.toString('base64')
|
||||
}
|
||||
|
||||
// Build auth headers for authenticated requests
|
||||
export function buildKalshiAuthHeaders(
|
||||
keyId: string,
|
||||
privateKey: string,
|
||||
method: string,
|
||||
path: string
|
||||
): Record<string, string> {
|
||||
const timestamp = Date.now().toString()
|
||||
const signature = generateKalshiSignature(privateKey, timestamp, method, path)
|
||||
|
||||
return {
|
||||
'KALSHI-ACCESS-KEY': keyId,
|
||||
'KALSHI-ACCESS-TIMESTAMP': timestamp,
|
||||
'KALSHI-ACCESS-SIGNATURE': signature,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for consistent error handling
|
||||
export function handleKalshiError(data: any, status: number, operation: string): never {
|
||||
const errorMessage =
|
||||
data.error?.message || data.error || data.message || data.detail || 'Unknown error'
|
||||
throw new Error(`Kalshi ${operation} failed: ${errorMessage}`)
|
||||
}
|
||||
Reference in New Issue
Block a user