Claude
Skills
Sign in
Back

polymarket-arbitrage-trading-bot

Included with Lifetime
$97 forever

Automated dump-and-hedge arbitrage trading bot for Polymarket's 15-minute crypto Up/Down markets, supporting BTC, ETH, SOL, and XRP.

Web3

What this skill does


# Polymarket Arbitrage Trading Bot

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

Automated dump-and-hedge arbitrage bot for Polymarket's 15-minute crypto Up/Down prediction markets. Written in TypeScript using the official `@polymarket/clob-client`. Watches BTC, ETH, SOL, and XRP markets for sharp price drops on one leg, then buys both legs when combined cost falls below a target threshold to lock in a structural edge before resolution.

---

## Installation

```bash
git clone https://github.com/apechurch/polymarket-arbitrage-trading-bot.git
cd polymarket-arbitrage-trading-bot
npm install
cp .env.example .env
# Configure .env — see Configuration section
npm run build
```

**Requirements:** Node.js 16+, USDC on Polygon (for live trading), a Polymarket-compatible wallet.

---

## Project Structure

```text
src/
  main.ts              # Entry point: market discovery, monitors, period rollover
  monitor.ts           # Price polling & snapshots
  dumpHedgeTrader.ts   # Core strategy: dump → hedge → stop-loss → settlement
  api.ts               # Gamma API, CLOB API, order placement, redemption
  config.ts            # Environment variable loading
  models.ts            # Shared TypeScript types
  logger.ts            # History file (history.toml) + stderr logging
```

---

## Key Commands

| Command | Purpose |
|---------|---------|
| `npm run dev` | Run via `ts-node` (development, no build needed) |
| `npm run build` | Compile TypeScript to `dist/` |
| `npm run typecheck` | Type-check without emitting output |
| `npm run clean` | Remove `dist/` directory |
| `npm run sim` | **Simulation mode** — logs trades, no real orders |
| `npm run prod` | **Production mode** — places real CLOB orders |
| `npm start` | Run compiled output (defaults to simulation unless `--production` passed) |

---

## Configuration (`.env`)

```bash
# Wallet / Auth
PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE
PROXY_WALLET_ADDRESS=0xYOUR_PROXY_WALLET
SIGNATURE_TYPE=2          # 0=EOA, 1=Proxy, 2=Gnosis Safe

# Markets to trade (comma-separated)
MARKETS=btc,eth,sol,xrp

# Polling
CHECK_INTERVAL_MS=1000

# Strategy thresholds
DUMP_HEDGE_SHARES=10                    # Shares per leg
DUMP_HEDGE_SUM_TARGET=0.95             # Max combined price for both legs
DUMP_HEDGE_MOVE_THRESHOLD=0.15         # Min fractional drop to trigger (15%)
DUMP_HEDGE_WINDOW_MINUTES=5            # Only detect dumps in first N minutes of round
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES=8 # Force stop-loss hedge after N minutes

# Mode flag (use --production CLI flag for live trading)
PRODUCTION=false

# Optional API overrides
GAMMA_API_URL=https://gamma-api.polymarket.com
CLOB_API_URL=https://clob.polymarket.com
API_KEY=
API_SECRET=
API_PASSPHRASE=
```

---

## Strategy Overview

```text
New 15m round starts
        │
        ▼
Watch first DUMP_HEDGE_WINDOW_MINUTES minutes
        │
        ├── Up or Down leg drops ≥ DUMP_HEDGE_MOVE_THRESHOLD?
        │         │
        │         ▼
        │   Buy dumped leg (Leg 1)
        │         │
        │         ├── Opposite ask cheap enough?
        │         │   (leg1_entry + opposite_ask ≤ DUMP_HEDGE_SUM_TARGET)
        │         │         │
        │         │         ▼
        │         │   Buy hedge leg (Leg 2) → locked-in edge
        │         │
        │         └── Timeout (DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES)?
        │                   │
        │                   ▼
        │           Execute stop-loss hedge
        │
        └── Round ends → settle winners, redeem on-chain (production)
```

---

## Code Examples

### Loading Config (`src/config.ts` pattern)

```typescript
import * as dotenv from 'dotenv';
dotenv.config();

export const config = {
  privateKey: process.env.PRIVATE_KEY!,
  proxyWalletAddress: process.env.PROXY_WALLET_ADDRESS ?? '',
  signatureType: parseInt(process.env.SIGNATURE_TYPE ?? '2', 10),
  markets: (process.env.MARKETS ?? 'btc').split(',').map(m => m.trim()),
  checkIntervalMs: parseInt(process.env.CHECK_INTERVAL_MS ?? '1000', 10),
  dumpHedgeShares: parseFloat(process.env.DUMP_HEDGE_SHARES ?? '10'),
  dumpHedgeSumTarget: parseFloat(process.env.DUMP_HEDGE_SUM_TARGET ?? '0.95'),
  dumpHedgeMoveThreshold: parseFloat(process.env.DUMP_HEDGE_MOVE_THRESHOLD ?? '0.15'),
  dumpHedgeWindowMinutes: parseInt(process.env.DUMP_HEDGE_WINDOW_MINUTES ?? '5', 10),
  dumpHedgeStopLossMaxWaitMinutes: parseInt(
    process.env.DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES ?? '8', 10
  ),
  production: process.env.PRODUCTION === 'true',
};
```

### Initializing the CLOB Client

```typescript
import { ClobClient } from '@polymarket/clob-client';
import { ethers } from 'ethers';
import { config } from './config';

function createClobClient(): ClobClient {
  const wallet = new ethers.Wallet(config.privateKey);
  return new ClobClient(
    config.clobApiUrl,         // e.g. 'https://clob.polymarket.com'
    137,                        // Polygon chain ID
    wallet,
    undefined,                  // credentials (set after key derivation if needed)
    config.signatureType,
    config.proxyWalletAddress
  );
}
```

### Discovering the Active 15-Minute Market

```typescript
import axios from 'axios';

interface GammaMarket {
  conditionId: string;
  question: string;
  endDateIso: string;
  active: boolean;
  tokens: Array<{ outcome: string; token_id: string }>;
}

async function findActive15mMarket(asset: string): Promise<GammaMarket | null> {
  const tag = `${asset.toUpperCase()}-15m`;
  const resp = await axios.get(`${config.gammaApiUrl}/markets`, {
    params: { tag, active: true, limit: 5 }
  });
  const markets: GammaMarket[] = resp.data;
  // Return the earliest-closing active market
  return markets.sort(
    (a, b) => new Date(a.endDateIso).getTime() - new Date(b.endDateIso).getTime()
  )[0] ?? null;
}
```

### Fetching Best Ask Price from CLOB

```typescript
async function getBestAsk(tokenId: string): Promise<number | null> {
  try {
    const resp = await axios.get(`${config.clobApiUrl}/book`, {
      params: { token_id: tokenId }
    });
    const asks: Array<{ price: string; size: string }> = resp.data.asks ?? [];
    if (asks.length === 0) return null;
    // Best ask = lowest price
    return Math.min(...asks.map(a => parseFloat(a.price)));
  } catch {
    return null;
  }
}
```

### Dump Detection Logic

```typescript
interface PriceSnapshot {
  timestamp: number;
  ask: number;
}

function detectDump(
  history: PriceSnapshot[],
  currentAsk: number,
  threshold: number,
  windowMs: number
): boolean {
  const cutoff = Date.now() - windowMs;
  const recent = history.filter(s => s.timestamp >= cutoff);
  if (recent.length === 0) return false;
  const highestRecentAsk = Math.max(...recent.map(s => s.ask));
  const drop = (highestRecentAsk - currentAsk) / highestRecentAsk;
  return drop >= threshold;
}

// Usage:
const windowMs = config.dumpHedgeWindowMinutes * 60 * 1000;
const isDump = detectDump(
  priceHistory,
  currentAsk,
  config.dumpHedgeMoveThreshold,
  windowMs
);
```

### Placing a Market Buy Order (Production)

```typescript
import { ClobClient, OrderType, Side } from '@polymarket/clob-client';

async function buyShares(
  client: ClobClient,
  tokenId: string,
  price: number,
  shares: number,
  simulate: boolean
): Promise<string | null> {
  if (simulate) {
    console.error(`[SIM] BUY ${shares} shares @ ${price} token=${tokenId}`);
    return 'sim-order-id';
  }
  const order = await client.createOrder({
    tokenID: tokenId,
    price,
    size: shares,
    side: Side.BUY,
    orderType: OrderType.FOK,   // Fill-or-Kill for immediate execution
  });
  const resp = await client.postOrder(order);
  return resp.orderID ?? null;
}
```

### Core Dump-Hedge Cycle

```typescript
interface LegState {
  filled: boolean;
  tokenId: string;
  entryPrice: number | null;
  orderId: string | null;
}

async function runDumpHedgeCycle(
  client: ClobClient,
  upTokenId: string,
  downTokenId: string,
  simulate: boolean
): Promise<void

Related in Web3