polymarket-arbitrage-trading-bot

安装量: 405
排名: #5445

安装

npx skills add https://github.com/aradotso/trending-skills --skill polymarket-arbitrage-trading-bot

Polymarket Arbitrage Trading Bot Skill by 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 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 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 )

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 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) 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 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 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 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 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) 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 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

{ const leg1 : LegState = { filled : false , tokenId : '' , entryPrice : null , orderId : null } ; const leg2 : LegState = { filled : false , tokenId : '' , entryPrice : null , orderId : null } ; const startTime = Date . now ( ) ; const windowMs = config . dumpHedgeWindowMinutes * 60 * 1000 ; const stopLossMs = config . dumpHedgeStopLossMaxWaitMinutes * 60 * 1000 ; const priceHistory : Record < string , PriceSnapshot [ ]

= { [ upTokenId ] : [ ] , [ downTokenId ] : [ ] } ; const interval = setInterval ( async ( ) => { const elapsed = Date . now ( ) - startTime ; const upAsk = await getBestAsk ( upTokenId ) ; const downAsk = await getBestAsk ( downTokenId ) ; if ( upAsk == null || downAsk == null ) return ; // Record history const now = Date . now ( ) ; priceHistory [ upTokenId ] . push ( { timestamp : now , ask : upAsk } ) ; priceHistory [ downTokenId ] . push ( { timestamp : now , ask : downAsk } ) ; // === LEG 1: Detect dump, buy dumped leg === if ( ! leg1 . filled && elapsed <= windowMs ) { const upDumped = detectDump ( priceHistory [ upTokenId ] , upAsk , config . dumpHedgeMoveThreshold , windowMs ) ; const downDumped = detectDump ( priceHistory [ downTokenId ] , downAsk , config . dumpHedgeMoveThreshold , windowMs ) ; if ( upDumped || downDumped ) { const dumpedToken = upDumped ? upTokenId : downTokenId ; const dumpedAsk = upDumped ? upAsk : downAsk ; leg1 . tokenId = dumpedToken ; leg1 . entryPrice = dumpedAsk ; leg1 . orderId = await buyShares ( client , dumpedToken , dumpedAsk , config . dumpHedgeShares , simulate ) ; leg1 . filled = true ; console . error ( [LEG1] Bought dumped leg @ ${ dumpedAsk } ) ; } } // === LEG 2: Hedge when sum is favorable === if ( leg1 . filled && ! leg2 . filled ) { const hedgeToken = leg1 . tokenId === upTokenId ? downTokenId : upTokenId ; const hedgeAsk = leg1 . tokenId === upTokenId ? downAsk : upAsk ; const combinedCost = leg1 . entryPrice ! + hedgeAsk ; const shouldHedge = combinedCost <= config . dumpHedgeSumTarget || elapsed = stopLossMs ; // Stop-loss: force hedge on timeout if ( shouldHedge ) { const label = combinedCost <= config . dumpHedgeSumTarget ? 'HEDGE' : 'STOP-LOSS' ; leg2 . tokenId = hedgeToken ; leg2 . entryPrice = hedgeAsk ; leg2 . orderId = await buyShares ( client , hedgeToken , hedgeAsk , config . dumpHedgeShares , simulate ) ; leg2 . filled = true ; console . error ( [LEG2: ${ label } ] Bought hedge @ ${ hedgeAsk } , combined= ${ combinedCost } ) ; clearInterval ( interval ) ; } } } , config . checkIntervalMs ) ; } Settlement and Redemption async function settleRound ( client : ClobClient , conditionId : string , winningTokenId : string , simulate : boolean ) : Promise < void

{ if ( simulate ) { console . error ( [SIM] Would redeem winning token ${ winningTokenId } ) ; return ; } // Redeem via CLOB client (CTF redemption on Polygon) await client . redeemPositions ( { conditionId , amounts : [ { tokenId : winningTokenId , amount : config . dumpHedgeShares } ] } ) ; console . error ( [SETTLE] Redeemed ${ config . dumpHedgeShares } shares for ${ winningTokenId } ) ; } Running Modes Simulation (Recommended First)

Via npm script

npm run sim

Or directly with flag

node dist/main.js --simulation

Monitor output

tail -f history.toml Production (Live Trading)

Ensure .env has correct PRIVATE_KEY, PROXY_WALLET_ADDRESS, SIGNATURE_TYPE

npm run prod

Or:

PRODUCTION

true node dist/main.js --production Single Asset, Custom Thresholds MARKETS = btc \ DUMP_HEDGE_MOVE_THRESHOLD = 0.12 \ DUMP_HEDGE_SUM_TARGET = 0.93 \ DUMP_HEDGE_SHARES = 5 \ npm run prod Common Patterns Multi-Asset Parallel Monitoring // main.ts pattern: spin up one monitor per asset import { config } from './config' ; async function main ( ) { const isProduction = process . argv . includes ( '--production' ) || config . production ; await Promise . all ( config . markets . map ( asset => runAssetMonitor ( asset , isProduction ) ) ) ; } async function runAssetMonitor ( asset : string , production : boolean ) { while ( true ) { const market = await findActive15mMarket ( asset ) ; if ( ! market ) { console . error ( [ ${ asset } ] No active market, retrying in 30s ) ; await sleep ( 30_000 ) ; continue ; } const [ upToken , downToken ] = market . tokens ; const client = createClobClient ( ) ; await runDumpHedgeCycle ( client , upToken . token_id , downToken . token_id , ! production ) ; // Wait for round end, then loop for next round const roundEnd = new Date ( market . endDateIso ) . getTime ( ) ; await sleep ( Math . max ( 0 , roundEnd - Date . now ( ) + 5_000 ) ) ; } } function sleep ( ms : number ) : Promise < void

{ return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } main ( ) . catch ( console . error ) ; Logging to history.toml import * as fs from 'fs' ; interface TradeRecord { asset : string ; roundEnd : string ; leg1Price : number ; leg2Price : number ; combined : number ; target : number ; mode : 'hedge' | 'stop-loss' ; timestamp : string ; } function appendHistory ( record : TradeRecord ) : void { const entry = [[trade]] asset = " ${ record . asset } " round_end = " ${ record . roundEnd } " leg1_price = ${ record . leg1Price } leg2_price = ${ record . leg2Price } combined = ${ record . combined } target = ${ record . target } mode = " ${ record . mode } " timestamp = " ${ record . timestamp } " ; fs . appendFileSync ( 'history.toml' , entry , 'utf8' ) ; } Troubleshooting Issue Cause Fix Failed to fetch market/orderbook API/network error Temporary; check GAMMA_API_URL / CLOB_API_URL connectivity, retries are built in Orders fail in production Wrong auth config Verify PRIVATE_KEY , SIGNATURE_TYPE , and PROXY_WALLET_ADDRESS match your Polymarket account No market found for asset Round gap or unsupported asset Only use btc , eth , sol , xrp ; wait for next 15m round to start Bot never triggers leg 1 Threshold too high or quiet market Lower DUMP_HEDGE_MOVE_THRESHOLD or increase DUMP_HEDGE_WINDOW_MINUTES Combined cost always above target Market conditions Lower DUMP_HEDGE_SUM_TARGET or adjust DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES Cannot find module errors Missing build step Run npm run build before npm start / npm run prod Simulation not placing orders Expected behavior Simulation mode logs only; switch to --production for real orders Safety Checklist Always simulate first — run npm run sim across multiple rounds and inspect history.toml Start small — use low DUMP_HEDGE_SHARES (e.g. 1 ) in first production runs Secure credentials — never commit .env to version control; add it to .gitignore Monitor stop-loss behavior — tune DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES carefully; forced hedges at bad prices reduce edge Polygon USDC — ensure sufficient USDC balance on Polygon before running production Round timing — the bot auto-rolls to the next round; verify rollover logs look correct in simulation first

返回排行榜