@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
@@ -1,39 +1,47 @@
1
- import Table from 'cli-table3';
2
1
  import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
3
  /**
4
4
  * Output formatter for both human and JSON formats
5
5
  */
6
6
  export class OutputFormatter {
7
+ command;
7
8
  jsonMode;
8
9
  startTime;
9
- command;
10
10
  constructor(jsonMode = false, command) {
11
11
  this.jsonMode = jsonMode;
12
12
  this.startTime = Date.now();
13
13
  this.command = command;
14
14
  }
15
15
  /**
16
- * Output a success response
16
+ * Create an error response object
17
17
  */
18
- success(data) {
19
- if (this.jsonMode) {
20
- this.outputJSON(this.createSuccessResponse(data));
21
- }
22
- // For human-readable output, let the caller format the data
18
+ createErrorResponse(code, message, details) {
19
+ return {
20
+ error: {
21
+ code,
22
+ message,
23
+ ...(details && { details }),
24
+ },
25
+ metadata: {
26
+ timestamp: new Date().toISOString(),
27
+ ...(this.command && { command: this.command }),
28
+ },
29
+ success: false,
30
+ };
23
31
  }
24
32
  /**
25
- * Output an error response
33
+ * Create a success response object
26
34
  */
27
- error(code, message, details) {
28
- if (this.jsonMode) {
29
- this.outputJSON(this.createErrorResponse(code, message, details));
30
- }
31
- else {
32
- console.error(chalk.red(`Error: ${message}`));
33
- if (details) {
34
- console.error(chalk.gray(JSON.stringify(details, null, 2)));
35
- }
36
- }
35
+ createSuccessResponse(data) {
36
+ return {
37
+ data,
38
+ metadata: {
39
+ timestamp: new Date().toISOString(),
40
+ ...(this.command && { command: this.command }),
41
+ duration_ms: Date.now() - this.startTime,
42
+ },
43
+ success: true,
44
+ };
37
45
  }
38
46
  /**
39
47
  * Create a table for human-readable output
@@ -42,8 +50,8 @@ export class OutputFormatter {
42
50
  const table = new Table({
43
51
  head: head.map((h) => chalk.cyan(h)),
44
52
  style: {
45
- head: [],
46
53
  border: ['gray'],
54
+ head: [],
47
55
  },
48
56
  });
49
57
  for (const row of rows) {
@@ -51,13 +59,33 @@ export class OutputFormatter {
51
59
  }
52
60
  return table;
53
61
  }
62
+ /**
63
+ * Output an error response
64
+ */
65
+ error(code, message, details) {
66
+ if (this.jsonMode) {
67
+ this.outputJSON(this.createErrorResponse(code, message, details));
68
+ }
69
+ else {
70
+ console.error(chalk.red(`Error: ${message}`));
71
+ if (details) {
72
+ console.error(chalk.gray(JSON.stringify(details, null, 2)));
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Check if JSON mode is enabled
78
+ */
79
+ isJSONMode() {
80
+ return this.jsonMode;
81
+ }
54
82
  /**
55
83
  * Output a table
56
84
  */
57
85
  outputTable(head, rows) {
58
86
  if (this.jsonMode) {
59
87
  // In JSON mode, output structured data instead of a table
60
- const data = rows.map((row) => Object.fromEntries(head.map((h, i) => [h.toLowerCase().replace(/\s+/g, '_'), row[i]])));
88
+ const data = rows.map((row) => Object.fromEntries(head.map((h, i) => [h.toLowerCase().replaceAll(/\s+/g, '_'), row[i]])));
61
89
  this.success(data);
62
90
  }
63
91
  else {
@@ -66,35 +94,13 @@ export class OutputFormatter {
66
94
  }
67
95
  }
68
96
  /**
69
- * Create a success response object
70
- */
71
- createSuccessResponse(data) {
72
- return {
73
- success: true,
74
- data,
75
- metadata: {
76
- timestamp: new Date().toISOString(),
77
- ...(this.command && { command: this.command }),
78
- duration_ms: Date.now() - this.startTime,
79
- },
80
- };
81
- }
82
- /**
83
- * Create an error response object
97
+ * Output a success response
84
98
  */
85
- createErrorResponse(code, message, details) {
86
- return {
87
- success: false,
88
- error: {
89
- code,
90
- message,
91
- ...(details && { details }),
92
- },
93
- metadata: {
94
- timestamp: new Date().toISOString(),
95
- ...(this.command && { command: this.command }),
96
- },
97
- };
99
+ success(data) {
100
+ if (this.jsonMode) {
101
+ this.outputJSON(this.createSuccessResponse(data));
102
+ }
103
+ // For human-readable output, let the caller format the data
98
104
  }
99
105
  /**
100
106
  * Output JSON to stdout
@@ -102,10 +108,4 @@ export class OutputFormatter {
102
108
  outputJSON(data) {
103
109
  console.log(JSON.stringify(data, null, 2));
104
110
  }
105
- /**
106
- * Check if JSON mode is enabled
107
- */
108
- isJSONMode() {
109
- return this.jsonMode;
110
- }
111
111
  }
@@ -2,10 +2,10 @@
2
2
  * Retry configuration
3
3
  */
4
4
  export interface RetryConfig {
5
- maxAttempts?: number;
5
+ backoffMultiplier?: number;
6
6
  initialDelayMs?: number;
7
+ maxAttempts?: number;
7
8
  maxDelayMs?: number;
8
- backoffMultiplier?: number;
9
9
  retryableErrors?: Array<new (...args: any[]) => Error>;
10
10
  }
11
11
  /**
package/dist/lib/retry.js CHANGED
@@ -4,10 +4,10 @@ import { logger } from './logger.js';
4
4
  * Default retry configuration
5
5
  */
6
6
  const DEFAULT_RETRY_CONFIG = {
7
- maxAttempts: 3,
8
- initialDelayMs: 1000, // 1 second
9
- maxDelayMs: 10000, // 10 seconds
10
7
  backoffMultiplier: 2,
8
+ initialDelayMs: 1000, // 1 second
9
+ maxAttempts: 3,
10
+ maxDelayMs: 10_000, // 10 seconds
11
11
  retryableErrors: [RateLimitError],
12
12
  };
13
13
  /**
@@ -20,7 +20,7 @@ function sleep(ms) {
20
20
  * Calculate delay with exponential backoff and jitter
21
21
  */
22
22
  function calculateDelay(attempt, config) {
23
- const exponentialDelay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt - 1);
23
+ const exponentialDelay = config.initialDelayMs * config.backoffMultiplier ** (attempt - 1);
24
24
  const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
25
25
  // Add jitter (±25% randomization)
26
26
  const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
@@ -40,7 +40,7 @@ export async function retry(fn, config = {}, context) {
40
40
  let lastError;
41
41
  for (let attempt = 1; attempt <= fullConfig.maxAttempts; attempt++) {
42
42
  try {
43
- logger.debug({ attempt, maxAttempts: fullConfig.maxAttempts, context }, 'Attempting operation');
43
+ logger.debug({ attempt, context, maxAttempts: fullConfig.maxAttempts }, 'Attempting operation');
44
44
  return await fn();
45
45
  }
46
46
  catch (error) {
@@ -60,10 +60,10 @@ export async function retry(fn, config = {}, context) {
60
60
  const delayMs = calculateDelay(attempt, fullConfig);
61
61
  logger.info({
62
62
  attempt,
63
- maxAttempts: fullConfig.maxAttempts,
64
- delayMs,
65
63
  context,
64
+ delayMs,
66
65
  error: lastError.message,
66
+ maxAttempts: fullConfig.maxAttempts,
67
67
  }, 'Operation failed, retrying after delay');
68
68
  await sleep(delayMs);
69
69
  }
@@ -75,7 +75,5 @@ export async function retry(fn, config = {}, context) {
75
75
  * Retry wrapper for async functions
76
76
  */
77
77
  export function withRetry(fn, config = {}, context) {
78
- return ((...args) => {
79
- return retry(() => fn(...args), config, context || fn.name);
80
- });
78
+ return ((...args) => retry(() => fn(...args), config, context || fn.name));
81
79
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Risk management calculation utilities
3
+ */
4
+ import type { Market, Position } from './kalshi/types.js';
5
+ export type RiskLevel = 'HIGH' | 'LOW' | 'MEDIUM';
6
+ /**
7
+ * Calculate risk score for a single position (0-100)
8
+ * Higher score = higher risk
9
+ */
10
+ export declare function calculatePositionRiskScore(position: Position, portfolioValue: number, threshold: number): number;
11
+ /**
12
+ * Detect correlated positions by grouping them by event or series
13
+ * Returns map of event/series ticker to positions and total exposure
14
+ */
15
+ export declare function detectCorrelatedPositions(positions: Position[], markets: Market[]): Map<string, {
16
+ exposure: number;
17
+ markets: string[];
18
+ }>;
19
+ /**
20
+ * Suggest maximum safe position size based on portfolio value and risk tolerance
21
+ */
22
+ export declare function suggestMaxPositionSize(balance: number, portfolioValue: number, maxPct: number): number;
23
+ /**
24
+ * Group positions by event, series, or ticker
25
+ */
26
+ export declare function groupPositionsBy(positions: Position[], markets: Market[], groupBy: 'event' | 'series' | 'ticker'): Map<string, {
27
+ positions: Position[];
28
+ totalExposure: number;
29
+ }>;
30
+ /**
31
+ * Calculate overall portfolio risk score (0-100) and categorize risk level
32
+ */
33
+ export declare function calculatePortfolioRiskScore(positions: Position[], portfolioValue: number): {
34
+ level: RiskLevel;
35
+ score: number;
36
+ };
37
+ /**
38
+ * Identify positions that exceed a risk threshold
39
+ */
40
+ export declare function identifyHighRiskPositions(positions: Position[], portfolioValue: number, threshold: number): Position[];
41
+ /**
42
+ * Calculate concentration by grouping
43
+ * Returns sorted array of concentrations
44
+ */
45
+ export declare function calculateConcentrationByGroup(positions: Position[], markets: Market[], portfolioValue: number, groupBy: 'event' | 'series' | 'ticker', threshold: number): Array<{
46
+ alert: boolean;
47
+ exposure: number;
48
+ markets?: string[];
49
+ name: string;
50
+ pct_of_portfolio: number;
51
+ }>;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Risk management calculation utilities
3
+ */
4
+ /**
5
+ * Calculate risk score for a single position (0-100)
6
+ * Higher score = higher risk
7
+ */
8
+ export function calculatePositionRiskScore(position, portfolioValue, threshold) {
9
+ if (portfolioValue <= 0)
10
+ return 0;
11
+ const concentrationPct = (position.market_exposure / portfolioValue) * 100;
12
+ // Risk increases exponentially as concentration approaches/exceeds threshold
13
+ let score = (concentrationPct / threshold) * 50;
14
+ // Additional risk if position is unrealized loss
15
+ if (position.realized_pnl < 0) {
16
+ const lossRatio = Math.abs(position.realized_pnl) / position.total_cost;
17
+ score += lossRatio * 25;
18
+ }
19
+ return Math.min(100, Math.max(0, score));
20
+ }
21
+ /**
22
+ * Detect correlated positions by grouping them by event or series
23
+ * Returns map of event/series ticker to positions and total exposure
24
+ */
25
+ export function detectCorrelatedPositions(positions, markets) {
26
+ const correlations = new Map();
27
+ // Create a map of ticker to market for quick lookup
28
+ const marketMap = new Map(markets.map(m => [m.ticker, m]));
29
+ // Group positions by event ticker
30
+ for (const position of positions) {
31
+ const market = marketMap.get(position.ticker);
32
+ if (!market)
33
+ continue;
34
+ const eventTicker = market.event_ticker;
35
+ const existing = correlations.get(eventTicker) || { exposure: 0, markets: [] };
36
+ existing.markets.push(position.ticker);
37
+ existing.exposure += position.market_exposure;
38
+ correlations.set(eventTicker, existing);
39
+ }
40
+ // Filter to only events with multiple positions
41
+ const filtered = new Map();
42
+ for (const [eventTicker, data] of correlations) {
43
+ if (data.markets.length > 1) {
44
+ filtered.set(eventTicker, data);
45
+ }
46
+ }
47
+ return filtered;
48
+ }
49
+ /**
50
+ * Suggest maximum safe position size based on portfolio value and risk tolerance
51
+ */
52
+ export function suggestMaxPositionSize(balance, portfolioValue, maxPct) {
53
+ if (portfolioValue <= 0)
54
+ return balance;
55
+ return (portfolioValue * maxPct) / 100;
56
+ }
57
+ /**
58
+ * Group positions by event, series, or ticker
59
+ */
60
+ export function groupPositionsBy(positions, markets, groupBy) {
61
+ const grouped = new Map();
62
+ // Create a map of ticker to market for quick lookup
63
+ const marketMap = new Map(markets.map(m => [m.ticker, m]));
64
+ for (const position of positions) {
65
+ let key;
66
+ if (groupBy === 'ticker') {
67
+ key = position.ticker;
68
+ }
69
+ else {
70
+ const market = marketMap.get(position.ticker);
71
+ if (!market)
72
+ continue;
73
+ key = groupBy === 'event' ? market.event_ticker : market.event_ticker;
74
+ // series - extract from event ticker (format: SERIES-SPECIFIC or just use event_ticker)
75
+ // For now, use event_ticker as series identifier
76
+ }
77
+ const existing = grouped.get(key) || { positions: [], totalExposure: 0 };
78
+ existing.positions.push(position);
79
+ existing.totalExposure += position.market_exposure;
80
+ grouped.set(key, existing);
81
+ }
82
+ return grouped;
83
+ }
84
+ /**
85
+ * Calculate overall portfolio risk score (0-100) and categorize risk level
86
+ */
87
+ export function calculatePortfolioRiskScore(positions, portfolioValue) {
88
+ if (positions.length === 0 || portfolioValue <= 0) {
89
+ return { level: 'LOW', score: 0 };
90
+ }
91
+ // Calculate exposure ratio
92
+ const totalExposure = positions.reduce((sum, pos) => sum + pos.market_exposure, 0);
93
+ const exposureRatio = totalExposure / portfolioValue;
94
+ // Calculate concentration (largest position as % of portfolio)
95
+ const largestExposure = Math.max(...positions.map(pos => pos.market_exposure));
96
+ const largestConcentration = (largestExposure / portfolioValue) * 100;
97
+ // Calculate unrealized loss ratio
98
+ const totalUnrealizedLoss = positions
99
+ .filter(pos => pos.realized_pnl < 0)
100
+ .reduce((sum, pos) => sum + Math.abs(pos.realized_pnl), 0);
101
+ const lossRatio = totalExposure > 0 ? totalUnrealizedLoss / totalExposure : 0;
102
+ // Weighted risk score
103
+ let score = 0;
104
+ // Exposure contributes up to 40 points (0.5 exposure = 20 points, 1.0 = 40 points)
105
+ score += Math.min(40, exposureRatio * 40);
106
+ // Concentration contributes up to 30 points (20% = 15 points, 40% = 30 points)
107
+ score += Math.min(30, (largestConcentration / 40) * 30);
108
+ // Loss ratio contributes up to 30 points
109
+ score += Math.min(30, lossRatio * 100 * 0.3);
110
+ // Categorize risk level
111
+ let level;
112
+ if (score < 30)
113
+ level = 'LOW';
114
+ else if (score < 60)
115
+ level = 'MEDIUM';
116
+ else
117
+ level = 'HIGH';
118
+ return { level, score: Math.round(score) };
119
+ }
120
+ /**
121
+ * Identify positions that exceed a risk threshold
122
+ */
123
+ export function identifyHighRiskPositions(positions, portfolioValue, threshold) {
124
+ if (portfolioValue <= 0)
125
+ return [];
126
+ return positions.filter(pos => {
127
+ const concentrationPct = (pos.market_exposure / portfolioValue) * 100;
128
+ return concentrationPct > threshold;
129
+ });
130
+ }
131
+ /**
132
+ * Calculate concentration by grouping
133
+ * Returns sorted array of concentrations
134
+ */
135
+ // eslint-disable-next-line max-params
136
+ export function calculateConcentrationByGroup(positions, markets, portfolioValue, groupBy, threshold) {
137
+ if (portfolioValue <= 0)
138
+ return [];
139
+ const grouped = groupPositionsBy(positions, markets, groupBy);
140
+ const concentrations = [];
141
+ for (const [name, data] of grouped) {
142
+ const pctOfPortfolio = (data.totalExposure / portfolioValue) * 100;
143
+ concentrations.push({
144
+ alert: pctOfPortfolio > threshold,
145
+ exposure: data.totalExposure,
146
+ markets: groupBy === 'ticker' ? undefined : data.positions.map(p => p.ticker),
147
+ name,
148
+ pct_of_portfolio: pctOfPortfolio,
149
+ });
150
+ }
151
+ // Sort by exposure descending
152
+ return concentrations.sort((a, b) => b.exposure - a.exposure);
153
+ }
@@ -14,9 +14,9 @@ export function sanitizeString(input, maxLength = 1000) {
14
14
  });
15
15
  }
16
16
  // Remove null bytes (can cause issues in some systems)
17
- const sanitized = input.replace(/\0/g, '');
17
+ const sanitized = input.replaceAll('\0', '');
18
18
  // Remove control characters except newline, carriage return, and tab
19
- return sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
19
+ return sanitized.replaceAll(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
20
20
  }
21
21
  /**
22
22
  * Sanitize a ticker symbol
@@ -26,7 +26,7 @@ export function sanitizeTicker(ticker) {
26
26
  // Ensure uppercase
27
27
  const uppercased = sanitized.toUpperCase();
28
28
  // Remove any characters that aren't alphanumeric or hyphens
29
- const cleaned = uppercased.replace(/[^A-Z0-9-]/g, '');
29
+ const cleaned = uppercased.replaceAll(/[^A-Z0-9-]/g, '');
30
30
  if (cleaned !== uppercased) {
31
31
  throw new ValidationError('Ticker contains invalid characters', {
32
32
  original: ticker,
@@ -75,14 +75,14 @@ export function sanitizeNumber(input, min, max) {
75
75
  }
76
76
  if (min !== undefined && num < min) {
77
77
  throw new ValidationError(`Number is below minimum value of ${min}`, {
78
- value: num,
79
78
  min,
79
+ value: num,
80
80
  });
81
81
  }
82
82
  if (max !== undefined && num > max) {
83
83
  throw new ValidationError(`Number exceeds maximum value of ${max}`, {
84
- value: num,
85
84
  max,
85
+ value: num,
86
86
  });
87
87
  }
88
88
  return num;
@@ -113,12 +113,12 @@ export function sanitizeBoolean(input) {
113
113
  */
114
114
  export function redactSensitive(input) {
115
115
  // Redact potential API keys (long alphanumeric strings)
116
- let redacted = input.replace(/[a-zA-Z0-9]{32,}/g, '[REDACTED_KEY]');
116
+ let redacted = input.replaceAll(/[a-zA-Z0-9]{32,}/g, '[REDACTED_KEY]');
117
117
  // Redact email addresses
118
- redacted = redacted.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]');
118
+ redacted = redacted.replaceAll(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]');
119
119
  // Redact potential private keys (PEM format)
120
- redacted = redacted.replace(/-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/g, '[REDACTED_KEY]');
120
+ redacted = redacted.replaceAll(/-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/g, '[REDACTED_KEY]');
121
121
  // Redact file paths that might contain sensitive info
122
- redacted = redacted.replace(/\/[a-zA-Z0-9_\-./]+\/\.kalshitools\/[a-zA-Z0-9_\-./]+/g, '[REDACTED_PATH]');
122
+ redacted = redacted.replaceAll(/\/[a-zA-Z0-9_\-./]+\/\.kalshitools\/[a-zA-Z0-9_\-./]+/g, '[REDACTED_PATH]');
123
123
  return redacted;
124
124
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Market scanning and opportunity detection utilities
3
+ */
4
+ import type { Market, OrderBook } from './kalshi/types.js';
5
+ export type OpportunityType = 'balanced' | 'high_liquidity' | 'high_volume' | 'wide_spread';
6
+ /**
7
+ * Calculate total liquidity from an orderbook
8
+ */
9
+ export declare function calculateOrderbookLiquidity(orderbook: OrderBook): {
10
+ noTotal: number;
11
+ total: number;
12
+ yesTotal: number;
13
+ };
14
+ /**
15
+ * Calculate bid-ask spread from an orderbook
16
+ *
17
+ * In Kalshi orderbooks:
18
+ * - Lower prices in the yes array are bids (buyers)
19
+ * - Higher prices in the yes array are asks (sellers)
20
+ * - Spread = ask - bid
21
+ */
22
+ export declare function calculateSpread(orderbook: OrderBook): null | {
23
+ spreadCents: number;
24
+ spreadPct: number;
25
+ yesAsk: number;
26
+ yesBid: number;
27
+ };
28
+ /**
29
+ * Score a market based on liquidity, spread, and volume
30
+ * Returns score from 0-100
31
+ */
32
+ export declare function scoreMarket(market: Market, orderbook: OrderBook, weights?: {
33
+ liquidity: number;
34
+ spread: number;
35
+ volume: number;
36
+ }): number;
37
+ /**
38
+ * Classify the type of opportunity a market presents
39
+ */
40
+ export declare function classifyOpportunity(spread: number, liquidity: number, volume: number): OpportunityType;
41
+ /**
42
+ * Generate human-readable reason for opportunity
43
+ */
44
+ export declare function generateOpportunityReason(type: OpportunityType, spread: number, liquidity: number, volume: number): string;
45
+ /**
46
+ * Filter markets based on criteria
47
+ */
48
+ export declare function filterMarketsByCriteria<T extends {
49
+ liquidity: number;
50
+ market: Market;
51
+ spread: null | number;
52
+ volume: number;
53
+ }>(markets: T[], criteria: {
54
+ maxSpread?: number;
55
+ minLiquidity?: number;
56
+ minSpread?: number;
57
+ minVolume?: number;
58
+ }): T[];