polymarket-arbitrage-bot
TypeScript bot implementing dump-and-hedge arbitrage strategy on Polymarket 15-minute Up/Down prediction markets with CLOB order execution and simulation mode.
What this skill does
# Polymarket Arbitrage Bot
> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.
TypeScript bot automating the **dump-and-hedge** strategy on Polymarket's 15-minute Up/Down markets (BTC, ETH, SOL, XRP). Detects sharp price drops, buys the dipped side, then hedges the opposite outcome when combined cost falls below a profit threshold.
## Installation
```bash
git clone https://github.com/infraform/polymarket-arbitrage-bot.git
cd polymarket-arbitrage-bot
npm install
npm run build
cp .env.example .env
```
## Key Commands
| Command | Description |
|---------|-------------|
| `npm start` | Run compiled bot (simulation by default) |
| `npm run sim` | Explicitly run in simulation (no real orders) |
| `npm run prod` | Run with real trades (`PRODUCTION=true`) |
| `npm run dev` | Run TypeScript directly via ts-node |
| `npm run build` | Compile TypeScript to `dist/` |
**Always test with `npm run sim` before enabling production mode.**
## Project Structure
```
src/
├── main.ts # Entry point, config load, market discovery, wiring
├── config.ts # Loads/validates .env into typed config
├── api.ts # Gamma + CLOB API client (markets, orderbook, orders, redemption)
├── monitor.ts # Orderbook snapshot polling, strategy callback driver
├── dumpHedgeTrader.ts # Dump detection, leg1/leg2, stop-loss, P&L tracking
├── models.ts # Shared types: Market, OrderBook, TokenPrice, etc.
└── logger.ts # history.toml append log + stderr output
```
## Environment Configuration
Create `.env` from `.env.example`:
```env
# --- Wallet & Auth (required for production) ---
PRIVATE_KEY=0x_your_private_key_here
PROXY_WALLET_ADDRESS=0x_your_proxy_wallet_address
SIGNATURE_TYPE=2 # 0=EOA, 1=Proxy, 2=GnosisSafe
# --- Optional explicit CLOB API credentials ---
# If not set, credentials are derived from signer automatically
API_KEY=
API_SECRET=
API_PASSPHRASE=
# --- API Endpoints (defaults are production Polymarket) ---
GAMMA_API_URL=https://gamma-api.polymarket.com
CLOB_API_URL=https://clob.polymarket.com
# --- Markets ---
MARKETS=btc # comma-separated: btc,eth,sol,xrp
# --- Polling ---
CHECK_INTERVAL_MS=1000
MARKET_CLOSURE_CHECK_INTERVAL_SECONDS=20
# --- Strategy Parameters ---
DUMP_HEDGE_SHARES=10 # Shares per leg
DUMP_HEDGE_SUM_TARGET=0.95 # Hedge when leg1 + opposite_ask <= this
DUMP_HEDGE_MOVE_THRESHOLD=0.15 # 15% drop triggers dump detection
DUMP_HEDGE_WINDOW_MINUTES=2 # Watch window at period start
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES=5
DUMP_HEDGE_STOP_LOSS_PERCENTAGE=0.2
# --- Mode ---
PRODUCTION=false # true = real trades
```
## Core Types (models.ts)
```typescript
// Key shared types used throughout the bot
interface Market {
conditionId: string;
questionId: string;
tokens: Token[]; // [upToken, downToken]
startTime: number;
endTime: number;
asset: string; // "BTC", "ETH", etc.
}
interface Token {
tokenId: string;
outcome: string; // "Up" or "Down"
}
interface OrderBook {
tokenId: string;
outcome: string;
bids: PriceLevel[];
asks: PriceLevel[];
bestBid: number;
bestAsk: number;
}
interface TokenPrice {
tokenId: string;
outcome: string;
bestBid: number;
bestAsk: number;
timestamp: number;
}
interface MarketSnapshot {
upPrice: TokenPrice;
downPrice: TokenPrice;
timeRemainingSeconds: number;
periodStart: number;
}
```
## Strategy Flow
```
1. Discovery → Gamma API finds current 15m market slug for each asset
2. Monitor → Poll CLOB orderbooks every CHECK_INTERVAL_MS
3. Watch → First DUMP_HEDGE_WINDOW_MINUTES: detect if ask drops >= MOVE_THRESHOLD
4. Leg 1 → Buy DUMP_HEDGE_SHARES of dumped side at current ask
5. Wait → Watch for: leg1_entry + opposite_ask <= DUMP_HEDGE_SUM_TARGET
6. Leg 2 → Buy DUMP_HEDGE_SHARES of opposite outcome (hedge)
7. Stop-loss → If hedge not triggered within STOP_LOSS_MAX_WAIT_MINUTES, hedge anyway
8. Rollover → New 15m period → discover new market, reset state
9. Closure → Redeem winning tokens (production), log P&L
```
## Code Examples
### Loading and using config (config.ts pattern)
```typescript
import * as dotenv from 'dotenv';
dotenv.config();
interface BotConfig {
privateKey: string;
proxyWalletAddress: string | undefined;
signatureType: number;
markets: string[];
production: boolean;
checkIntervalMs: number;
dumpHedgeShares: number;
dumpHedgeSumTarget: number;
dumpHedgeMoveThreshold: number;
dumpHedgeWindowMinutes: number;
stopLossMaxWaitMinutes: number;
stopLossPercentage: number;
gammaApiUrl: string;
clobApiUrl: string;
}
function loadConfig(): BotConfig {
return {
privateKey: process.env.PRIVATE_KEY ?? '',
proxyWalletAddress: process.env.PROXY_WALLET_ADDRESS,
signatureType: parseInt(process.env.SIGNATURE_TYPE ?? '2'),
markets: (process.env.MARKETS ?? 'btc').split(',').map(m => m.trim()),
production: process.env.PRODUCTION === 'true',
checkIntervalMs: parseInt(process.env.CHECK_INTERVAL_MS ?? '1000'),
dumpHedgeShares: parseInt(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: parseFloat(process.env.DUMP_HEDGE_WINDOW_MINUTES ?? '2'),
stopLossMaxWaitMinutes: parseFloat(process.env.DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES ?? '5'),
stopLossPercentage: parseFloat(process.env.DUMP_HEDGE_STOP_LOSS_PERCENTAGE ?? '0.2'),
gammaApiUrl: process.env.GAMMA_API_URL ?? 'https://gamma-api.polymarket.com',
clobApiUrl: process.env.CLOB_API_URL ?? 'https://clob.polymarket.com',
};
}
```
### Fetching market via Gamma API (api.ts pattern)
```typescript
import axios from 'axios';
// Find current 15m market for an asset
async function findCurrentMarket(
gammaApiUrl: string,
asset: string // "btc", "eth", "sol", "xrp"
): Promise<Market | null> {
// Polymarket 15m slug format: btc-updown-15m-<period_timestamp>
// Round current time down to nearest 15m period
const now = Math.floor(Date.now() / 1000);
const periodStart = now - (now % (15 * 60));
const slug = `${asset}-updown-15m-${periodStart}`;
try {
const response = await axios.get(`${gammaApiUrl}/markets`, {
params: { slug }
});
const markets = response.data;
if (!markets || markets.length === 0) return null;
return markets[0] as Market;
} catch (err) {
console.error(`[${asset}] Market discovery failed:`, err);
return null;
}
}
```
### Fetching orderbook from CLOB (api.ts pattern)
```typescript
async function getOrderBook(
clobApiUrl: string,
tokenId: string
): Promise<OrderBook | null> {
try {
const response = await axios.get(`${clobApiUrl}/book`, {
params: { token_id: tokenId }
});
const data = response.data;
const bestBid = data.bids?.length > 0
? Math.max(...data.bids.map((b: any) => parseFloat(b.price)))
: 0;
const bestAsk = data.asks?.length > 0
? Math.min(...data.asks.map((a: any) => parseFloat(a.price)))
: 1;
return {
tokenId,
outcome: data.outcome ?? '',
bids: data.bids ?? [],
asks: data.asks ?? [],
bestBid,
bestAsk,
};
} catch (err) {
console.error(`OrderBook fetch failed for ${tokenId}:`, err);
return null;
}
}
```
### Dump detection logic (dumpHedgeTrader.ts pattern)
```typescript
interface DumpHedgeState {
phase: 'watching' | 'leg1_placed' | 'hedging' | 'closed';
leg1Outcome?: 'Up' | 'Down';
leg1EntryPrice?: number;
leg1PlacedAt?: number;
leg1TokenId?: string;
hedgeTokenId?: string;
periodStart: number;
}
function detectDump(
snapshot: MarketSnapshot,
priceHistory: TokenPrice[],
config: BotConfig
): 'Up' | 'Down' | null {
constRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.