@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.
- package/README.md +328 -27
- package/dist/commands/config/init.js +4 -4
- package/dist/commands/config/show.js +5 -5
- package/dist/commands/markets/list.d.ts +5 -1
- package/dist/commands/markets/list.js +28 -8
- package/dist/commands/markets/orderbook.d.ts +13 -0
- package/dist/commands/markets/orderbook.js +83 -0
- package/dist/commands/markets/scan.d.ts +18 -0
- package/dist/commands/markets/scan.js +237 -0
- package/dist/commands/markets/show.d.ts +3 -3
- package/dist/commands/markets/show.js +7 -7
- package/dist/commands/orders/cancel.d.ts +3 -3
- package/dist/commands/orders/cancel.js +7 -7
- package/dist/commands/orders/create.d.ts +5 -5
- package/dist/commands/orders/create.js +33 -33
- package/dist/commands/orders/list.d.ts +1 -1
- package/dist/commands/orders/list.js +9 -9
- package/dist/commands/portfolio/analytics.d.ts +12 -0
- package/dist/commands/portfolio/analytics.js +192 -0
- package/dist/commands/portfolio/fills.d.ts +1 -1
- package/dist/commands/portfolio/fills.js +7 -7
- package/dist/commands/portfolio/history.d.ts +14 -0
- package/dist/commands/portfolio/history.js +245 -0
- package/dist/commands/portfolio/positions.d.ts +1 -0
- package/dist/commands/portfolio/positions.js +11 -2
- package/dist/commands/portfolio/risk.d.ts +11 -0
- package/dist/commands/portfolio/risk.js +206 -0
- package/dist/lib/analytics.d.ts +64 -0
- package/dist/lib/analytics.js +236 -0
- package/dist/lib/base-command.d.ts +2 -2
- package/dist/lib/base-command.js +8 -8
- package/dist/lib/config/manager.d.ts +25 -25
- package/dist/lib/config/manager.js +51 -51
- package/dist/lib/config/schema.d.ts +11 -11
- package/dist/lib/config/schema.js +6 -6
- package/dist/lib/errors/base.d.ts +10 -10
- package/dist/lib/errors/base.js +7 -7
- package/dist/lib/kalshi/auth.d.ts +4 -4
- package/dist/lib/kalshi/auth.js +24 -24
- package/dist/lib/kalshi/client.d.ts +35 -35
- package/dist/lib/kalshi/client.js +93 -91
- package/dist/lib/kalshi/index.d.ts +1 -1
- package/dist/lib/kalshi/index.js +1 -1
- package/dist/lib/kalshi/types.d.ts +53 -53
- package/dist/lib/logger.js +3 -3
- package/dist/lib/output/formatter.d.ts +20 -20
- package/dist/lib/output/formatter.js +55 -55
- package/dist/lib/retry.d.ts +2 -2
- package/dist/lib/retry.js +8 -10
- package/dist/lib/risk.d.ts +51 -0
- package/dist/lib/risk.js +153 -0
- package/dist/lib/sanitize.js +9 -9
- package/dist/lib/scanner.d.ts +58 -0
- package/dist/lib/scanner.js +160 -0
- package/dist/lib/shutdown.d.ts +4 -4
- package/dist/lib/shutdown.js +7 -7
- package/dist/lib/validation.d.ts +5 -5
- package/dist/lib/validation.js +14 -20
- package/docs/TRADING_STRATEGIES.md +538 -0
- package/oclif.manifest.json +559 -170
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Market scanning and opportunity detection utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculate total liquidity from an orderbook
|
|
6
|
+
*/
|
|
7
|
+
export function calculateOrderbookLiquidity(orderbook) {
|
|
8
|
+
const yesTotal = orderbook.yes.reduce((sum, level) => sum + level.count * level.price, 0);
|
|
9
|
+
const noTotal = orderbook.no.reduce((sum, level) => sum + level.count * level.price, 0);
|
|
10
|
+
return {
|
|
11
|
+
noTotal,
|
|
12
|
+
total: yesTotal + noTotal,
|
|
13
|
+
yesTotal,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Calculate bid-ask spread from an orderbook
|
|
18
|
+
*
|
|
19
|
+
* In Kalshi orderbooks:
|
|
20
|
+
* - Lower prices in the yes array are bids (buyers)
|
|
21
|
+
* - Higher prices in the yes array are asks (sellers)
|
|
22
|
+
* - Spread = ask - bid
|
|
23
|
+
*/
|
|
24
|
+
export function calculateSpread(orderbook) {
|
|
25
|
+
if (orderbook.yes.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const prices = orderbook.yes.map(level => level.price).sort((a, b) => a - b);
|
|
29
|
+
// Best bid is the lowest price (buyers)
|
|
30
|
+
const yesBid = prices[0];
|
|
31
|
+
// Best ask is the highest price (sellers)
|
|
32
|
+
const yesAsk = prices.at(-1);
|
|
33
|
+
if (!yesAsk || !yesBid) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
// If bid and ask are the same, there's no spread
|
|
37
|
+
const spreadCents = yesAsk - yesBid;
|
|
38
|
+
const midPrice = (yesBid + yesAsk) / 2;
|
|
39
|
+
const spreadPct = midPrice > 0 ? (spreadCents / midPrice) * 100 : 0;
|
|
40
|
+
return {
|
|
41
|
+
spreadCents,
|
|
42
|
+
spreadPct,
|
|
43
|
+
yesAsk,
|
|
44
|
+
yesBid,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const DEFAULT_WEIGHTS = {
|
|
48
|
+
liquidity: 40,
|
|
49
|
+
spread: 30,
|
|
50
|
+
volume: 30,
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Score a market based on liquidity, spread, and volume
|
|
54
|
+
* Returns score from 0-100
|
|
55
|
+
*/
|
|
56
|
+
export function scoreMarket(market, orderbook, weights = DEFAULT_WEIGHTS) {
|
|
57
|
+
let score = 0;
|
|
58
|
+
// Liquidity score (0-40 points)
|
|
59
|
+
const liquidity = calculateOrderbookLiquidity(orderbook);
|
|
60
|
+
const liquidityScore = Math.min(weights.liquidity, (liquidity.total / 5000) * weights.liquidity);
|
|
61
|
+
score += liquidityScore;
|
|
62
|
+
// Spread score (0-30 points) - wider spreads are better for market making
|
|
63
|
+
const spread = calculateSpread(orderbook);
|
|
64
|
+
if (spread) {
|
|
65
|
+
// Spread between 0.03 (3 cents) and 0.20 (20 cents) is ideal
|
|
66
|
+
const { spreadCents } = spread;
|
|
67
|
+
let spreadScore = 0;
|
|
68
|
+
if (spreadCents >= 0.03 && spreadCents <= 0.2) {
|
|
69
|
+
spreadScore = weights.spread;
|
|
70
|
+
}
|
|
71
|
+
else if (spreadCents > 0.2) {
|
|
72
|
+
// Too wide - gradually decrease score
|
|
73
|
+
spreadScore = Math.max(0, weights.spread * (1 - (spreadCents - 0.2) / 0.3));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Too narrow - gradually decrease score
|
|
77
|
+
spreadScore = (spreadCents / 0.03) * weights.spread;
|
|
78
|
+
}
|
|
79
|
+
score += spreadScore;
|
|
80
|
+
}
|
|
81
|
+
// Volume score (0-30 points)
|
|
82
|
+
const volume = market.volume_24h || market.volume || 0;
|
|
83
|
+
const volumeScore = Math.min(weights.volume, (volume / 10_000) * weights.volume);
|
|
84
|
+
score += volumeScore;
|
|
85
|
+
return Math.round(score);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Classify the type of opportunity a market presents
|
|
89
|
+
*/
|
|
90
|
+
export function classifyOpportunity(spread, liquidity, volume) {
|
|
91
|
+
const hasWideSpread = spread >= 0.05;
|
|
92
|
+
const hasHighLiquidity = liquidity >= 2000;
|
|
93
|
+
const hasHighVolume = volume >= 5000;
|
|
94
|
+
// Check combinations
|
|
95
|
+
if (hasWideSpread && hasHighLiquidity && hasHighVolume) {
|
|
96
|
+
return 'balanced';
|
|
97
|
+
}
|
|
98
|
+
if (hasWideSpread) {
|
|
99
|
+
return 'wide_spread';
|
|
100
|
+
}
|
|
101
|
+
if (hasHighLiquidity) {
|
|
102
|
+
return 'high_liquidity';
|
|
103
|
+
}
|
|
104
|
+
if (hasHighVolume) {
|
|
105
|
+
return 'high_volume';
|
|
106
|
+
}
|
|
107
|
+
// Default to balanced if none of the above
|
|
108
|
+
return 'balanced';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Generate human-readable reason for opportunity
|
|
112
|
+
*/
|
|
113
|
+
export function generateOpportunityReason(type, spread, liquidity, volume) {
|
|
114
|
+
const spreadCents = Math.round(spread * 100);
|
|
115
|
+
switch (type) {
|
|
116
|
+
case 'balanced': {
|
|
117
|
+
return `Balanced opportunity: ${spreadCents} cent spread, $${Math.round(liquidity)} liquidity, $${Math.round(volume)} volume`;
|
|
118
|
+
}
|
|
119
|
+
case 'high_liquidity': {
|
|
120
|
+
return `Strong liquidity ($${Math.round(liquidity).toLocaleString()})`;
|
|
121
|
+
}
|
|
122
|
+
case 'high_volume': {
|
|
123
|
+
return `High trading volume ($${Math.round(volume).toLocaleString()} 24h)`;
|
|
124
|
+
}
|
|
125
|
+
case 'wide_spread': {
|
|
126
|
+
return `High liquidity with ${spreadCents} cent spread`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Filter markets based on criteria
|
|
132
|
+
*/
|
|
133
|
+
export function filterMarketsByCriteria(markets, criteria) {
|
|
134
|
+
return markets.filter(m => {
|
|
135
|
+
// Liquidity check
|
|
136
|
+
if (criteria.minLiquidity && m.liquidity < criteria.minLiquidity) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
// Spread checks
|
|
140
|
+
if (m.spread === null) {
|
|
141
|
+
// No spread data - exclude if spread filters are specified
|
|
142
|
+
if (criteria.minSpread || criteria.maxSpread) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (criteria.minSpread && m.spread < criteria.minSpread) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (criteria.maxSpread && m.spread > criteria.maxSpread) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Volume check
|
|
155
|
+
if (criteria.minVolume && m.volume < criteria.minVolume) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
});
|
|
160
|
+
}
|
package/dist/lib/shutdown.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ declare class ShutdownManager {
|
|
|
10
10
|
private isShuttingDown;
|
|
11
11
|
private shutdownTimeout;
|
|
12
12
|
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Check if shutdown is in progress
|
|
15
|
+
*/
|
|
16
|
+
isShutdownInProgress(): boolean;
|
|
13
17
|
/**
|
|
14
18
|
* Register a cleanup function to be called on shutdown
|
|
15
19
|
*/
|
|
@@ -26,10 +30,6 @@ declare class ShutdownManager {
|
|
|
26
30
|
* Perform graceful shutdown
|
|
27
31
|
*/
|
|
28
32
|
private shutdown;
|
|
29
|
-
/**
|
|
30
|
-
* Check if shutdown is in progress
|
|
31
|
-
*/
|
|
32
|
-
isShutdownInProgress(): boolean;
|
|
33
33
|
}
|
|
34
34
|
export declare const shutdownManager: ShutdownManager;
|
|
35
35
|
/**
|
package/dist/lib/shutdown.js
CHANGED
|
@@ -10,6 +10,12 @@ class ShutdownManager {
|
|
|
10
10
|
// Register signal handlers
|
|
11
11
|
this.registerSignalHandlers();
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if shutdown is in progress
|
|
15
|
+
*/
|
|
16
|
+
isShutdownInProgress() {
|
|
17
|
+
return this.isShuttingDown;
|
|
18
|
+
}
|
|
13
19
|
/**
|
|
14
20
|
* Register a cleanup function to be called on shutdown
|
|
15
21
|
*/
|
|
@@ -56,7 +62,7 @@ class ShutdownManager {
|
|
|
56
62
|
return;
|
|
57
63
|
}
|
|
58
64
|
this.isShuttingDown = true;
|
|
59
|
-
logger.info({
|
|
65
|
+
logger.info({ cleanupCount: this.cleanupFunctions.length, signal }, 'Starting graceful shutdown');
|
|
60
66
|
// Set timeout for shutdown
|
|
61
67
|
const shutdownTimer = setTimeout(() => {
|
|
62
68
|
logger.error({ timeout: this.shutdownTimeout }, 'Shutdown timeout exceeded, forcing exit');
|
|
@@ -83,12 +89,6 @@ class ShutdownManager {
|
|
|
83
89
|
process.exit(1);
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Check if shutdown is in progress
|
|
88
|
-
*/
|
|
89
|
-
isShutdownInProgress() {
|
|
90
|
-
return this.isShuttingDown;
|
|
91
|
-
}
|
|
92
92
|
}
|
|
93
93
|
// Singleton instance
|
|
94
94
|
export const shutdownManager = new ShutdownManager();
|
package/dist/lib/validation.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare function validatePrice(price: number): void;
|
|
|
18
18
|
/**
|
|
19
19
|
* Validate side
|
|
20
20
|
*/
|
|
21
|
-
export declare function validateSide(side: string): '
|
|
21
|
+
export declare function validateSide(side: string): 'no' | 'yes';
|
|
22
22
|
/**
|
|
23
23
|
* Validate action
|
|
24
24
|
*/
|
|
@@ -26,12 +26,12 @@ export declare function validateAction(action: string): 'buy' | 'sell';
|
|
|
26
26
|
/**
|
|
27
27
|
* Validate order type
|
|
28
28
|
*/
|
|
29
|
-
export declare function validateOrderType(type: string): '
|
|
29
|
+
export declare function validateOrderType(type: string): 'limit' | 'market';
|
|
30
30
|
/**
|
|
31
31
|
* Calculate order cost estimate
|
|
32
32
|
*/
|
|
33
|
-
export declare function estimateOrderCost(side: '
|
|
34
|
-
min: number;
|
|
35
|
-
max: number;
|
|
33
|
+
export declare function estimateOrderCost(side: 'no' | 'yes', action: 'buy' | 'sell', quantity: number, price?: number): {
|
|
36
34
|
estimate: number;
|
|
35
|
+
max: number;
|
|
36
|
+
min: number;
|
|
37
37
|
};
|
package/dist/lib/validation.js
CHANGED
|
@@ -18,8 +18,8 @@ export function validateTicker(ticker) {
|
|
|
18
18
|
}
|
|
19
19
|
if (ticker.length < 3 || ticker.length > 100) {
|
|
20
20
|
throw new ValidationError('Ticker must be between 3 and 100 characters', {
|
|
21
|
-
ticker,
|
|
22
21
|
length: ticker.length,
|
|
22
|
+
ticker,
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -35,8 +35,8 @@ export function validateQuantity(quantity, maxOrderSize) {
|
|
|
35
35
|
}
|
|
36
36
|
if (quantity > maxOrderSize) {
|
|
37
37
|
throw new ValidationError(`Quantity exceeds maximum order size of ${maxOrderSize}`, {
|
|
38
|
-
quantity,
|
|
39
38
|
maxOrderSize,
|
|
39
|
+
quantity,
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -52,8 +52,8 @@ export function validatePrice(price) {
|
|
|
52
52
|
const decimalPlaces = (price.toString().split('.')[1] || '').length;
|
|
53
53
|
if (decimalPlaces > 2) {
|
|
54
54
|
throw new ValidationError('Price cannot have more than 2 decimal places', {
|
|
55
|
-
price,
|
|
56
55
|
decimalPlaces,
|
|
56
|
+
price,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -97,24 +97,18 @@ export function estimateOrderCost(side, action, quantity, price) {
|
|
|
97
97
|
if (price) {
|
|
98
98
|
// Limit buy - exact price known
|
|
99
99
|
const cost = quantity * price;
|
|
100
|
-
return {
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
// Market buy - could fill at any price
|
|
104
|
-
// Estimate mid-range
|
|
105
|
-
const estimate = quantity * 0.5;
|
|
106
|
-
return { min: quantity * 0.01, max: quantity * 0.99, estimate };
|
|
100
|
+
return { estimate: cost, max: cost, min: cost };
|
|
107
101
|
}
|
|
102
|
+
// Market buy - could fill at any price
|
|
103
|
+
// Estimate mid-range
|
|
104
|
+
const estimate = quantity * 0.5;
|
|
105
|
+
return { estimate, max: quantity * 0.99, min: quantity * 0.01 };
|
|
108
106
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return { min: proceeds, max: proceeds, estimate: proceeds };
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
const estimate = quantity * 0.5;
|
|
117
|
-
return { min: quantity * 0.01, max: quantity * 0.99, estimate };
|
|
118
|
-
}
|
|
107
|
+
// Sell orders
|
|
108
|
+
if (price) {
|
|
109
|
+
const proceeds = quantity * price;
|
|
110
|
+
return { estimate: proceeds, max: proceeds, min: proceeds };
|
|
119
111
|
}
|
|
112
|
+
const estimate = quantity * 0.5;
|
|
113
|
+
return { estimate, max: quantity * 0.99, min: quantity * 0.01 };
|
|
120
114
|
}
|