@gaberoo/kalshitools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +666 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +5 -0
  6. package/dist/commands/config/init.d.ts +13 -0
  7. package/dist/commands/config/init.js +89 -0
  8. package/dist/commands/config/show.d.ts +10 -0
  9. package/dist/commands/config/show.js +77 -0
  10. package/dist/commands/markets/list.d.ts +11 -0
  11. package/dist/commands/markets/list.js +64 -0
  12. package/dist/commands/markets/show.d.ts +13 -0
  13. package/dist/commands/markets/show.js +79 -0
  14. package/dist/commands/orders/cancel.d.ts +14 -0
  15. package/dist/commands/orders/cancel.js +129 -0
  16. package/dist/commands/orders/create.d.ts +19 -0
  17. package/dist/commands/orders/create.js +211 -0
  18. package/dist/commands/orders/list.d.ts +13 -0
  19. package/dist/commands/orders/list.js +92 -0
  20. package/dist/commands/portfolio/balance.d.ts +9 -0
  21. package/dist/commands/portfolio/balance.js +36 -0
  22. package/dist/commands/portfolio/fills.d.ts +11 -0
  23. package/dist/commands/portfolio/fills.js +80 -0
  24. package/dist/commands/portfolio/positions.d.ts +9 -0
  25. package/dist/commands/portfolio/positions.js +58 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +1 -0
  28. package/dist/lib/base-command.d.ts +13 -0
  29. package/dist/lib/base-command.js +38 -0
  30. package/dist/lib/config/manager.d.ts +71 -0
  31. package/dist/lib/config/manager.js +137 -0
  32. package/dist/lib/config/schema.d.ts +175 -0
  33. package/dist/lib/config/schema.js +59 -0
  34. package/dist/lib/errors/base.d.ts +84 -0
  35. package/dist/lib/errors/base.js +106 -0
  36. package/dist/lib/kalshi/auth.d.ts +17 -0
  37. package/dist/lib/kalshi/auth.js +71 -0
  38. package/dist/lib/kalshi/client.d.ts +86 -0
  39. package/dist/lib/kalshi/client.js +228 -0
  40. package/dist/lib/kalshi/index.d.ts +8 -0
  41. package/dist/lib/kalshi/index.js +19 -0
  42. package/dist/lib/kalshi/types.d.ts +155 -0
  43. package/dist/lib/kalshi/types.js +4 -0
  44. package/dist/lib/logger.d.ts +9 -0
  45. package/dist/lib/logger.js +41 -0
  46. package/dist/lib/output/formatter.d.ts +69 -0
  47. package/dist/lib/output/formatter.js +111 -0
  48. package/dist/lib/retry.d.ts +18 -0
  49. package/dist/lib/retry.js +81 -0
  50. package/dist/lib/sanitize.d.ts +28 -0
  51. package/dist/lib/sanitize.js +124 -0
  52. package/dist/lib/shutdown.d.ts +43 -0
  53. package/dist/lib/shutdown.js +106 -0
  54. package/dist/lib/validation.d.ts +37 -0
  55. package/dist/lib/validation.js +120 -0
  56. package/oclif.manifest.json +520 -0
  57. package/package.json +98 -0
@@ -0,0 +1,86 @@
1
+ import type { Balance, CreateOrderRequest, Fill, FillsQuery, Market, MarketsQuery, Order, OrderBook, OrdersQuery, Position } from './types.js';
2
+ /**
3
+ * Kalshi API client
4
+ */
5
+ export declare class KalshiClient {
6
+ private axios;
7
+ private auth;
8
+ private baseUrl;
9
+ constructor(baseUrl: string, keyId: string, privateKey: string);
10
+ /**
11
+ * Make a rate-limited GET request
12
+ */
13
+ private get;
14
+ /**
15
+ * Make a rate-limited POST request
16
+ */
17
+ private post;
18
+ /**
19
+ * Make a rate-limited DELETE request
20
+ */
21
+ private delete;
22
+ /**
23
+ * Get account balance
24
+ */
25
+ getBalance(): Promise<Balance>;
26
+ /**
27
+ * Get portfolio positions
28
+ */
29
+ getPositions(params?: {
30
+ ticker?: string;
31
+ settlement_status?: string;
32
+ }): Promise<Position[]>;
33
+ /**
34
+ * Get markets
35
+ */
36
+ getMarkets(query?: MarketsQuery): Promise<{
37
+ markets: Market[];
38
+ cursor?: string;
39
+ }>;
40
+ /**
41
+ * Get single market by ticker
42
+ */
43
+ getMarket(ticker: string): Promise<Market>;
44
+ /**
45
+ * Get order book for a market
46
+ */
47
+ getOrderBook(ticker: string, depth?: number): Promise<OrderBook>;
48
+ /**
49
+ * Get user orders
50
+ */
51
+ getOrders(query?: OrdersQuery): Promise<{
52
+ orders: Order[];
53
+ cursor?: string;
54
+ }>;
55
+ /**
56
+ * Get single order by ID
57
+ */
58
+ getOrder(orderId: string): Promise<Order>;
59
+ /**
60
+ * Create an order
61
+ */
62
+ createOrder(request: CreateOrderRequest): Promise<Order>;
63
+ /**
64
+ * Cancel an order
65
+ */
66
+ cancelOrder(orderId: string): Promise<Order>;
67
+ /**
68
+ * Get fills (executed trades)
69
+ */
70
+ getFills(query?: FillsQuery): Promise<{
71
+ fills: Fill[];
72
+ cursor?: string;
73
+ }>;
74
+ /**
75
+ * Test connection to API
76
+ */
77
+ testConnection(): Promise<boolean>;
78
+ /**
79
+ * Get the base URL
80
+ */
81
+ getBaseUrl(): string;
82
+ }
83
+ /**
84
+ * Create a Kalshi client instance
85
+ */
86
+ export declare function createKalshiClient(baseUrl: string, keyId: string, privateKey: string): KalshiClient;
@@ -0,0 +1,228 @@
1
+ import axios from 'axios';
2
+ import Bottleneck from 'bottleneck';
3
+ import { APIError, NetworkError, RateLimitError, TimeoutError } from '../errors/base.js';
4
+ import { logger } from '../logger.js';
5
+ import { KalshiAuth } from './auth.js';
6
+ /**
7
+ * Rate limiter configuration for Kalshi API
8
+ * Basic tier: 20 req/sec for reads, 10 req/sec for writes
9
+ */
10
+ const READ_LIMITER = new Bottleneck({
11
+ maxConcurrent: 5,
12
+ minTime: 50, // 20 requests per second = 1000ms / 20 = 50ms
13
+ reservoir: 20,
14
+ reservoirRefreshAmount: 20,
15
+ reservoirRefreshInterval: 1000, // Refresh every second
16
+ });
17
+ const WRITE_LIMITER = new Bottleneck({
18
+ maxConcurrent: 3,
19
+ minTime: 100, // 10 requests per second = 1000ms / 10 = 100ms
20
+ reservoir: 10,
21
+ reservoirRefreshAmount: 10,
22
+ reservoirRefreshInterval: 1000,
23
+ });
24
+ /**
25
+ * Kalshi API client
26
+ */
27
+ export class KalshiClient {
28
+ axios;
29
+ auth;
30
+ baseUrl;
31
+ constructor(baseUrl, keyId, privateKey) {
32
+ this.baseUrl = baseUrl;
33
+ this.auth = new KalshiAuth(keyId, privateKey);
34
+ this.axios = axios.create({
35
+ baseURL: baseUrl,
36
+ timeout: 30000, // 30 second default timeout
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Accept': 'application/json',
40
+ },
41
+ });
42
+ // Add request interceptor for authentication
43
+ this.axios.interceptors.request.use((config) => {
44
+ const method = (config.method || 'GET').toUpperCase();
45
+ const path = config.url || '/';
46
+ const authHeaders = this.auth.generateAuthHeaders(method, path);
47
+ // Set auth headers
48
+ for (const [key, value] of Object.entries(authHeaders)) {
49
+ config.headers.set(key, value);
50
+ }
51
+ logger.debug({ method, path }, 'Making API request');
52
+ return config;
53
+ });
54
+ // Add response interceptor for error handling
55
+ this.axios.interceptors.response.use((response) => response, (error) => {
56
+ if (axios.isAxiosError(error)) {
57
+ if (error.code === 'ECONNABORTED') {
58
+ throw new TimeoutError('Request timed out', {
59
+ url: error.config?.url,
60
+ timeout: error.config?.timeout,
61
+ });
62
+ }
63
+ if (!error.response) {
64
+ throw new NetworkError('Network error - no response received', {
65
+ message: error.message,
66
+ });
67
+ }
68
+ const status = error.response.status;
69
+ const data = error.response.data;
70
+ if (status === 429) {
71
+ throw new RateLimitError('Rate limit exceeded', {
72
+ status,
73
+ retryAfter: error.response.headers['retry-after'],
74
+ });
75
+ }
76
+ throw new APIError(data.error?.message || `API error: ${status}`, {
77
+ status,
78
+ code: data.error?.code,
79
+ details: data.error?.details,
80
+ });
81
+ }
82
+ throw error;
83
+ });
84
+ }
85
+ /**
86
+ * Make a rate-limited GET request
87
+ */
88
+ async get(path, config) {
89
+ return READ_LIMITER.schedule(async () => {
90
+ const response = await this.axios.get(path, config);
91
+ if (response.data.error) {
92
+ throw new APIError(response.data.error.message, {
93
+ code: response.data.error.code,
94
+ details: response.data.error.details,
95
+ });
96
+ }
97
+ return response.data.data;
98
+ });
99
+ }
100
+ /**
101
+ * Make a rate-limited POST request
102
+ */
103
+ async post(path, data, config) {
104
+ return WRITE_LIMITER.schedule(async () => {
105
+ const response = await this.axios.post(path, data, config);
106
+ if (response.data.error) {
107
+ throw new APIError(response.data.error.message, {
108
+ code: response.data.error.code,
109
+ details: response.data.error.details,
110
+ });
111
+ }
112
+ return response.data.data;
113
+ });
114
+ }
115
+ /**
116
+ * Make a rate-limited DELETE request
117
+ */
118
+ async delete(path, config) {
119
+ return WRITE_LIMITER.schedule(async () => {
120
+ const response = await this.axios.delete(path, config);
121
+ if (response.data.error) {
122
+ throw new APIError(response.data.error.message, {
123
+ code: response.data.error.code,
124
+ details: response.data.error.details,
125
+ });
126
+ }
127
+ return response.data.data;
128
+ });
129
+ }
130
+ /**
131
+ * Get account balance
132
+ */
133
+ async getBalance() {
134
+ logger.info('Fetching account balance');
135
+ return this.get('/portfolio/balance');
136
+ }
137
+ /**
138
+ * Get portfolio positions
139
+ */
140
+ async getPositions(params) {
141
+ logger.info({ params }, 'Fetching portfolio positions');
142
+ return this.get('/portfolio/positions', { params });
143
+ }
144
+ /**
145
+ * Get markets
146
+ */
147
+ async getMarkets(query) {
148
+ logger.info({ query }, 'Fetching markets');
149
+ return this.get('/markets', { params: query });
150
+ }
151
+ /**
152
+ * Get single market by ticker
153
+ */
154
+ async getMarket(ticker) {
155
+ logger.info({ ticker }, 'Fetching market details');
156
+ return this.get(`/markets/${ticker}`);
157
+ }
158
+ /**
159
+ * Get order book for a market
160
+ */
161
+ async getOrderBook(ticker, depth) {
162
+ logger.info({ ticker, depth }, 'Fetching order book');
163
+ return this.get(`/markets/${ticker}/orderbook`, {
164
+ params: depth ? { depth } : undefined,
165
+ });
166
+ }
167
+ /**
168
+ * Get user orders
169
+ */
170
+ async getOrders(query) {
171
+ logger.info({ query }, 'Fetching orders');
172
+ return this.get('/portfolio/orders', { params: query });
173
+ }
174
+ /**
175
+ * Get single order by ID
176
+ */
177
+ async getOrder(orderId) {
178
+ logger.info({ orderId }, 'Fetching order details');
179
+ return this.get(`/portfolio/orders/${orderId}`);
180
+ }
181
+ /**
182
+ * Create an order
183
+ */
184
+ async createOrder(request) {
185
+ logger.info({ ticker: request.ticker, side: request.side, action: request.action }, 'Creating order');
186
+ return this.post('/portfolio/orders', request);
187
+ }
188
+ /**
189
+ * Cancel an order
190
+ */
191
+ async cancelOrder(orderId) {
192
+ logger.info({ orderId }, 'Canceling order');
193
+ return this.delete(`/portfolio/orders/${orderId}`);
194
+ }
195
+ /**
196
+ * Get fills (executed trades)
197
+ */
198
+ async getFills(query) {
199
+ logger.info({ query }, 'Fetching fills');
200
+ return this.get('/portfolio/fills', { params: query });
201
+ }
202
+ /**
203
+ * Test connection to API
204
+ */
205
+ async testConnection() {
206
+ try {
207
+ await this.getBalance();
208
+ logger.info('API connection test successful');
209
+ return true;
210
+ }
211
+ catch (error) {
212
+ logger.error({ error }, 'API connection test failed');
213
+ return false;
214
+ }
215
+ }
216
+ /**
217
+ * Get the base URL
218
+ */
219
+ getBaseUrl() {
220
+ return this.baseUrl;
221
+ }
222
+ }
223
+ /**
224
+ * Create a Kalshi client instance
225
+ */
226
+ export function createKalshiClient(baseUrl, keyId, privateKey) {
227
+ return new KalshiClient(baseUrl, keyId, privateKey);
228
+ }
@@ -0,0 +1,8 @@
1
+ import { KalshiClient } from './client.js';
2
+ export * from './client.js';
3
+ export * from './types.js';
4
+ export * from './auth.js';
5
+ /**
6
+ * Create a Kalshi client from the current configuration
7
+ */
8
+ export declare function createClientFromConfig(): KalshiClient;
@@ -0,0 +1,19 @@
1
+ import { getConfig } from '../config/manager.js';
2
+ import { logger } from '../logger.js';
3
+ import { createKalshiClient } from './client.js';
4
+ export * from './client.js';
5
+ export * from './types.js';
6
+ export * from './auth.js';
7
+ /**
8
+ * Create a Kalshi client from the current configuration
9
+ */
10
+ export function createClientFromConfig() {
11
+ const configManager = getConfig();
12
+ const apiConfig = configManager.getApiConfig();
13
+ const privateKey = configManager.readPrivateKey();
14
+ logger.info({
15
+ baseUrl: apiConfig.baseUrl,
16
+ environment: configManager.getEnvironment(),
17
+ }, 'Creating Kalshi client from config');
18
+ return createKalshiClient(apiConfig.baseUrl, apiConfig.keyId, privateKey);
19
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Kalshi API types and interfaces
3
+ */
4
+ /**
5
+ * API response wrapper
6
+ */
7
+ export interface KalshiResponse<T> {
8
+ data?: T;
9
+ error?: {
10
+ code: string;
11
+ message: string;
12
+ details?: Record<string, unknown>;
13
+ };
14
+ }
15
+ /**
16
+ * User balance response
17
+ */
18
+ export interface Balance {
19
+ balance: number;
20
+ payout: number;
21
+ }
22
+ /**
23
+ * Market data
24
+ */
25
+ export interface Market {
26
+ ticker: string;
27
+ event_ticker: string;
28
+ market_type: string;
29
+ title: string;
30
+ subtitle: string;
31
+ open_time: string;
32
+ close_time: string;
33
+ expiration_time: string;
34
+ status: 'active' | 'closed' | 'settled' | 'finalized';
35
+ result?: 'yes' | 'no';
36
+ volume?: number;
37
+ volume_24h?: number;
38
+ liquidity?: number;
39
+ yes_bid?: number;
40
+ yes_ask?: number;
41
+ no_bid?: number;
42
+ no_ask?: number;
43
+ last_price?: number;
44
+ }
45
+ /**
46
+ * Position data
47
+ */
48
+ export interface Position {
49
+ ticker: string;
50
+ market_ticker: string;
51
+ position: number;
52
+ market_exposure: number;
53
+ total_cost: number;
54
+ fees_paid: number;
55
+ realized_pnl: number;
56
+ }
57
+ /**
58
+ * Order data
59
+ */
60
+ export interface Order {
61
+ order_id: string;
62
+ user_id: string;
63
+ ticker: string;
64
+ status: 'pending' | 'resting' | 'canceled' | 'executed' | 'expired';
65
+ yes_price?: number;
66
+ no_price?: number;
67
+ count: number;
68
+ side: 'yes' | 'no';
69
+ action: 'buy' | 'sell';
70
+ type: 'market' | 'limit';
71
+ created_time: string;
72
+ updated_time: string;
73
+ expiration_time?: string;
74
+ remaining_count: number;
75
+ filled_count: number;
76
+ avg_fill_price?: number;
77
+ }
78
+ /**
79
+ * Fill (executed trade) data
80
+ */
81
+ export interface Fill {
82
+ fill_id: string;
83
+ order_id: string;
84
+ ticker: string;
85
+ side: 'yes' | 'no';
86
+ action: 'buy' | 'sell';
87
+ count: number;
88
+ price: number;
89
+ created_time: string;
90
+ is_taker: boolean;
91
+ trade_id: string;
92
+ }
93
+ /**
94
+ * Order book level
95
+ */
96
+ export interface OrderBookLevel {
97
+ price: number;
98
+ count: number;
99
+ }
100
+ /**
101
+ * Order book data
102
+ */
103
+ export interface OrderBook {
104
+ ticker: string;
105
+ yes: OrderBookLevel[];
106
+ no: OrderBookLevel[];
107
+ }
108
+ /**
109
+ * Create order request
110
+ */
111
+ export interface CreateOrderRequest {
112
+ ticker: string;
113
+ client_order_id?: string;
114
+ side: 'yes' | 'no';
115
+ action: 'buy' | 'sell';
116
+ count: number;
117
+ type: 'market' | 'limit';
118
+ yes_price?: number;
119
+ no_price?: number;
120
+ expiration_ts?: number;
121
+ sell_position_floor?: number;
122
+ buy_max_cost?: number;
123
+ }
124
+ /**
125
+ * Markets query parameters
126
+ */
127
+ export interface MarketsQuery {
128
+ limit?: number;
129
+ cursor?: string;
130
+ event_ticker?: string;
131
+ series_ticker?: string;
132
+ status?: 'active' | 'closed' | 'settled' | 'finalized';
133
+ tickers?: string;
134
+ with_nested_markets?: boolean;
135
+ }
136
+ /**
137
+ * Orders query parameters
138
+ */
139
+ export interface OrdersQuery {
140
+ ticker?: string;
141
+ status?: 'pending' | 'resting' | 'canceled' | 'executed' | 'expired';
142
+ limit?: number;
143
+ cursor?: string;
144
+ }
145
+ /**
146
+ * Fills query parameters
147
+ */
148
+ export interface FillsQuery {
149
+ ticker?: string;
150
+ order_id?: string;
151
+ min_ts?: number;
152
+ max_ts?: number;
153
+ limit?: number;
154
+ cursor?: string;
155
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Kalshi API types and interfaces
3
+ */
4
+ export {};
@@ -0,0 +1,9 @@
1
+ import pino from 'pino';
2
+ /**
3
+ * Create a logger instance with redaction for sensitive fields
4
+ */
5
+ export declare function createLogger(options?: {
6
+ level?: string;
7
+ pretty?: boolean;
8
+ }): pino.Logger<never, boolean>;
9
+ export declare const logger: pino.Logger<never, boolean>;
@@ -0,0 +1,41 @@
1
+ import pino from 'pino';
2
+ /**
3
+ * Create a logger instance with redaction for sensitive fields
4
+ */
5
+ export function createLogger(options = {}) {
6
+ const { level = 'info', pretty = false } = options;
7
+ const pinoOptions = {
8
+ level,
9
+ // Redact sensitive fields
10
+ redact: {
11
+ paths: [
12
+ 'keyId',
13
+ 'privateKey',
14
+ 'privateKeyPath',
15
+ 'password',
16
+ 'token',
17
+ 'apiKey',
18
+ 'api.*.keyId',
19
+ 'api.*.privateKey',
20
+ 'api.*.privateKeyPath',
21
+ '*.privateKey',
22
+ '*.password',
23
+ '*.token',
24
+ ],
25
+ censor: '[REDACTED]',
26
+ },
27
+ };
28
+ if (pretty) {
29
+ return pino(pinoOptions, pino.transport({
30
+ target: 'pino-pretty',
31
+ options: {
32
+ colorize: true,
33
+ translateTime: 'HH:MM:ss',
34
+ ignore: 'pid,hostname',
35
+ },
36
+ }));
37
+ }
38
+ return pino(pinoOptions);
39
+ }
40
+ // Default logger instance
41
+ export const logger = createLogger({ level: process.env.LOG_LEVEL || 'info', pretty: true });
@@ -0,0 +1,69 @@
1
+ import Table from 'cli-table3';
2
+ /**
3
+ * Standard success response structure
4
+ */
5
+ export interface SuccessResponse<T = unknown> {
6
+ success: true;
7
+ data: T;
8
+ metadata: {
9
+ timestamp: string;
10
+ command?: string;
11
+ duration_ms?: number;
12
+ };
13
+ }
14
+ /**
15
+ * Standard error response structure
16
+ */
17
+ export interface ErrorResponse {
18
+ success: false;
19
+ error: {
20
+ code: string;
21
+ message: string;
22
+ details?: Record<string, unknown>;
23
+ };
24
+ metadata: {
25
+ timestamp: string;
26
+ command?: string;
27
+ };
28
+ }
29
+ /**
30
+ * Output formatter for both human and JSON formats
31
+ */
32
+ export declare class OutputFormatter {
33
+ private jsonMode;
34
+ private startTime;
35
+ private command?;
36
+ constructor(jsonMode?: boolean, command?: string);
37
+ /**
38
+ * Output a success response
39
+ */
40
+ success<T>(data: T): void;
41
+ /**
42
+ * Output an error response
43
+ */
44
+ error(code: string, message: string, details?: Record<string, unknown>): void;
45
+ /**
46
+ * Create a table for human-readable output
47
+ */
48
+ createTable(head: string[], rows: string[][]): Table.Table;
49
+ /**
50
+ * Output a table
51
+ */
52
+ outputTable(head: string[], rows: string[][]): void;
53
+ /**
54
+ * Create a success response object
55
+ */
56
+ createSuccessResponse<T>(data: T): SuccessResponse<T>;
57
+ /**
58
+ * Create an error response object
59
+ */
60
+ createErrorResponse(code: string, message: string, details?: Record<string, unknown>): ErrorResponse;
61
+ /**
62
+ * Output JSON to stdout
63
+ */
64
+ private outputJSON;
65
+ /**
66
+ * Check if JSON mode is enabled
67
+ */
68
+ isJSONMode(): boolean;
69
+ }