A professional TypeScript-based arbitrage detection and execution bot that connects to Kalshi and Polymarket prediction markets. The bot identifies cross-platform arbitrage opportunities and can automatically execute trades with comprehensive safety features.
This bot continuously monitors prediction markets on both Kalshi and Polymarket, normalizes prices to a common probability scale (0-1), applies realistic fee and gas cost models, maps equivalent markets across platforms, and identifies arbitrage opportunities with net edge calculations. It supports both paper trading (simulation) and live trading (real money) modes.
- β Dual Platform Connection - Authenticated API connections to Kalshi and Polymarket
- β Complete Market Fetching - Fetches up to 1500 markets per platform from Sports and Politics categories
- β Price Normalization - Converts all prices to 0-1 probability range for cross-platform comparison
- β Fee + Gas Model - Realistic cost modeling (Kalshi: 0.5% fee, Polymarket: 0.7% fee + 0.1% gas)
- β Market Mapping - Text normalization to match equivalent markets across platforms
- β Arbitrage Detection - Finds opportunities with net edge calculation (after fees + gas)
- β Paper Trading - Simulated trading with P&L tracking and position management
- β Live Trading - Real trade execution with hedged execution and safety features
- β Web Dashboard - Real-time monitoring dashboard with WebSocket updates
- β 24/7 Operation - Designed for continuous operation with error handling
- β Structured Logging - Winston-based logging to separate files
- β Dynamic Intervals - 5-minute base interval, switches to 1-minute in final hour
- Installation
- Configuration
- Usage
- Web Dashboard
- Trading Modes
- Architecture
- API Reference
- Examples
- Deployment
- Troubleshooting
- Development
- Node.js 18+ (ES2022 modules support)
- npm or yarn
- Kalshi API Key (get from Kalshi API)
-
Clone the repository:
git clone <repository-url> cd arbitrage-bot
-
Install dependencies:
npm install
-
Create
.envfile:cp .env.example .env # Edit .env with your configuration -
Build the project:
npm run build
Create a .env file in the project root with the following variables:
# ============================================
# API Configuration (Required)
# ============================================
KALSHI_API_KEY=your_kalshi_api_key_here
KALSHI_API_KEY_ID=your_kalshi_api_key_id_here
KALSHI_BASE_URL=https://trading-api.kalshi.com/trade-api/v2
POLY_GAMMA_URL=https://gamma-api.polymarket.com
POLY_CLOB_URL=https://clob.polymarket.com
# ============================================
# Polymarket Live Trading (Optional)
# ============================================
POLYMARKET_PRIVATE_KEY=your_ethereum_private_key_here
# ============================================
# Bot Settings
# ============================================
# Run mode: true = run once and exit, false = continuous loop (24-hour mode)
RUN_ONCE=false
# Active categories to fetch (comma-separated)
ACTIVE_CATEGORIES=sports,politics
# Market limits
TARGET_MARKETS_PER_PLATFORM=1500 # Markets per platform (1000-2000 recommended)
# Time filtering (optional)
MIN_MINUTES_TO_END=5 # Ignore markets resolving sooner than 5 minutes
MAX_HOURS_TO_END=720 # Ignore markets resolving later than 30 days
# ============================================
# Trading Configuration
# ============================================
# Enable paper trading (simulation mode)
PAPER_TRADING_ENABLED=true
# Enable live trading (real money - USE WITH CAUTION)
LIVE_TRADING_ENABLED=false
# Minimum net arbitrage edge to trade (0.01 = 1%)
MIN_ARBITRAGE_EDGE=0.01
# Safety limits
MAX_EXPOSURE=10.0 # Maximum total exposure
MAX_POSITION_SIZE=1.0 # Maximum position size per trade
# ============================================
# Fee & Gas Configuration
# ============================================
# Trading fees (as probability adjustments, e.g., 0.005 = 0.5%)
KALSHI_FEE_PROB=0.005 # 0.5% trading fee
POLYMARKET_FEE_PROB=0.007 # 0.7% trading fee
# Gas costs (blockchain transaction costs)
KALSHI_GAS_PROB=0 # 0% (centralized platform, no gas)
POLYMARKET_GAS_PROB=0.001 # 0.1% (Polygon gas cost)
# ============================================
# Web Dashboard
# ============================================
WEB_SERVER_PORT=3000KALSHI_API_KEY(Required): Your Kalshi API key. Get it from Kalshi Trading API.KALSHI_API_KEY_ID(Required for live trading): Your Kalshi API key ID.KALSHI_BASE_URL: Kalshi API base URL (default provided).POLY_GAMMA_URL: Polymarket Gamma API URL for market discovery (default provided).POLY_CLOB_URL: Polymarket CLOB API URL for orderbook data (default provided).POLYMARKET_PRIVATE_KEY(Required for live trading): Your Ethereum wallet private key for Polymarket.
RUN_ONCE: Set totrueto run one cycle and exit (useful for testing). Default:false(24-hour continuous mode).ACTIVE_CATEGORIES: Comma-separated list of categories to fetch. Options:sports,politics. Default:sports,politics.TARGET_MARKETS_PER_PLATFORM: Number of markets to fetch per platform. Default:1500. Recommended: 1000-2000.MIN_MINUTES_TO_END: Ignore markets resolving sooner than this many minutes. Default:5.MAX_HOURS_TO_END: Ignore markets resolving later than this many hours. Default:720(30 days).
PAPER_TRADING_ENABLED: Enable paper trading (simulation mode). Default:true(recommended for testing).LIVE_TRADING_ENABLED: Enable live trading (real money). Default:false. Use with extreme caution!MIN_ARBITRAGE_EDGE: Minimum net edge (after fees + gas) to trade. Default:0.01(1%).MAX_EXPOSURE: Maximum total exposure across all positions. Default:10.0.MAX_POSITION_SIZE: Maximum position size per trade. Default:1.0.
KALSHI_FEE_PROB: Kalshi trading fee as probability adjustment. Default:0.005(0.5%).POLYMARKET_FEE_PROB: Polymarket trading fee as probability adjustment. Default:0.007(0.7%).KALSHI_GAS_PROB: Kalshi gas cost (always 0, centralized platform). Default:0.POLYMARKET_GAS_PROB: Polymarket gas cost (Polygon blockchain). Default:0.001(0.1%).
Run the bot continuously for 24 hours:
npm startThe bot will:
- Start web dashboard on
http://localhost:3000 - Fetch up to 1500 markets per platform from Sports and Politics categories
- Detect arbitrage opportunities
- Execute paper trades (if enabled) or live trades (if enabled)
- Wait 5 minutes before next cycle (or 1 minute in final hour)
- Continue for 24 hours, then automatically complete
Run the bot once and exit:
RUN_ONCE=true npm startRun with auto-reload on file changes:
npm run dev:botThe bot includes a real-time web dashboard for monitoring:
Open your browser to:
http://localhost:3000
- β Real-time Status - Bot running/stopped status
- β Market Statistics - Live market counts from both platforms
- β Arbitrage Opportunities - Recent opportunities with details
- β Trade History - Paper and live trades with P&L
- β Live Updates - WebSocket-based real-time updates
- β Cycle Information - Current cycle progress and timing
GET /api/status- Bot status and statisticsGET /api/opportunities- Recent arbitrage opportunitiesGET /api/trades- Recent trades (paper and live)GET /api/dashboard- Complete dashboard data
Paper trading mode simulates trades without using real money:
Features:
- β Simulated trade execution
- β P&L tracking
- β Position management
- β Win rate calculation
- β Exposure limits
Enable:
PAPER_TRADING_ENABLED=trueLive trading executes real trades on both platforms:
Features:
- β Real trade execution
- β Hedged execution (simultaneous buy/sell)
- β Safety features (circuit breakers, slippage guards)
- β Position tracking
- β Error handling and recovery
Enable:
LIVE_TRADING_ENABLED=true
POLYMARKET_PRIVATE_KEY=your_private_key_here
KALSHI_API_KEY_ID=your_key_id_hereSafety Features:
- Maximum exposure limits
- Position size limits
- Slippage protection
- Circuit breakers
- Automatic error recovery
View logs in real-time in your browser using Frontail:
-
Install Frontail globally (one-time setup):
npm install -g frontail
-
Start the log viewer (in a separate terminal):
npm run logs
This will watch both
logs/bot.logandlogs/arbitrage.log -
Open your browser:
http://localhost:9001 -
View specific log files:
# View only bot.log npm run logs:bot # View only arbitrage.log npm run logs:arbitrage
Frontail provides a beautiful web interface to view log files in real-time with:
- Auto-refresh as new logs are written
- Search and filtering capabilities
- Color-coded log levels
- Multiple log file support
Note: Keep Frontail running in a separate terminal while your bot is running to see logs in real-time.
The bot outputs to:
- Console: Real-time progress and results
logs/bot.log: Bot lifecycle eventslogs/market_fetcher.log: Market fetching detailslogs/arbitrage.log: Arbitrage opportunities (structured JSON)
======================================================================
π€ Dual-Market Bot
======================================================================
π Fetching ALL markets from Kalshi and Polymarket (no pagination limits)
π·οΈ Active Categories: SPORTS, POLITICS
β±οΈ Update interval: 300 seconds
======================================================================
[2025-12-17T15:00:00.000Z] Starting market fetch cycle...
Active Categories: SPORTS, POLITICS
======================================================================
[KALSHI] Starting market fetch...
[KALSHI] Fetching ALL markets from Sports and Politics categories (no limit)
[KALSHI] Fetching page 1... (0 normalized so far, 0 total fetched)
[KALSHI] Page 1: Received 1000 markets (Total fetched so far: 1000)
[KALSHI] [1] Processing: "Will Team A win the championship?"
...
[POLYMARKET] Starting market fetch...
[POLYMARKET] Fetching ALL open markets from Sport and Politics categories (no limit)
[POLYMARKET API] Requesting markets from https://gamma-api.polymarket.com/markets for category=Sport (active=true&closed=false)
[POLYMARKET API] Requesting markets from https://gamma-api.polymarket.com/markets for category=Politics (active=true&closed=false)
...
======================================================================
π FINAL SUMMARY
======================================================================
Kalshi Markets: 245
Polymarket Markets: 189
Total Markets: 434
======================================================================
π° Arbitrage opportunities found: 3
π‘ Arbitrage opportunity on "Will Team A win?" [YES]:
BUY KALSHI @ eff=0.5050 (raw ask=0.5000, fee=0.50% + gas=0.00%)
SELL POLYMARKET @ eff=0.4920 (raw bid=0.5000, fee=0.70% + gas=0.10%)
Raw edge: 0.00% | Net edge (after fees+gas): 1.30%
βΈοΈ 3 arbitrage opportunity/opportunities found. Pausing for 300 seconds (5 minutes) before next cycle...
arbitrage-bot/
βββ src/
β βββ api/ # API clients
β β βββ kalshiApi.ts # Kalshi Trading API
β β βββ polymarketGamma.ts # Polymarket Gamma API (market discovery)
β β βββ polymarketClobClient.ts # Polymarket CLOB API (orderbooks)
β β
β βββ config/
β β βββ settings.ts # Configuration & environment variables
β β
β βββ models/
β β βββ NormalizedMarket.ts # Market data model
β β
β βββ services/
β β βββ marketFetcher.ts # Market fetching & normalization
β β βββ arbitrageFinder.ts # Arbitrage detection with fee/gas model
β β βββ paperTrading.ts # Paper trading engine
β β βββ liveTrading.ts # Live trading engine
β β
β βββ server/
β β βββ webServer.ts # Web dashboard server (Express + WebSocket)
β β
β βββ types/
β β βββ index.ts # TypeScript type definitions
β β
β βββ utils/
β β βββ logger.ts # Winston logger setup
β β βββ priceNormalizer.ts # Price normalization functions
β β βββ marketFilter.ts # Category-based market filtering
β β βββ polymarketSigning.ts # EIP-712 signing for Polymarket
β β βββ kalshiSigning.ts # RSA signing for Kalshi
β β
β βββ bot.ts # Main bot entry point
β
βββ public/
β βββ index.html # Web dashboard frontend
β
βββ logs/ # Log files
β βββ bot.log # Bot lifecycle events
β βββ market_fetcher.log # Market fetching details
β βββ arbitrage.log # Arbitrage opportunities (JSON)
β βββ paper_trading.log # Paper trading activity
β βββ live_trading.log # Live trading activity
βββ dist/ # Compiled JavaScript (generated)
βββ .env # Environment variables
βββ tsconfig.json # TypeScript configuration
βββ package.json # Dependencies & scripts
βββ README.md # This file
START
β
[Load Config] β [Initialize Logging] β [Start Web Server] β [Check Run Mode]
β
βββββββββββββββββββββββββββββββββββββββββββ
β MARKET FETCH CYCLE β
β βββββββββββββββββββββββββββββββββββββ β
β β Fetch Kalshi Markets (Parallel) β β
β β - Paginate through pages β β
β β - Filter: Sports + Politics β β
β β - Fetch orderbooks β β
β β - Normalize prices (0-100 β 0-1) β β
β βββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
β β Fetch Polymarket Markets (Parallel)β
β β - Fetch open Sport markets β β
β β - Fetch open Politics markets β β
β β - Fetch orderbooks (CLOB) β β
β β - Normalize prices (clamp 0-1) β β
β βββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
β β Combine Markets β β
β βββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β ARBITRAGE DETECTION β
β - Map equivalent markets β
β - Apply fees + gas model β
β - Compute net arbitrage edge β
β - Filter by MIN_ARBITRAGE_EDGE β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β TRADE EXECUTION β
β βββββββββββββββββββββββββββββββββββββ β
β β Paper Trading (if enabled) β β
β β - Simulate trade execution β β
β β - Track P&L β β
β β - Update positions β β
β βββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
β β Live Trading (if enabled) β β
β β - Execute hedged trades β β
β β - Apply safety features β β
β β - Track positions β β
β βββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
β
[Update Web Dashboard] β [Display Results]
β
βββββββββββββββββββββββββββββββββββββββββββ
β Wait Dynamic Interval β
β - Base: 5 minutes β
β - Final Hour: 1 minute β
β β Loop back β
βββββββββββββββββββββββββββββββββββββββββββ
- API Clients (
src/api/): Function-based API clients for Kalshi and Polymarket - Market Fetcher (
src/services/marketFetcher.ts): Orchestrates fetching and normalization - Arbitrage Finder (
src/services/arbitrageFinder.ts): Detects opportunities with fee/gas model - Paper Trading (
src/services/paperTrading.ts): Simulated trading engine with P&L tracking - Live Trading (
src/services/liveTrading.ts): Real trade execution with safety features - Web Server (
src/server/webServer.ts): Express server with WebSocket for real-time dashboard - Price Normalizer (
src/utils/priceNormalizer.ts): Converts prices to 0-1 range - Market Filter (
src/utils/marketFilter.ts): Category-based filtering for Kalshi - Signing Utils (
src/utils/): EIP-712 (Polymarket) and RSA (Kalshi) signing for live trading
Fetches and normalizes Kalshi markets.
Parameters:
fastMode(optional): Iftrue, skips orderbook fetching for speed. Default:false.
Returns: Array of normalized Kalshi markets.
Example:
const markets = await fetchNormalizedKalshiMarkets(true);Fetches and normalizes Polymarket markets from Sport and Politics categories. Only fetches open markets (active=true&closed=false) to reduce markets with no price data.
Returns: Array of normalized Polymarket markets.
Example:
const markets = await fetchNormalizedPolymarketMarkets();Detects arbitrage opportunities in a set of normalized markets.
Parameters:
markets: Array of normalized markets from both platforms.
Returns: Array of arbitrage opportunities.
Example:
const opportunities = findArbitrageOpportunities(allMarkets);Computes effective prices after applying fees and gas costs.
Parameters:
market: Normalized market object.
Returns: Object with raw and effective prices, plus fee and gas breakdown.
Example:
const prices = computeEffectivePrices(market);
// prices.effBid = market.best_bid - fee - gas
// prices.effAsk = market.best_ask + fee + gasinterface NormalizedMarket {
platform: 'kalshi' | 'polymarket';
market_id: string;
question: string; // Market question/title
outcome: 'YES' | 'NO' | 'UP' | 'DOWN' | string;
best_bid: number | null; // Normalized to 0-1
best_ask: number | null; // Normalized to 0-1
implied_probability: number | null; // Normalized to 0-1
effective_bid?: number | null; // After fees + gas
effective_ask?: number | null; // After fees + gas
}interface ArbitrageOpportunity {
eventKey: string; // Normalized event identifier
buyLeg: {
platform: 'kalshi' | 'polymarket';
market_id: string;
question: string;
outcome: string;
side: 'BUY';
price: number; // Raw ask price
effectivePrice: number; // After fees + gas
};
sellLeg: {
platform: 'kalshi' | 'polymarket';
market_id: string;
question: string;
outcome: string;
side: 'SELL';
price: number; // Raw bid price
effectivePrice: number; // After fees + gas
};
rawEdge: number; // sellBid - buyAsk (before fees)
netEdge: number; // sellEffBid - buyEffAsk (after fees + gas)
}# Set up .env file with your API key
echo "KALSHI_API_KEY=your_key_here" > .env
# Run the bot
npm start# Run once and exit
RUN_ONCE=true npm start# .env file
KALSHI_API_KEY=your_key_here
FETCH_INTERVAL_SECONDS=600 # Wait 10 minutes when opportunities found
MIN_ARBITRAGE_EDGE=0.02 # Only log opportunities with 2%+ net edge
ACTIVE_CATEGORIES=sports # Only fetch sports markets# Watch for file changes and auto-reload
npm run dev:botProblem: Missing or invalid Kalshi API key.
Solution:
- Ensure
.envfile exists in project root - Add
KALSHI_API_KEY=your_key_hereto.env - Get your API key from Kalshi API Documentation
Problem: API endpoint issue or no markets matching filters.
Solution:
- Check API URLs in
.envare correct - Verify
ACTIVE_CATEGORIESincludes valid categories (sports,politics) - Check network connectivity
- Review
logs/market_fetcher.logfor detailed error messages
Problem: Polymarket API not returning markets.
Solution:
- Verify
POLY_GAMMA_URLis correct:https://gamma-api.polymarket.com - Check that categories are correct:
SportandPolitics(case-sensitive in API) - Note: The bot only fetches open markets (
active=true&closed=false) to reduce markets with no price data - Review
logs/market_fetcher.logfor API response details
Problem: TypeScript compilation issue or missing build.
Solution:
# Rebuild the project
npm run build
# Or clean and rebuild
npm run clean
npm run buildProblem: Loop timing not optimal.
Solution:
- Adjust
FETCH_INTERVAL_SECONDSin.env - When opportunities found, bot waits this many seconds
- When no opportunities, bot continues immediately
Check log files for detailed information:
logs/bot.log: Bot lifecycle, errors, and general eventslogs/market_fetcher.log: Market fetching details, API responses, filtering statslogs/arbitrage.log: Arbitrage opportunities in structured JSON format
Enable verbose logging by modifying src/utils/logger.ts:
// Change log level to 'debug'
const logger = setupLogger('bot', 'logs/bot.log', 'debug');# Build TypeScript to JavaScript
npm run build
# Output goes to dist/ directory# Watch mode (auto-rebuild on changes)
npm run dev
# Watch mode with auto-reload bot
npm run dev:bot
# Clean build artifacts
npm run clean- Function-based: No classes (except error classes), pure functions
- TypeScript: Full type safety with strict mode
- Modular: Clear separation of concerns
- Read-only: All operations are GET requests, no trading
- New Category: Add keywords to
MARKET_CATEGORIESinsrc/config/settings.ts - New Platform: Create API client in
src/api/, add tomarketFetcher.ts - New Filter: Add function to
src/utils/marketFilter.ts - New Normalizer: Add function to
src/utils/priceNormalizer.ts
Currently, use RUN_ONCE=true mode for testing:
RUN_ONCE=true npm startThis runs one complete cycle and exits, useful for:
- Testing configuration
- Verifying API connections
- Checking market fetching
- Validating arbitrage detection
ISC
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
For issues, questions, or contributions:
- Check
logs/directory for detailed error messages - Review
CODEBASE_SUMMARY.mdfor architecture details - Open an issue on GitHub
The bot is designed for continuous operation. For production deployment:
-
Install PM2 (process manager):
npm install -g pm2
-
Build the project:
npm run build
-
Start with PM2:
pm2 start dist/bot.js --name arbitrage-bot
-
Monitor:
pm2 logs arbitrage-bot pm2 monit
-
Auto-start on reboot:
pm2 save pm2 startup
# Build
docker build -t arbitrage-bot .
# Run
docker run -d --name arbitrage-bot --restart unless-stopped \
-v $(pwd)/.env:/app/.env \
-v $(pwd)/logs:/app/logs \
-p 3000:3000 \
arbitrage-botMilestone 1: Read-only Arbitrage Detector β COMPLETE
- β Full Data Ingestion
- β Normalized Markets/Orderbooks
- β Fee/Gas Modeling
- β Mapped Equivalent Markets
- β Net Arbitrage Edge Calculation
- β Logged Signals
Milestone 2: Paper Trading β COMPLETE
- β Simulated Trade Execution
- β P&L Tracking
- β Position Management
- β Statistics and Reporting
Milestone 3: Live Trading β COMPLETE
- β Real Trade Execution
- β Hedged Execution
- β Safety Features
- β Error Handling
- β Position Tracking
Note: The bot supports both paper trading (simulation) and live trading (real money). Always test with paper trading first before enabling live trading.