@gaberoo/kalshitools 1.0.2 → 1.1.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 (61) hide show
  1. package/README.md +328 -27
  2. package/dist/commands/config/init.js +4 -4
  3. package/dist/commands/config/show.js +5 -5
  4. package/dist/commands/markets/list.d.ts +5 -1
  5. package/dist/commands/markets/list.js +28 -8
  6. package/dist/commands/markets/orderbook.d.ts +13 -0
  7. package/dist/commands/markets/orderbook.js +83 -0
  8. package/dist/commands/markets/scan.d.ts +18 -0
  9. package/dist/commands/markets/scan.js +237 -0
  10. package/dist/commands/markets/show.d.ts +3 -3
  11. package/dist/commands/markets/show.js +7 -7
  12. package/dist/commands/orders/cancel.d.ts +3 -3
  13. package/dist/commands/orders/cancel.js +7 -7
  14. package/dist/commands/orders/create.d.ts +5 -5
  15. package/dist/commands/orders/create.js +33 -33
  16. package/dist/commands/orders/list.d.ts +1 -1
  17. package/dist/commands/orders/list.js +9 -9
  18. package/dist/commands/portfolio/analytics.d.ts +12 -0
  19. package/dist/commands/portfolio/analytics.js +192 -0
  20. package/dist/commands/portfolio/fills.d.ts +1 -1
  21. package/dist/commands/portfolio/fills.js +7 -7
  22. package/dist/commands/portfolio/history.d.ts +14 -0
  23. package/dist/commands/portfolio/history.js +245 -0
  24. package/dist/commands/portfolio/positions.d.ts +1 -0
  25. package/dist/commands/portfolio/positions.js +11 -2
  26. package/dist/commands/portfolio/risk.d.ts +11 -0
  27. package/dist/commands/portfolio/risk.js +206 -0
  28. package/dist/lib/analytics.d.ts +64 -0
  29. package/dist/lib/analytics.js +236 -0
  30. package/dist/lib/base-command.d.ts +2 -2
  31. package/dist/lib/base-command.js +8 -8
  32. package/dist/lib/config/manager.d.ts +25 -25
  33. package/dist/lib/config/manager.js +51 -51
  34. package/dist/lib/config/schema.d.ts +11 -11
  35. package/dist/lib/config/schema.js +6 -6
  36. package/dist/lib/errors/base.d.ts +10 -10
  37. package/dist/lib/errors/base.js +7 -7
  38. package/dist/lib/kalshi/auth.d.ts +4 -4
  39. package/dist/lib/kalshi/auth.js +24 -24
  40. package/dist/lib/kalshi/client.d.ts +35 -35
  41. package/dist/lib/kalshi/client.js +93 -91
  42. package/dist/lib/kalshi/index.d.ts +1 -1
  43. package/dist/lib/kalshi/index.js +1 -1
  44. package/dist/lib/kalshi/types.d.ts +53 -53
  45. package/dist/lib/logger.js +3 -3
  46. package/dist/lib/output/formatter.d.ts +20 -20
  47. package/dist/lib/output/formatter.js +55 -55
  48. package/dist/lib/retry.d.ts +2 -2
  49. package/dist/lib/retry.js +8 -10
  50. package/dist/lib/risk.d.ts +51 -0
  51. package/dist/lib/risk.js +153 -0
  52. package/dist/lib/sanitize.js +9 -9
  53. package/dist/lib/scanner.d.ts +58 -0
  54. package/dist/lib/scanner.js +160 -0
  55. package/dist/lib/shutdown.d.ts +4 -4
  56. package/dist/lib/shutdown.js +7 -7
  57. package/dist/lib/validation.d.ts +5 -5
  58. package/dist/lib/validation.js +14 -20
  59. package/docs/TRADING_STRATEGIES.md +538 -0
  60. package/oclif.manifest.json +559 -170
  61. package/package.json +1 -1
@@ -25,19 +25,19 @@ const WRITE_LIMITER = new Bottleneck({
25
25
  * Kalshi API client
26
26
  */
27
27
  export class KalshiClient {
28
- axios;
29
28
  auth;
29
+ axios;
30
30
  baseUrl;
31
31
  constructor(baseUrl, keyId, privateKey) {
32
32
  this.baseUrl = baseUrl;
33
33
  this.auth = new KalshiAuth(keyId, privateKey);
34
34
  this.axios = axios.create({
35
35
  baseURL: baseUrl,
36
- timeout: 30000, // 30 second default timeout
37
36
  headers: {
38
- 'Content-Type': 'application/json',
39
37
  'Accept': 'application/json',
38
+ 'Content-Type': 'application/json',
40
39
  },
40
+ timeout: 30_000, // 30 second default timeout
41
41
  });
42
42
  // Add request interceptor for authentication
43
43
  this.axios.interceptors.request.use((config) => {
@@ -61,8 +61,8 @@ export class KalshiClient {
61
61
  if (axios.isAxiosError(error)) {
62
62
  if (error.code === 'ECONNABORTED') {
63
63
  throw new TimeoutError('Request timed out', {
64
- url: error.config?.url,
65
64
  timeout: error.config?.timeout,
65
+ url: error.config?.url,
66
66
  });
67
67
  }
68
68
  if (!error.response) {
@@ -70,73 +70,36 @@ export class KalshiClient {
70
70
  message: error.message,
71
71
  });
72
72
  }
73
- const status = error.response.status;
73
+ const { status } = error.response;
74
74
  const data = error.response.data;
75
75
  if (status === 429) {
76
76
  throw new RateLimitError('Rate limit exceeded', {
77
- status,
78
77
  retryAfter: error.response.headers['retry-after'],
78
+ status,
79
79
  });
80
80
  }
81
81
  throw new APIError(data.error?.message || `API error: ${status}`, {
82
- status,
83
82
  code: data.error?.code,
84
83
  details: data.error?.details,
84
+ status,
85
85
  });
86
86
  }
87
87
  throw error;
88
88
  });
89
89
  }
90
90
  /**
91
- * Make a rate-limited GET request
92
- */
93
- async get(path, config) {
94
- return READ_LIMITER.schedule(async () => {
95
- const response = await this.axios.get(path, config);
96
- if (response.data.error) {
97
- throw new APIError(response.data.error.message, {
98
- code: response.data.error.code,
99
- details: response.data.error.details,
100
- });
101
- }
102
- // Kalshi API doesn't always wrap in a data envelope
103
- // Return response.data.data if it exists, otherwise response.data
104
- return (response.data.data ?? response.data);
105
- });
106
- }
107
- /**
108
- * Make a rate-limited POST request
91
+ * Cancel an order
109
92
  */
110
- async post(path, data, config) {
111
- return WRITE_LIMITER.schedule(async () => {
112
- const response = await this.axios.post(path, data, config);
113
- if (response.data.error) {
114
- throw new APIError(response.data.error.message, {
115
- code: response.data.error.code,
116
- details: response.data.error.details,
117
- });
118
- }
119
- // Kalshi API doesn't always wrap in a data envelope
120
- // Return response.data.data if it exists, otherwise response.data
121
- return (response.data.data ?? response.data);
122
- });
93
+ async cancelOrder(orderId) {
94
+ logger.info({ orderId }, 'Canceling order');
95
+ return this.delete(`/portfolio/orders/${orderId}`);
123
96
  }
124
97
  /**
125
- * Make a rate-limited DELETE request
98
+ * Create an order
126
99
  */
127
- async delete(path, config) {
128
- return WRITE_LIMITER.schedule(async () => {
129
- const response = await this.axios.delete(path, config);
130
- if (response.data.error) {
131
- throw new APIError(response.data.error.message, {
132
- code: response.data.error.code,
133
- details: response.data.error.details,
134
- });
135
- }
136
- // Kalshi API doesn't always wrap in a data envelope
137
- // Return response.data.data if it exists, otherwise response.data
138
- return (response.data.data ?? response.data);
139
- });
100
+ async createOrder(request) {
101
+ logger.info({ action: request.action, side: request.side, ticker: request.ticker }, 'Creating order');
102
+ return this.post('/portfolio/orders', request);
140
103
  }
141
104
  /**
142
105
  * Get account balance
@@ -146,43 +109,33 @@ export class KalshiClient {
146
109
  return this.get('/portfolio/balance');
147
110
  }
148
111
  /**
149
- * Get portfolio positions
112
+ * Get the base URL
150
113
  */
151
- async getPositions(params) {
152
- logger.info({ params }, 'Fetching portfolio positions');
153
- const response = await this.get('/portfolio/positions', { params });
154
- // Kalshi returns { market_positions: [...] }, not just the array
155
- return response.market_positions || [];
114
+ getBaseUrl() {
115
+ return this.baseUrl;
156
116
  }
157
117
  /**
158
- * Get markets
118
+ * Get fills (executed trades)
159
119
  */
160
- async getMarkets(query) {
161
- logger.info({ query }, 'Fetching markets');
162
- return this.get('/markets', { params: query });
120
+ async getFills(query) {
121
+ logger.info({ query }, 'Fetching fills');
122
+ return this.get('/portfolio/fills', { params: query });
163
123
  }
164
124
  /**
165
125
  * Get single market by ticker
166
126
  */
167
127
  async getMarket(ticker) {
168
128
  logger.info({ ticker }, 'Fetching market details');
169
- return this.get(`/markets/${ticker}`);
170
- }
171
- /**
172
- * Get order book for a market
173
- */
174
- async getOrderBook(ticker, depth) {
175
- logger.info({ ticker, depth }, 'Fetching order book');
176
- return this.get(`/markets/${ticker}/orderbook`, {
177
- params: depth ? { depth } : undefined,
178
- });
129
+ const response = await this.get(`/markets/${ticker}`);
130
+ // Kalshi returns { market: {...} }
131
+ return response.market;
179
132
  }
180
133
  /**
181
- * Get user orders
134
+ * Get markets
182
135
  */
183
- async getOrders(query) {
184
- logger.info({ query }, 'Fetching orders');
185
- return this.get('/portfolio/orders', { params: query });
136
+ async getMarkets(query) {
137
+ logger.info({ query }, 'Fetching markets');
138
+ return this.get('/markets', { params: query });
186
139
  }
187
140
  /**
188
141
  * Get single order by ID
@@ -192,25 +145,29 @@ export class KalshiClient {
192
145
  return this.get(`/portfolio/orders/${orderId}`);
193
146
  }
194
147
  /**
195
- * Create an order
148
+ * Get order book for a market
196
149
  */
197
- async createOrder(request) {
198
- logger.info({ ticker: request.ticker, side: request.side, action: request.action }, 'Creating order');
199
- return this.post('/portfolio/orders', request);
150
+ async getOrderBook(ticker, depth) {
151
+ logger.info({ depth, ticker }, 'Fetching order book');
152
+ return this.get(`/markets/${ticker}/orderbook`, {
153
+ params: depth ? { depth } : undefined,
154
+ });
200
155
  }
201
156
  /**
202
- * Cancel an order
157
+ * Get user orders
203
158
  */
204
- async cancelOrder(orderId) {
205
- logger.info({ orderId }, 'Canceling order');
206
- return this.delete(`/portfolio/orders/${orderId}`);
159
+ async getOrders(query) {
160
+ logger.info({ query }, 'Fetching orders');
161
+ return this.get('/portfolio/orders', { params: query });
207
162
  }
208
163
  /**
209
- * Get fills (executed trades)
164
+ * Get portfolio positions
210
165
  */
211
- async getFills(query) {
212
- logger.info({ query }, 'Fetching fills');
213
- return this.get('/portfolio/fills', { params: query });
166
+ async getPositions(params) {
167
+ logger.info({ params }, 'Fetching portfolio positions');
168
+ const response = await this.get('/portfolio/positions', { params });
169
+ // Kalshi returns { market_positions: [...] }, not just the array
170
+ return response.market_positions || [];
214
171
  }
215
172
  /**
216
173
  * Test connection to API
@@ -227,10 +184,55 @@ export class KalshiClient {
227
184
  }
228
185
  }
229
186
  /**
230
- * Get the base URL
187
+ * Make a rate-limited DELETE request
231
188
  */
232
- getBaseUrl() {
233
- return this.baseUrl;
189
+ async delete(path, config) {
190
+ return WRITE_LIMITER.schedule(async () => {
191
+ const response = await this.axios.delete(path, config);
192
+ if (response.data.error) {
193
+ throw new APIError(response.data.error.message, {
194
+ code: response.data.error.code,
195
+ details: response.data.error.details,
196
+ });
197
+ }
198
+ // Kalshi API doesn't always wrap in a data envelope
199
+ // Return response.data.data if it exists, otherwise response.data
200
+ return (response.data.data ?? response.data);
201
+ });
202
+ }
203
+ /**
204
+ * Make a rate-limited GET request
205
+ */
206
+ async get(path, config) {
207
+ return READ_LIMITER.schedule(async () => {
208
+ const response = await this.axios.get(path, config);
209
+ if (response.data.error) {
210
+ throw new APIError(response.data.error.message, {
211
+ code: response.data.error.code,
212
+ details: response.data.error.details,
213
+ });
214
+ }
215
+ // Kalshi API doesn't always wrap in a data envelope
216
+ // Return response.data.data if it exists, otherwise response.data
217
+ return (response.data.data ?? response.data);
218
+ });
219
+ }
220
+ /**
221
+ * Make a rate-limited POST request
222
+ */
223
+ async post(path, data, config) {
224
+ return WRITE_LIMITER.schedule(async () => {
225
+ const response = await this.axios.post(path, data, config);
226
+ if (response.data.error) {
227
+ throw new APIError(response.data.error.message, {
228
+ code: response.data.error.code,
229
+ details: response.data.error.details,
230
+ });
231
+ }
232
+ // Kalshi API doesn't always wrap in a data envelope
233
+ // Return response.data.data if it exists, otherwise response.data
234
+ return (response.data.data ?? response.data);
235
+ });
234
236
  }
235
237
  }
236
238
  /**
@@ -1,7 +1,7 @@
1
1
  import { KalshiClient } from './client.js';
2
+ export * from './auth.js';
2
3
  export * from './client.js';
3
4
  export * from './types.js';
4
- export * from './auth.js';
5
5
  /**
6
6
  * Create a Kalshi client from the current configuration
7
7
  */
@@ -1,9 +1,9 @@
1
1
  import { getConfig } from '../config/manager.js';
2
2
  import { logger } from '../logger.js';
3
3
  import { createKalshiClient } from './client.js';
4
+ export * from './auth.js';
4
5
  export * from './client.js';
5
6
  export * from './types.js';
6
- export * from './auth.js';
7
7
  /**
8
8
  * Create a Kalshi client from the current configuration
9
9
  */
@@ -8,8 +8,8 @@ export interface KalshiResponse<T> {
8
8
  data?: T;
9
9
  error?: {
10
10
  code: string;
11
- message: string;
12
11
  details?: Record<string, unknown>;
12
+ message: string;
13
13
  };
14
14
  }
15
15
  /**
@@ -23,113 +23,113 @@ export interface Balance {
23
23
  * Market data
24
24
  */
25
25
  export interface Market {
26
- ticker: string;
26
+ close_time: string;
27
27
  event_ticker: string;
28
+ expiration_time: string;
29
+ last_price?: number;
30
+ liquidity?: number;
28
31
  market_type: string;
29
- title: string;
30
- subtitle: string;
32
+ no_ask?: number;
33
+ no_bid?: number;
31
34
  open_time: string;
32
- close_time: string;
33
- expiration_time: string;
34
- status: 'active' | 'closed' | 'settled' | 'finalized';
35
- result?: 'yes' | 'no';
35
+ result?: 'no' | 'yes';
36
+ status: 'active' | 'closed' | 'finalized' | 'settled';
37
+ subtitle: string;
38
+ ticker: string;
39
+ title: string;
36
40
  volume?: number;
37
41
  volume_24h?: number;
38
- liquidity?: number;
39
- yes_bid?: number;
40
42
  yes_ask?: number;
41
- no_bid?: number;
42
- no_ask?: number;
43
- last_price?: number;
43
+ yes_bid?: number;
44
44
  }
45
45
  /**
46
46
  * Position data
47
47
  */
48
48
  export interface Position {
49
- ticker: string;
49
+ fees_paid: number;
50
+ market_exposure: number;
50
51
  market_ticker: string;
51
52
  position: number;
52
- market_exposure: number;
53
- total_cost: number;
54
- fees_paid: number;
55
53
  realized_pnl: number;
54
+ ticker: string;
55
+ total_cost: number;
56
56
  }
57
57
  /**
58
58
  * Order data
59
59
  */
60
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
61
  action: 'buy' | 'sell';
70
- type: 'market' | 'limit';
62
+ avg_fill_price?: number;
63
+ count: number;
71
64
  created_time: string;
72
- updated_time: string;
73
65
  expiration_time?: string;
74
- remaining_count: number;
75
66
  filled_count: number;
76
- avg_fill_price?: number;
67
+ no_price?: number;
68
+ order_id: string;
69
+ remaining_count: number;
70
+ side: 'no' | 'yes';
71
+ status: 'canceled' | 'executed' | 'expired' | 'pending' | 'resting';
72
+ ticker: string;
73
+ type: 'limit' | 'market';
74
+ updated_time: string;
75
+ user_id: string;
76
+ yes_price?: number;
77
77
  }
78
78
  /**
79
79
  * Fill (executed trade) data
80
80
  */
81
81
  export interface Fill {
82
- fill_id: string;
83
- order_id: string;
84
- ticker: string;
85
- side: 'yes' | 'no';
86
82
  action: 'buy' | 'sell';
87
83
  count: number;
88
- price: number;
89
84
  created_time: string;
85
+ fill_id: string;
90
86
  is_taker: boolean;
87
+ order_id: string;
88
+ price: number;
89
+ side: 'no' | 'yes';
90
+ ticker: string;
91
91
  trade_id: string;
92
92
  }
93
93
  /**
94
94
  * Order book level
95
95
  */
96
96
  export interface OrderBookLevel {
97
- price: number;
98
97
  count: number;
98
+ price: number;
99
99
  }
100
100
  /**
101
101
  * Order book data
102
102
  */
103
103
  export interface OrderBook {
104
+ no: OrderBookLevel[];
104
105
  ticker: string;
105
106
  yes: OrderBookLevel[];
106
- no: OrderBookLevel[];
107
107
  }
108
108
  /**
109
109
  * Create order request
110
110
  */
111
111
  export interface CreateOrderRequest {
112
- ticker: string;
113
- client_order_id?: string;
114
- side: 'yes' | 'no';
115
112
  action: 'buy' | 'sell';
113
+ buy_max_cost?: number;
114
+ client_order_id?: string;
116
115
  count: number;
117
- type: 'market' | 'limit';
118
- yes_price?: number;
119
- no_price?: number;
120
116
  expiration_ts?: number;
117
+ no_price?: number;
121
118
  sell_position_floor?: number;
122
- buy_max_cost?: number;
119
+ side: 'no' | 'yes';
120
+ ticker: string;
121
+ type: 'limit' | 'market';
122
+ yes_price?: number;
123
123
  }
124
124
  /**
125
125
  * Markets query parameters
126
126
  */
127
127
  export interface MarketsQuery {
128
- limit?: number;
129
128
  cursor?: string;
130
129
  event_ticker?: string;
130
+ limit?: number;
131
131
  series_ticker?: string;
132
- status?: 'active' | 'closed' | 'settled' | 'finalized';
132
+ status?: 'active' | 'closed' | 'finalized' | 'settled';
133
133
  tickers?: string;
134
134
  with_nested_markets?: boolean;
135
135
  }
@@ -137,19 +137,19 @@ export interface MarketsQuery {
137
137
  * Orders query parameters
138
138
  */
139
139
  export interface OrdersQuery {
140
- ticker?: string;
141
- status?: 'pending' | 'resting' | 'canceled' | 'executed' | 'expired';
142
- limit?: number;
143
140
  cursor?: string;
141
+ limit?: number;
142
+ status?: 'canceled' | 'executed' | 'expired' | 'pending' | 'resting';
143
+ ticker?: string;
144
144
  }
145
145
  /**
146
146
  * Fills query parameters
147
147
  */
148
148
  export interface FillsQuery {
149
- ticker?: string;
150
- order_id?: string;
151
- min_ts?: number;
152
- max_ts?: number;
153
- limit?: number;
154
149
  cursor?: string;
150
+ limit?: number;
151
+ max_ts?: number;
152
+ min_ts?: number;
153
+ order_id?: string;
154
+ ticker?: string;
155
155
  }
@@ -8,6 +8,7 @@ export function createLogger(options = {}) {
8
8
  level,
9
9
  // Redact sensitive fields
10
10
  redact: {
11
+ censor: '[REDACTED]',
11
12
  paths: [
12
13
  'keyId',
13
14
  'privateKey',
@@ -22,17 +23,16 @@ export function createLogger(options = {}) {
22
23
  '*.password',
23
24
  '*.token',
24
25
  ],
25
- censor: '[REDACTED]',
26
26
  },
27
27
  };
28
28
  if (pretty) {
29
29
  return pino(pinoOptions, pino.transport({
30
- target: 'pino-pretty',
31
30
  options: {
32
31
  colorize: true,
33
- translateTime: 'HH:MM:ss',
34
32
  ignore: 'pid,hostname',
33
+ translateTime: 'HH:MM:ss',
35
34
  },
35
+ target: 'pino-pretty',
36
36
  }));
37
37
  }
38
38
  return pino(pinoOptions);
@@ -3,67 +3,67 @@ import Table from 'cli-table3';
3
3
  * Standard success response structure
4
4
  */
5
5
  export interface SuccessResponse<T = unknown> {
6
- success: true;
7
6
  data: T;
8
7
  metadata: {
9
- timestamp: string;
10
8
  command?: string;
11
9
  duration_ms?: number;
10
+ timestamp: string;
12
11
  };
12
+ success: true;
13
13
  }
14
14
  /**
15
15
  * Standard error response structure
16
16
  */
17
17
  export interface ErrorResponse {
18
- success: false;
19
18
  error: {
20
19
  code: string;
21
- message: string;
22
20
  details?: Record<string, unknown>;
21
+ message: string;
23
22
  };
24
23
  metadata: {
25
- timestamp: string;
26
24
  command?: string;
25
+ timestamp: string;
27
26
  };
27
+ success: false;
28
28
  }
29
29
  /**
30
30
  * Output formatter for both human and JSON formats
31
31
  */
32
32
  export declare class OutputFormatter {
33
+ private command?;
33
34
  private jsonMode;
34
35
  private startTime;
35
- private command?;
36
36
  constructor(jsonMode?: boolean, command?: string);
37
37
  /**
38
- * Output a success response
38
+ * Create an error response object
39
39
  */
40
- success<T>(data: T): void;
40
+ createErrorResponse(code: string, message: string, details?: Record<string, unknown>): ErrorResponse;
41
41
  /**
42
- * Output an error response
42
+ * Create a success response object
43
43
  */
44
- error(code: string, message: string, details?: Record<string, unknown>): void;
44
+ createSuccessResponse<T>(data: T): SuccessResponse<T>;
45
45
  /**
46
46
  * Create a table for human-readable output
47
47
  */
48
48
  createTable(head: string[], rows: string[][]): Table.Table;
49
49
  /**
50
- * Output a table
50
+ * Output an error response
51
51
  */
52
- outputTable(head: string[], rows: string[][]): void;
52
+ error(code: string, message: string, details?: Record<string, unknown>): void;
53
53
  /**
54
- * Create a success response object
54
+ * Check if JSON mode is enabled
55
55
  */
56
- createSuccessResponse<T>(data: T): SuccessResponse<T>;
56
+ isJSONMode(): boolean;
57
57
  /**
58
- * Create an error response object
58
+ * Output a table
59
59
  */
60
- createErrorResponse(code: string, message: string, details?: Record<string, unknown>): ErrorResponse;
60
+ outputTable(head: string[], rows: string[][]): void;
61
61
  /**
62
- * Output JSON to stdout
62
+ * Output a success response
63
63
  */
64
- private outputJSON;
64
+ success<T>(data: T): void;
65
65
  /**
66
- * Check if JSON mode is enabled
66
+ * Output JSON to stdout
67
67
  */
68
- isJSONMode(): boolean;
68
+ private outputJSON;
69
69
  }