⚠️ Risk Warning: This software places real orders with real money when dry-run flags are disabled. Always test thoroughly in dry-run mode before using live funds. You are solely responsible for any financial losses.
A TypeScript trading toolkit that monitors and executes cross-venue arbitrage on Bitcoin 15-minute direction markets across Kalshi and Polymarket. It provides venue monitoring, single-order execution helpers, and configurable arbitrage logic.
- How It Works
- Prerequisites
- Installation
- Configuration
- Running the Bot
- NPM Scripts
- Usage Examples
- Programmatic API
- Logging & Process Management
- Tech Stack
- References
- Disclaimer
The bot simultaneously monitors complementary prediction market legs across two venues:
- Kalshi — trades the
KXBTC15Mseries (Bitcoin price UP or DOWN in a 15-minute window). - Polymarket — trades the corresponding BTC 15-minute DOWN token via the CLOB API.
When the combined best-ask price of two opposite legs (e.g., Kalshi UP + Polymarket DOWN) falls within a configured profitable range, the bot places limit orders on both venues simultaneously to lock in a risk-free spread.
Arbitrage trigger logic:
ARB_SUM_LOW ≤ (kalshi_ask + polymarket_ask) ≤ ARB_SUM_THRESHOLD
If the sum of both legs is below ARB_SUM_THRESHOLD (default: 0.92), one of the legs is mispriced relative to the other, creating a potential arbitrage opportunity. The bot executes both orders when the combined price also exceeds ARB_SUM_LOW (default: 0.75) to filter out stale or illiquid quotes.
- Node.js v18 or later
- npm v8 or later
- A funded Kalshi account with API access enabled
- A funded Polymarket account with an Ethereum-compatible wallet (private key)
git clone https://github.com/your-org/your-repo.git
cd your-repo
npm install
cp .env.example .envThen open .env and fill in your credentials (see Configuration below).
All settings are controlled via environment variables in your .env file. A complete template is provided in .env.example.
| Variable | Description |
|---|---|
KALSHI_API_KEY |
Your Kalshi API key |
KALSHI_PRIVATE_KEY_PATH |
Path to your RSA private key file |
KALSHI_PRIVATE_KEY_PEM |
Inline PEM string (alternative to PATH) |
Provide either
KALSHI_PRIVATE_KEY_PATHorKALSHI_PRIVATE_KEY_PEM— not both.
| Variable | Default | Description |
|---|---|---|
KALSHI_DEMO |
false |
Set to true to use the Kalshi demo environment |
KALSHI_BASE_PATH |
Kalshi default | Override the Kalshi API base URL |
| Variable | Description |
|---|---|
POLYMARKET_PRIVATE_KEY |
Ethereum wallet private key (hex, with or without 0x prefix) |
| Variable | Default | Description |
|---|---|---|
POLYMARKET_PROXY |
— | HTTP proxy URL for outbound Polymarket requests |
POLYMARKET_CLOB_URL |
Polymarket default | Override the CLOB API base URL |
POLYMARKET_CHAIN_ID |
Polymarket default | Chain ID for order signing |
POLYMARKET_TICK_SIZE |
Polymarket default | Minimum price increment |
POLYMARKET_NEG_RISK |
false |
Enable negative-risk market handling |
POLYMARKET_CREDENTIAL_PATH |
— | Path to saved CLOB credentials JSON |
POLYMARKET_MIN_USD |
— | Minimum USD order size |
| Variable | Default | Description |
|---|---|---|
KALSHI_BOT_SIDE |
yes |
Order side: yes or no |
KALSHI_BOT_PRICE_CENTS |
— | Limit price in cents (e.g., 48 = $0.48) |
KALSHI_BOT_CONTRACTS |
1 |
Number of contracts to order |
KALSHI_BOT_MAX_MARKETS |
1 |
Maximum number of open markets to target |
KALSHI_BOT_DRY_RUN |
false |
If true, logs the order without submitting it |
| Variable | Default | Description |
|---|---|---|
KALSHI_MONITOR_INTERVAL_MS |
200 |
Polling frequency in milliseconds |
KALSHI_MONITOR_TICKER |
— | Pin the monitor to a specific market ticker; auto-rotates if unset |
KALSHI_MONITOR_NO_RESTART |
false |
Disable automatic restart at quarter-hour boundaries |
| Variable | Default | Description |
|---|---|---|
ARB_SUM_THRESHOLD |
0.92 |
Maximum combined ask price that triggers arbitrage |
ARB_SUM_LOW |
0.75 |
Minimum combined ask price (filters illiquid quotes) |
ARB_PRICE_BUFFER |
0 |
Additional price buffer added to each leg's order price |
ARB_SIZE |
1 |
Number of contracts/shares to trade per arb execution |
ARB_DRY_RUN |
false |
If true, logs arb opportunities without placing orders |
npm startThis starts high-frequency polling of both venues, logs pricing data, and automatically executes arbitrage trades when conditions are met.
npm run balance# Dry-run a Kalshi order (no real order placed)
KALSHI_BOT_DRY_RUN=true npm run kalshi-single-order
# Dry-run a Polymarket order
ARB_DRY_RUN=true npm run poly-single-order| Command | Description |
|---|---|
npm start |
Start the dual-venue monitor with integrated arbitrage logic |
npm run balance |
Print your current Kalshi portfolio balance |
npm run kalshi-single-order |
Place one Kalshi limit order on the first open KXBTC15M market |
npm run poly-single-order |
Place one Polymarket DOWN limit buy (accepts optional [price] [size] args) |
npm run build |
Compile TypeScript source to dist/ |
# --- Kalshi ---
# Dry-run: log the order, do not submit
KALSHI_BOT_DRY_RUN=true npm run kalshi-single-order
# Place a YES order at $0.50 for 2 contracts
KALSHI_BOT_SIDE=yes KALSHI_BOT_PRICE_CENTS=50 KALSHI_BOT_CONTRACTS=2 npm run kalshi-single-order
# Place a NO order at $0.44 for 1 contract
KALSHI_BOT_SIDE=no KALSHI_BOT_PRICE_CENTS=44 KALSHI_BOT_CONTRACTS=1 npm run kalshi-single-order
# --- Polymarket ---
# Place a DOWN order at $0.45 for 10 shares
npm run poly-single-order 0.45 10
# Place with default price and size (from .env)
npm run poly-single-order
# --- Full arbitrage monitor with custom thresholds ---
ARB_SUM_THRESHOLD=0.90 ARB_SUM_LOW=0.78 ARB_SIZE=5 npm startYou can import individual modules to build your own workflows:
// Place a Kalshi limit order
import { placeOrder } from './bot'; // or './orders'
await placeOrder(ticker, side, count, priceCents, options?);
// Place a Polymarket limit order
import { placePolymarketOrder } from './polymarket-order'; // or './orders'
await placePolymarketOrder(tokenId, price, size, options?);
// Resolve a Polymarket market slug to token IDs (cached)
import { getTokenIdsForSlugCached } from './polymarket-monitor';
const { upTokenId, downTokenId } = await getTokenIdsForSlugCached(slug);- Log files are written to
logs/monitor_YYYY-MM-DD_HH-{00|15|30|45}.log, rotating every 15 minutes in sync with market expiry windows. - Console output mirrors log file content in real time.
- Lock file at
logs/monitor.lockensures only one monitor process runs at a time. If the process crashes, delete this file manually before restarting. - The monitor automatically restarts at quarter-hour boundaries to pick up new market tickers, unless
KALSHI_MONITOR_NO_RESTART=trueis set or a specific ticker is pinned viaKALSHI_MONITOR_TICKER.
| Package | Purpose |
|---|---|
| TypeScript | Primary language |
| kalshi-typescript | Kalshi REST API client |
| @polymarket/clob-client | Polymarket CLOB API client |
| ethers | Ethereum wallet & order signing |
- Kalshi API Documentation
- Kalshi TypeScript SDK Quickstart
- Kalshi WebSocket Documentation
- Polymarket CLOB API
This project is provided for educational and research purposes only. It is not financial advice. Prediction market arbitrage carries real financial risk, including but not limited to: execution risk, API downtime, slippage, liquidity gaps, and rapid market movement.
- Always start with
DRY_RUN=trueflags enabled and verify the logged output is correct before disabling them. - Use conservative sizing (
ARB_SIZE,KALSHI_BOT_CONTRACTS) when going live for the first time. - Never risk capital you cannot afford to lose.
- Ensure your use of these APIs complies with Kalshi's and Polymarket's respective Terms of Service.
The authors of this software accept no liability for financial losses incurred through its use.