pmxtjs 0.1.0 → 0.1.2
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/{src/BaseExchange.ts → dist/BaseExchange.d.ts} +4 -22
- package/dist/BaseExchange.js +30 -0
- package/dist/exchanges/Kalshi.d.ts +20 -0
- package/{src/exchanges/Kalshi.ts → dist/exchanges/Kalshi.js} +75 -114
- package/dist/exchanges/Polymarket.d.ts +38 -0
- package/{src/exchanges/Polymarket.ts → dist/exchanges/Polymarket.js} +105 -146
- package/{src/index.ts → dist/index.d.ts} +0 -1
- package/dist/index.js +20 -0
- package/{src/types.ts → dist/types.d.ts} +3 -17
- package/dist/types.js +5 -0
- package/package.json +9 -1
- package/readme.md +62 -0
- package/coverage/clover.xml +0 -334
- package/coverage/coverage-final.json +0 -4
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/pmxt/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/pmxt/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/pmxt/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/pmxt/exchanges/index.html +0 -131
- package/coverage/lcov-report/pmxt/index.html +0 -116
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/src/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/src/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/src/exchanges/index.html +0 -131
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov.info +0 -766
- package/examples/get_event_prices.ts +0 -37
- package/examples/historical_prices.ts +0 -117
- package/examples/orderbook.ts +0 -102
- package/examples/recent_trades.ts +0 -29
- package/examples/search_events.ts +0 -68
- package/examples/search_market.ts +0 -29
- package/jest.config.js +0 -11
- package/pmxt-0.1.0.tgz +0 -0
- package/test/exchanges/kalshi/ApiErrors.test.ts +0 -132
- package/test/exchanges/kalshi/EmptyResponse.test.ts +0 -44
- package/test/exchanges/kalshi/FetchAndNormalizeMarkets.test.ts +0 -56
- package/test/exchanges/kalshi/LiveApi.integration.test.ts +0 -40
- package/test/exchanges/kalshi/MarketHistory.test.ts +0 -185
- package/test/exchanges/kalshi/OrderBook.test.ts +0 -149
- package/test/exchanges/kalshi/SearchMarkets.test.ts +0 -174
- package/test/exchanges/kalshi/VolumeFallback.test.ts +0 -44
- package/test/exchanges/polymarket/DataValidation.test.ts +0 -271
- package/test/exchanges/polymarket/ErrorHandling.test.ts +0 -34
- package/test/exchanges/polymarket/FetchAndNormalizeMarkets.test.ts +0 -68
- package/test/exchanges/polymarket/GetMarketsBySlug.test.ts +0 -268
- package/test/exchanges/polymarket/LiveApi.integration.test.ts +0 -44
- package/test/exchanges/polymarket/MarketHistory.test.ts +0 -207
- package/test/exchanges/polymarket/OrderBook.test.ts +0 -167
- package/test/exchanges/polymarket/RequestParameters.test.ts +0 -39
- package/test/exchanges/polymarket/SearchMarkets.test.ts +0 -176
- package/test/exchanges/polymarket/TradeHistory.test.ts +0 -248
- package/tsconfig.json +0 -12
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
2
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
3
|
-
|
|
4
|
-
const main = async () => {
|
|
5
|
-
const polySlug = process.argv[2] || 'who-will-trump-nominate-as-fed-chair';
|
|
6
|
-
const kalshiTicker = process.argv[3] || 'KXFEDCHAIRNOM-29';
|
|
7
|
-
|
|
8
|
-
console.log(`Fetching prices for: ${polySlug} / ${kalshiTicker}\n`);
|
|
9
|
-
|
|
10
|
-
// Polymarket
|
|
11
|
-
const polymarket = new PolymarketExchange();
|
|
12
|
-
const polyMarkets = await polymarket.getMarketsBySlug(polySlug);
|
|
13
|
-
|
|
14
|
-
console.log('--- Polymarket ---');
|
|
15
|
-
polyMarkets
|
|
16
|
-
.sort((a, b) => b.outcomes[0].price - a.outcomes[0].price)
|
|
17
|
-
.slice(0, 10)
|
|
18
|
-
.forEach(m => {
|
|
19
|
-
console.log(`${m.outcomes[0].label}: ${(m.outcomes[0].price * 100).toFixed(1)}% ` +
|
|
20
|
-
`(Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Kalshi
|
|
24
|
-
const kalshi = new KalshiExchange();
|
|
25
|
-
const kalshiMarkets = await kalshi.getMarketsBySlug(kalshiTicker);
|
|
26
|
-
|
|
27
|
-
console.log('\n--- Kalshi ---');
|
|
28
|
-
kalshiMarkets
|
|
29
|
-
.sort((a, b) => b.outcomes[0].price - a.outcomes[0].price)
|
|
30
|
-
.slice(0, 10)
|
|
31
|
-
.forEach(m => {
|
|
32
|
-
console.log(`${m.outcomes[0].label}: ${(m.outcomes[0].price * 100).toFixed(1)}% ` +
|
|
33
|
-
`(Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
main();
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
3
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
4
|
-
import { CandleInterval } from '../src/types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* START HERE
|
|
8
|
-
*
|
|
9
|
-
* This example fetches historical price data for a specific EVENT.
|
|
10
|
-
*
|
|
11
|
-
* NOTE:
|
|
12
|
-
* An "Event" (e.g. "Who will be Fed Chair?") contains multiple "Markets" or "Outcomes"
|
|
13
|
-
* (e.g. "Kevin Warsh", "Marc Rowan", "Scott Bessent").
|
|
14
|
-
*
|
|
15
|
-
* Price history is tracked at the MARKET/OUTCOME level.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const main = async () => {
|
|
19
|
-
// 1. Define the Event Slugs (Hardcoded for simplicity)
|
|
20
|
-
const polySlug = 'who-will-trump-nominate-as-fed-chair';
|
|
21
|
-
const kalshiTicker = 'KXFEDCHAIRNOM-29';
|
|
22
|
-
|
|
23
|
-
console.log(`=== Historical Price Fetcher ===\n`);
|
|
24
|
-
console.log(`Target Event: Fed Chair Nomination (Kevin Warsh - YES)`);
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------
|
|
27
|
-
// Polymarket
|
|
28
|
-
// ---------------------------------------------------------
|
|
29
|
-
const polymarket = new PolymarketExchange();
|
|
30
|
-
console.log('\n--- Polymarket ---');
|
|
31
|
-
try {
|
|
32
|
-
const polyMarkets = await polymarket.getMarketsBySlug(polySlug);
|
|
33
|
-
|
|
34
|
-
if (polyMarkets.length > 0) {
|
|
35
|
-
const eventMarket = polyMarkets[0];
|
|
36
|
-
|
|
37
|
-
// Find "Kevin Warsh" outcome
|
|
38
|
-
const warshOutcome = eventMarket.outcomes.find(o =>
|
|
39
|
-
o.label.toLowerCase().includes('warsh') &&
|
|
40
|
-
!o.label.toLowerCase().includes('not') // Ensure it's YES
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
if (!warshOutcome) {
|
|
44
|
-
console.log("Could not find 'Kevin Warsh' outcome in Polymarket event.");
|
|
45
|
-
} else {
|
|
46
|
-
console.log(`Target Outcome: ${warshOutcome.label}`);
|
|
47
|
-
console.log(`Current Price: ${(warshOutcome.price * 100).toFixed(1)}%`);
|
|
48
|
-
|
|
49
|
-
const clobTokenId = warshOutcome.metadata?.clobTokenId;
|
|
50
|
-
|
|
51
|
-
if (clobTokenId) {
|
|
52
|
-
console.log(`Fetching 1-hour history for "${warshOutcome.label}"...`);
|
|
53
|
-
console.log(`Token ID: ${clobTokenId}`);
|
|
54
|
-
|
|
55
|
-
const history = await polymarket.getMarketHistory(clobTokenId, {
|
|
56
|
-
resolution: '1h',
|
|
57
|
-
limit: 20,
|
|
58
|
-
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // 7 days ago
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
console.log(`Received ${history.length} data points.`);
|
|
62
|
-
history.forEach(point => {
|
|
63
|
-
const date = new Date(point.timestamp).toLocaleString();
|
|
64
|
-
console.log(` ${date} | Price: $${point.close.toFixed(3)}`);
|
|
65
|
-
});
|
|
66
|
-
} else {
|
|
67
|
-
console.log('No Token ID found, cannot fetch history.');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
console.error("Polymarket Error:", e);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------
|
|
76
|
-
// Kalshi
|
|
77
|
-
// ---------------------------------------------------------
|
|
78
|
-
const kalshi = new KalshiExchange();
|
|
79
|
-
console.log('\n--- Kalshi ---');
|
|
80
|
-
try {
|
|
81
|
-
// First get the event to find the markets
|
|
82
|
-
const kalshiMarkets = await kalshi.getMarketsBySlug(kalshiTicker);
|
|
83
|
-
|
|
84
|
-
if (kalshiMarkets.length > 0) {
|
|
85
|
-
// Find the market where the subtitle/title includes Warsh
|
|
86
|
-
// Kalshi often has subtitles like "Kevin Warsh"
|
|
87
|
-
const warshMarket = kalshiMarkets.find(m =>
|
|
88
|
-
m.description.toLowerCase().includes('warsh') ||
|
|
89
|
-
m.outcomes[0].label.toLowerCase().includes('warsh')
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (!warshMarket) {
|
|
93
|
-
console.log("Could not find 'Kevin Warsh' market in Kalshi event.");
|
|
94
|
-
} else {
|
|
95
|
-
console.log(`Target Market: ${warshMarket.title} (${warshMarket.description})`);
|
|
96
|
-
console.log(`Ticker: ${warshMarket.id}`);
|
|
97
|
-
console.log(`Current Price: ${(warshMarket.outcomes[0].price * 100).toFixed(1)}%`);
|
|
98
|
-
|
|
99
|
-
console.log(`Fetching 1-hour history...`);
|
|
100
|
-
|
|
101
|
-
const history = await kalshi.getMarketHistory(warshMarket.id, {
|
|
102
|
-
resolution: '1h',
|
|
103
|
-
limit: 10
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
history.forEach(candle => {
|
|
107
|
-
const date = new Date(candle.timestamp).toLocaleString();
|
|
108
|
-
console.log(` ${date} | Price: $${candle.close.toFixed(2)}`);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error("Kalshi Error:", e);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
main();
|
package/examples/orderbook.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
3
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
4
|
-
import { OrderBook } from '../src/types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Example: Get Order Book (Multi-Exchange)
|
|
8
|
-
*
|
|
9
|
-
* This example fetches and compares order books from both Polymarket and Kalshi
|
|
10
|
-
* for the "Kevin Warsh" Fed Chair nomination prediction.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Helper to display an order book
|
|
14
|
-
const displayOrderBook = (exchangeName: string, book: OrderBook) => {
|
|
15
|
-
console.log(`\n-----------------------------------------`);
|
|
16
|
-
console.log(`ORDER BOOK: ${exchangeName}`);
|
|
17
|
-
console.log(`-----------------------------------------`);
|
|
18
|
-
|
|
19
|
-
if (book.bids.length === 0 && book.asks.length === 0) {
|
|
20
|
-
console.log("No orders found or market is closed.");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Show Asks (Sellers) - Reverse to show highest price (lowest/best ask) at bottom
|
|
25
|
-
console.log(`\n --- ASKS (Sellers) ---`);
|
|
26
|
-
const asks = book.asks.slice(0, 5).sort((a, b) => b.price - a.price); // Sort descending for display (high to low)
|
|
27
|
-
asks.forEach(level => {
|
|
28
|
-
console.log(` Price: $${level.price.toFixed(2)} | Size: ${level.size.toLocaleString().padStart(8)}`);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Show Bids (Buyers) - Descending (Highest/best buy first)
|
|
32
|
-
console.log(` --- BIDS (Buyers) ---`);
|
|
33
|
-
const bids = book.bids.slice(0, 5).sort((a, b) => b.price - a.price);
|
|
34
|
-
bids.forEach(level => {
|
|
35
|
-
console.log(` Price: $${level.price.toFixed(2)} | Size: ${level.size.toLocaleString().padStart(8)}`);
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const main = async () => {
|
|
40
|
-
console.log(`=== Multi-Exchange Order Book Fetcher ===`);
|
|
41
|
-
console.log(`Target: "Kevin Warsh" -> Fed Chair Nomination\n`);
|
|
42
|
-
|
|
43
|
-
// 1. Polymarket
|
|
44
|
-
try {
|
|
45
|
-
const polymarket = new PolymarketExchange();
|
|
46
|
-
const polySlug = 'who-will-trump-nominate-as-fed-chair';
|
|
47
|
-
|
|
48
|
-
console.log(`Fetching Polymarket data...`);
|
|
49
|
-
const markets = await polymarket.getMarketsBySlug(polySlug);
|
|
50
|
-
|
|
51
|
-
if (markets.length > 0) {
|
|
52
|
-
const warshOutcome = markets[0].outcomes.find(o =>
|
|
53
|
-
o.label.toLowerCase().includes('warsh') &&
|
|
54
|
-
!o.label.toLowerCase().includes('not')
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
if (warshOutcome && warshOutcome.metadata?.clobTokenId) {
|
|
58
|
-
const book = await polymarket.getOrderBook(warshOutcome.metadata.clobTokenId);
|
|
59
|
-
displayOrderBook('Polymarket', book);
|
|
60
|
-
} else {
|
|
61
|
-
console.log("Polymarket: Outcome or Token ID not found.");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.error("Polymarket Error:", e);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 2. Kalshi
|
|
69
|
-
try {
|
|
70
|
-
const kalshi = new KalshiExchange();
|
|
71
|
-
// Specifically for "Kevin Warsh" in the Fed Chair market.
|
|
72
|
-
// Ticker derived from previous searches.
|
|
73
|
-
const kalshiTicker = 'KXFEDCHAIRNOM-29';
|
|
74
|
-
|
|
75
|
-
console.log(`\nFetching Kalshi data...`);
|
|
76
|
-
// Verify event exists and find the specific market
|
|
77
|
-
const markets = await kalshi.getMarketsBySlug(kalshiTicker);
|
|
78
|
-
|
|
79
|
-
if (markets.length > 0) {
|
|
80
|
-
// Find "Kevin Warsh" market within the event group
|
|
81
|
-
const warshMarket = markets.find(m =>
|
|
82
|
-
m.description.toLowerCase().includes('warsh') ||
|
|
83
|
-
m.outcomes[0].label.toLowerCase().includes('warsh')
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
if (warshMarket) {
|
|
87
|
-
console.log(`Found Market Ticker: ${warshMarket.id}`);
|
|
88
|
-
const book = await kalshi.getOrderBook(warshMarket.id);
|
|
89
|
-
displayOrderBook('Kalshi', book);
|
|
90
|
-
} else {
|
|
91
|
-
console.log("Kalshi: 'Kevin Warsh' market not found in event.");
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
console.log("Kalshi: Event not found.");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
} catch (e) {
|
|
98
|
-
console.error("Kalshi Error:", e);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
main();
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
2
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
3
|
-
|
|
4
|
-
async function run() {
|
|
5
|
-
const kalshi = new KalshiExchange();
|
|
6
|
-
const poly = new PolymarketExchange();
|
|
7
|
-
|
|
8
|
-
const kMarkets = await kalshi.getMarketsBySlug('KXFEDCHAIRNOM-29');
|
|
9
|
-
// Filter for Kevin Warsh specifically
|
|
10
|
-
const warshMarket = kMarkets.find(m => m.outcomes[0].label.includes('Kevin Warsh'));
|
|
11
|
-
|
|
12
|
-
if (warshMarket) {
|
|
13
|
-
console.log(`--- Kalshi Tape: ${warshMarket.outcomes[0].label} ---`);
|
|
14
|
-
const trades = await kalshi.getTradeHistory(warshMarket.id, { limit: 10, resolution: '1m' });
|
|
15
|
-
trades.forEach(t => console.log(`${new Date(t.timestamp).toLocaleTimeString()} | ${t.side.toUpperCase()} | ${(t.price * 100).toFixed(1)}c | ${t.amount}`));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const pMarkets = await poly.getMarketsBySlug('who-will-trump-nominate-as-fed-chair');
|
|
19
|
-
const pWarsh = pMarkets.find(m => m.outcomes[0].label.includes('Kevin Warsh'));
|
|
20
|
-
|
|
21
|
-
if (pWarsh) {
|
|
22
|
-
console.log(`\n--- Polymarket Tape: Kevin Warsh ---`);
|
|
23
|
-
const tokenId = pWarsh.outcomes[0].metadata?.clobTokenId;
|
|
24
|
-
const trades = await poly.getTradeHistory(tokenId, { limit: 5, resolution: '1m' });
|
|
25
|
-
trades.forEach(t => console.log(`${new Date(t.timestamp).toLocaleTimeString()} | ${t.side.toUpperCase()} | ${(t.price * 100).toFixed(1)}c | ${t.amount}`));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
run();
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
2
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
3
|
-
import { UnifiedMarket } from '../src/types';
|
|
4
|
-
|
|
5
|
-
interface UnifiedEvent {
|
|
6
|
-
title: string;
|
|
7
|
-
url: string;
|
|
8
|
-
volume24h: number; // Aggregated
|
|
9
|
-
markets: UnifiedMarket[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const groupMarketsByEvent = (markets: UnifiedMarket[]): UnifiedEvent[] => {
|
|
13
|
-
const eventsMap = new Map<string, UnifiedEvent>();
|
|
14
|
-
|
|
15
|
-
for (const m of markets) {
|
|
16
|
-
if (!eventsMap.has(m.url)) {
|
|
17
|
-
eventsMap.set(m.url, {
|
|
18
|
-
title: m.title,
|
|
19
|
-
url: m.url,
|
|
20
|
-
volume24h: 0,
|
|
21
|
-
markets: []
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
const event = eventsMap.get(m.url)!;
|
|
25
|
-
event.volume24h += m.volume24h;
|
|
26
|
-
event.markets.push(m);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return Array.from(eventsMap.values());
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const main = async () => {
|
|
33
|
-
const query = process.argv[2] || 'Fed';
|
|
34
|
-
console.log(`Searching for Events matching "${query}"...\n`);
|
|
35
|
-
|
|
36
|
-
// 1. Polymarket
|
|
37
|
-
const polymarket = new PolymarketExchange();
|
|
38
|
-
// Polymarket returns many sub-markets per event
|
|
39
|
-
const polyMarkets = await polymarket.searchMarkets(query, { sort: 'volume' });
|
|
40
|
-
const polyEvents = groupMarketsByEvent(polyMarkets);
|
|
41
|
-
|
|
42
|
-
console.log(`--- Polymarket Events (${polyEvents.length}) ---`);
|
|
43
|
-
polyEvents
|
|
44
|
-
.sort((a, b) => b.volume24h - a.volume24h)
|
|
45
|
-
.slice(0, 5)
|
|
46
|
-
.forEach(e => {
|
|
47
|
-
console.log(`${e.title} (Vol24h: $${e.volume24h.toLocaleString()})`);
|
|
48
|
-
console.log(` -> ${e.markets.length} sub-markets`);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
console.log('');
|
|
52
|
-
|
|
53
|
-
// 2. Kalshi
|
|
54
|
-
const kalshi = new KalshiExchange();
|
|
55
|
-
const kalshiMarkets = await kalshi.searchMarkets(query);
|
|
56
|
-
const kalshiEvents = groupMarketsByEvent(kalshiMarkets);
|
|
57
|
-
|
|
58
|
-
console.log(`--- Kalshi Events (${kalshiEvents.length}) ---`);
|
|
59
|
-
kalshiEvents
|
|
60
|
-
.sort((a, b) => b.volume24h - a.volume24h)
|
|
61
|
-
.slice(0, 5)
|
|
62
|
-
.forEach(e => {
|
|
63
|
-
console.log(`${e.title} (Vol24h: $${e.volume24h.toLocaleString()})`);
|
|
64
|
-
console.log(` -> ${e.markets.length} sub-markets`);
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
main();
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { PolymarketExchange } from '../src/exchanges/Polymarket';
|
|
2
|
-
import { KalshiExchange } from '../src/exchanges/Kalshi';
|
|
3
|
-
|
|
4
|
-
const main = async () => {
|
|
5
|
-
const query = process.argv[2] || 'Fed';
|
|
6
|
-
console.log(`Searching for "${query}"...\n`);
|
|
7
|
-
|
|
8
|
-
// Polymarket
|
|
9
|
-
const polymarket = new PolymarketExchange();
|
|
10
|
-
const polyResults = await polymarket.searchMarkets(query, { sort: 'volume' });
|
|
11
|
-
|
|
12
|
-
console.log(`--- Polymarket Found ${polyResults.length} ---`);
|
|
13
|
-
polyResults.slice(0, 10).forEach(m => {
|
|
14
|
-
const label = m.outcomes[0]?.label || 'Unknown';
|
|
15
|
-
console.log(`[${m.id}] ${m.title} - ${label} (Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Kalshi
|
|
19
|
-
const kalshi = new KalshiExchange();
|
|
20
|
-
const kalshiResults = await kalshi.searchMarkets(query);
|
|
21
|
-
|
|
22
|
-
console.log(`\n--- Kalshi Found ${kalshiResults.length} ---`);
|
|
23
|
-
kalshiResults.slice(0, 10).forEach(m => {
|
|
24
|
-
const label = m.outcomes[0]?.label || 'Unknown';
|
|
25
|
-
console.log(`[${m.id}] ${m.title} - ${label} (Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
main();
|
package/jest.config.js
DELETED
package/pmxt-0.1.0.tgz
DELETED
|
Binary file
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { KalshiExchange } from '../../../src/exchanges/Kalshi';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Kalshi API Error Handling Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests how the exchange handles various API error responses (4xx, 5xx).
|
|
8
|
-
* Why: External APIs can fail with rate limits, authentication errors, or server issues.
|
|
9
|
-
* The library must handle these gracefully without crashing.
|
|
10
|
-
* How: Mocks different HTTP error responses and verifies proper error handling.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
jest.mock('axios');
|
|
14
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
15
|
-
|
|
16
|
-
describe('KalshiExchange - API Error Handling', () => {
|
|
17
|
-
let exchange: KalshiExchange;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
exchange = new KalshiExchange();
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should handle 404 errors in getMarketsBySlug', async () => {
|
|
25
|
-
const error = {
|
|
26
|
-
response: {
|
|
27
|
-
status: 404,
|
|
28
|
-
data: { error: 'Event not found' }
|
|
29
|
-
},
|
|
30
|
-
isAxiosError: true
|
|
31
|
-
};
|
|
32
|
-
mockedAxios.get.mockRejectedValue(error);
|
|
33
|
-
// @ts-expect-error - Mock type mismatch is expected in tests
|
|
34
|
-
mockedAxios.isAxiosError = jest.fn().mockReturnValue(true);
|
|
35
|
-
|
|
36
|
-
await expect(exchange.getMarketsBySlug('INVALID-EVENT'))
|
|
37
|
-
.rejects
|
|
38
|
-
.toThrow(/not found/i);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should handle 500 errors in getMarketsBySlug', async () => {
|
|
42
|
-
const error = {
|
|
43
|
-
response: {
|
|
44
|
-
status: 500,
|
|
45
|
-
data: { error: 'Internal Server Error' }
|
|
46
|
-
},
|
|
47
|
-
isAxiosError: true
|
|
48
|
-
};
|
|
49
|
-
mockedAxios.get.mockRejectedValue(error);
|
|
50
|
-
// @ts-expect-error - Mock type mismatch is expected in tests
|
|
51
|
-
mockedAxios.isAxiosError = jest.fn().mockReturnValue(true);
|
|
52
|
-
|
|
53
|
-
await expect(exchange.getMarketsBySlug('SOME-EVENT'))
|
|
54
|
-
.rejects
|
|
55
|
-
.toThrow(/API Error/i);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should handle network errors in fetchMarkets', async () => {
|
|
59
|
-
mockedAxios.get.mockRejectedValue(new Error('Network timeout'));
|
|
60
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
61
|
-
|
|
62
|
-
const markets = await exchange.fetchMarkets();
|
|
63
|
-
|
|
64
|
-
expect(markets).toEqual([]);
|
|
65
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
66
|
-
consoleSpy.mockRestore();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should handle malformed response data', async () => {
|
|
70
|
-
mockedAxios.get.mockResolvedValue({
|
|
71
|
-
data: {
|
|
72
|
-
events: [
|
|
73
|
-
{
|
|
74
|
-
// Missing required fields
|
|
75
|
-
event_ticker: 'TEST',
|
|
76
|
-
markets: [{ ticker: 'TEST-MARKET' }]
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const markets = await exchange.fetchMarkets();
|
|
83
|
-
|
|
84
|
-
// Should not crash, may return empty or partial data
|
|
85
|
-
expect(Array.isArray(markets)).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should handle invalid ticker format in getMarketHistory', async () => {
|
|
89
|
-
await expect(exchange.getMarketHistory('INVALID', { resolution: '1h' }))
|
|
90
|
-
.rejects
|
|
91
|
-
.toThrow(/Invalid Kalshi Ticker format/i);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should handle API errors in getMarketHistory', async () => {
|
|
95
|
-
const error = {
|
|
96
|
-
response: {
|
|
97
|
-
status: 400,
|
|
98
|
-
data: { error: 'Invalid parameters' }
|
|
99
|
-
},
|
|
100
|
-
isAxiosError: true
|
|
101
|
-
};
|
|
102
|
-
mockedAxios.get.mockRejectedValue(error);
|
|
103
|
-
// @ts-expect-error - Mock type mismatch is expected in tests
|
|
104
|
-
mockedAxios.isAxiosError = jest.fn().mockReturnValue(true);
|
|
105
|
-
|
|
106
|
-
await expect(exchange.getMarketHistory('FED-25JAN-B4.75', { resolution: '1h' }))
|
|
107
|
-
.rejects
|
|
108
|
-
.toThrow(/History API Error/i);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should return empty orderbook on error', async () => {
|
|
112
|
-
mockedAxios.get.mockRejectedValue(new Error('API Error'));
|
|
113
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
114
|
-
|
|
115
|
-
const orderbook = await exchange.getOrderBook('TEST-TICKER');
|
|
116
|
-
|
|
117
|
-
expect(orderbook).toEqual({ bids: [], asks: [] });
|
|
118
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
119
|
-
consoleSpy.mockRestore();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should return empty trades on error', async () => {
|
|
123
|
-
mockedAxios.get.mockRejectedValue(new Error('API Error'));
|
|
124
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
125
|
-
|
|
126
|
-
const trades = await exchange.getTradeHistory('TEST-TICKER', { resolution: '1h' });
|
|
127
|
-
|
|
128
|
-
expect(trades).toEqual([]);
|
|
129
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
130
|
-
consoleSpy.mockRestore();
|
|
131
|
-
});
|
|
132
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { KalshiExchange } from '../../../src/exchanges/Kalshi';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Kalshi Empty Response Handling Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests how the exchange handles empty responses from the API.
|
|
8
|
-
* Why: APIs can return empty lists during maintenance or based on filters.
|
|
9
|
-
* The library must handle this gracefully without throwing errors.
|
|
10
|
-
* How: Mocks an empty events array and verifies an empty market array is returned.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
jest.mock('axios');
|
|
14
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
15
|
-
|
|
16
|
-
describe('KalshiExchange - Empty Response Handling', () => {
|
|
17
|
-
let exchange: KalshiExchange;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
exchange = new KalshiExchange();
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should handle empty events array gracefully', async () => {
|
|
25
|
-
mockedAxios.get.mockResolvedValue({
|
|
26
|
-
data: {
|
|
27
|
-
events: [],
|
|
28
|
-
cursor: ''
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const markets = await exchange.fetchMarkets();
|
|
33
|
-
expect(markets).toEqual([]);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should handle missing events field gracefully', async () => {
|
|
37
|
-
mockedAxios.get.mockResolvedValue({
|
|
38
|
-
data: {}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const markets = await exchange.fetchMarkets();
|
|
42
|
-
expect(markets).toEqual([]);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { KalshiExchange } from '../../../src/exchanges/Kalshi';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Kalshi Markets Normalization Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests if Kalshi markets are correctly normalized from the event-based structure.
|
|
8
|
-
* Why: Kalshi uses a hierarchical Event > Market structure. We need to ensured nested markets
|
|
9
|
-
* are correctly flattened and combined with their parent event metadata (title, category).
|
|
10
|
-
* How: Mocks the Kalshi /events API response with nested markets and verifies the UnifiedMarket output.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
jest.mock('axios');
|
|
14
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
15
|
-
|
|
16
|
-
describe('KalshiExchange - Fetch and Normalize Markets', () => {
|
|
17
|
-
let exchange: KalshiExchange;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
exchange = new KalshiExchange();
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should correctly flatten nested markets from events', async () => {
|
|
25
|
-
mockedAxios.get.mockResolvedValue({
|
|
26
|
-
data: {
|
|
27
|
-
events: [{
|
|
28
|
-
event_ticker: 'KXINFLATION',
|
|
29
|
-
title: 'Inflation > 3%',
|
|
30
|
-
sub_title: 'CPI exceeds 3%',
|
|
31
|
-
category: 'Economics',
|
|
32
|
-
markets: [{
|
|
33
|
-
ticker: 'KXINFLATION24',
|
|
34
|
-
yes_ask: 45,
|
|
35
|
-
yes_bid: 43,
|
|
36
|
-
volume_24h: 1000,
|
|
37
|
-
expiration_time: "2024-12-31T00:00:00Z",
|
|
38
|
-
open_interest: 500,
|
|
39
|
-
last_price: 44
|
|
40
|
-
}]
|
|
41
|
-
}],
|
|
42
|
-
cursor: ''
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const markets = await exchange.fetchMarkets();
|
|
47
|
-
|
|
48
|
-
expect(markets.length).toBe(1);
|
|
49
|
-
const m = markets[0];
|
|
50
|
-
expect(m.id).toBe('KXINFLATION24');
|
|
51
|
-
expect(m.title).toBe('Inflation > 3%');
|
|
52
|
-
expect(m.outcomes[0].price).toBe(0.44); // last_price / 100
|
|
53
|
-
expect(m.volume24h).toBe(1000);
|
|
54
|
-
expect(m.category).toBe('Economics');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { KalshiExchange } from '../../../src/exchanges/Kalshi';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Kalshi Integration Test (Live API)
|
|
5
|
-
*
|
|
6
|
-
* What: Verifies real-world connectivity and data structure from the live Kalshi API.
|
|
7
|
-
* Why: To ensure our mapping logic matches the actual live data and to detect
|
|
8
|
-
* breaking API changes early.
|
|
9
|
-
* How: Makes actual HTTP requests to Kalshi's public endpoints and validates
|
|
10
|
-
* the properties of the returned markets.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
describe('KalshiExchange - Live API Integration', () => {
|
|
14
|
-
const exchange = new KalshiExchange();
|
|
15
|
-
jest.setTimeout(30000); // Kalshi fetches all events before applying limit (needs optimization)
|
|
16
|
-
|
|
17
|
-
it('should fetch real markets with expected properties', async () => {
|
|
18
|
-
const markets = await exchange.fetchMarkets({ limit: 5 });
|
|
19
|
-
|
|
20
|
-
if (markets.length > 0) {
|
|
21
|
-
const m = markets[0];
|
|
22
|
-
expect(m.id).toBeDefined();
|
|
23
|
-
expect(typeof m.title).toBe('string');
|
|
24
|
-
expect(m.outcomes.length).toBeGreaterThan(0);
|
|
25
|
-
expect(m.volume24h).toBeGreaterThanOrEqual(0);
|
|
26
|
-
|
|
27
|
-
// Prices should be normalized to 0-1
|
|
28
|
-
m.outcomes.forEach(outcome => {
|
|
29
|
-
expect(outcome.price).toBeGreaterThanOrEqual(0);
|
|
30
|
-
expect(outcome.price).toBeLessThanOrEqual(1);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should respect the limit parameter on live data', async () => {
|
|
36
|
-
const limit = 3;
|
|
37
|
-
const markets = await exchange.fetchMarkets({ limit });
|
|
38
|
-
expect(markets.length).toBeLessThanOrEqual(limit);
|
|
39
|
-
});
|
|
40
|
-
});
|