polymarket-arbitrage-trading-bot

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Polymarket Arbitrage Trading Bot

Polymarket 套利交易机器人

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.

ara.so提供的Skill — 2026每日技能合集。
这是一款针对Polymarket 15分钟加密货币涨跌预测市场的自动抛售对冲套利机器人,使用官方
@polymarket/clob-client
以TypeScript编写。它会监控BTC、ETH、SOL和XRP市场,当某一方向的价格出现大幅下跌时,若两个方向的合并成本低于目标阈值,就会同时买入两个方向,从而在结算前锁定结构性收益。

Installation

安装

bash
git clone https://github.com/apechurch/polymarket-arbitrage-trading-bot.git
cd polymarket-arbitrage-trading-bot
npm install
cp .env.example .env
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

配置.env — 查看配置章节

npm run build

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

---
npm run build

**要求:** Node.js 16+,Polygon链上的USDC(用于实盘交易),兼容Polymarket的钱包。

---

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

text
src/
  main.ts              # 入口文件:市场发现、监控、周期切换
  monitor.ts           # 价格轮询与快照
  dumpHedgeTrader.ts   # 核心策略:抛售 → 对冲 → 止损 → 结算
  api.ts               # Gamma API、CLOB API、订单提交、赎回
  config.ts            # 环境变量加载
  models.ts            # 共享TypeScript类型
  logger.ts            # 历史文件(history.toml)+ 标准错误日志

Key Commands

关键命令

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

命令用途
npm run dev
通过
ts-node
运行(开发模式,无需构建)
npm run build
将TypeScript编译到
dist/
目录
npm run typecheck
类型检查,不生成输出
npm run clean
删除
dist/
目录
npm run sim
模拟模式 — 仅记录交易,不提交真实订单
npm run prod
生产模式 — 提交真实CLOB订单
npm start
运行编译后的代码(默认模拟模式,除非传入
--production
参数)

Configuration (
.env
)

配置(
.env

bash
undefined
bash
undefined

Wallet / Auth

钱包 / 认证

PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE PROXY_WALLET_ADDRESS=0xYOUR_PROXY_WALLET SIGNATURE_TYPE=2 # 0=EOA, 1=Proxy, 2=Gnosis Safe
PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE PROXY_WALLET_ADDRESS=0xYOUR_PROXY_WALLET SIGNATURE_TYPE=2 # 0=EOA, 1=代理钱包, 2=Gnosis Safe

Markets to trade (comma-separated)

要交易的市场(逗号分隔)

MARKETS=btc,eth,sol,xrp
MARKETS=btc,eth,sol,xrp

Polling

轮询间隔

CHECK_INTERVAL_MS=1000
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
DUMP_HEDGE_SHARES=10 # 每个方向的份额 DUMP_HEDGE_SUM_TARGET=0.95 # 两个方向的最高合并成本 DUMP_HEDGE_MOVE_THRESHOLD=0.15 # 触发抛售检测的最小跌幅(15%) DUMP_HEDGE_WINDOW_MINUTES=5 # 仅在周期开始后的N分钟内检测抛售 DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES=8 # N分钟后强制执行止损对冲

Mode flag (use --production CLI flag for live trading)

模式标记(使用--production CLI参数启用实盘交易)

PRODUCTION=false
PRODUCTION=false

Optional API overrides

可选API覆盖

GAMMA_API_URL=https://gamma-api.polymarket.com CLOB_API_URL=https://clob.polymarket.com API_KEY= API_SECRET= API_PASSPHRASE=

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

text
新的15分钟周期开始
监控周期开始后的DUMP_HEDGE_WINDOW_MINUTES分钟
        ├── 上涨或下跌方向的价格跌幅≥DUMP_HEDGE_MOVE_THRESHOLD?
        │         │
        │         ▼
        │   买入下跌的方向(方向1)
        │         │
        │         ├── 相反方向的卖价足够低?
        │         │   (方向1入场价 + 相反方向卖价 ≤ DUMP_HEDGE_SUM_TARGET)
        │         │         │
        │         │         ▼
        │         │   买入对冲方向(方向2)→ 锁定收益
        │         │
        │         └── 超时(DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES)?
        │                   │
        │                   ▼
        │           执行止损对冲
        └── 周期结束 → 结算盈利方,链上赎回(生产模式)

Code Examples

代码示例

Loading Config (
src/config.ts
pattern)

加载配置(
src/config.ts
模式)

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',
};
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

初始化CLOB客户端

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
  );
}
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,         // 例如: 'https://clob.polymarket.com'
    137,                        // Polygon链ID
    wallet,
    undefined,                  // 凭证(若需要可在密钥派生后设置)
    config.signatureType,
    config.proxyWalletAddress
  );
}

Discovering the Active 15-Minute Market

发现当前活跃的15分钟市场

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;
}
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 markets.sort(
    (a, b) => new Date(a.endDateIso).getTime() - new Date(b.endDateIso).getTime()
  )[0] ?? null;
}

Fetching Best Ask Price from CLOB

从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;
  }
}
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;
    // 最优卖价 = 最低价格
    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
);
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;
}

// 使用示例:
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;
}
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] 买入 ${shares} 份额 @ ${price} 代币=${tokenId}`);
    return 'sim-order-id';
  }
  const order = await client.createOrder({
    tokenID: tokenId,
    price,
    size: shares,
    side: Side.BUY,
    orderType: OrderType.FOK,   // 立即成交或取消(FOK)
  });
  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> {
  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);
}
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> {
  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;

    // 记录价格历史
    const now = Date.now();
    priceHistory[upTokenId].push({ timestamp: now, ask: upAsk });
    priceHistory[downTokenId].push({ timestamp: now, ask: downAsk });

    // === 方向1:检测抛售,买入下跌方向 ===
    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(`[方向1] 买入下跌方向 @ ${dumpedAsk}`);
      }
    }

    // === 方向2:当合并成本有利时进行对冲 ===
    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; // 止损:超时后强制对冲

      if (shouldHedge) {
        const label = combinedCost <= config.dumpHedgeSumTarget ? '对冲' : '止损';
        leg2.tokenId = hedgeToken;
        leg2.entryPrice = hedgeAsk;
        leg2.orderId = await buyShares(
          client, hedgeToken, hedgeAsk, config.dumpHedgeShares, simulate
        );
        leg2.filled = true;
        console.error(`[方向2:${label}] 买入对冲方向 @ ${hedgeAsk}, 合并成本=${combinedCost}`);
        clearInterval(interval);
      }
    }
  }, config.checkIntervalMs);
}

Settlement and Redemption

结算与赎回

typescript
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}`);
}

typescript
async function settleRound(
  client: ClobClient,
  conditionId: string,
  winningTokenId: string,
  simulate: boolean
): Promise<void> {
  if (simulate) {
    console.error(`[SIM] 本应赎回盈利代币 ${winningTokenId}`);
    return;
  }
  // 通过CLOB客户端赎回(Polygon链上的CTF赎回)
  await client.redeemPositions({
    conditionId,
    amounts: [{ tokenId: winningTokenId, amount: config.dumpHedgeShares }]
  });
  console.error(`[结算] 赎回 ${config.dumpHedgeShares} 份额的 ${winningTokenId}`);
}

Running Modes

运行模式

Simulation (Recommended First)

模拟模式(推荐先使用)

bash
undefined
bash
undefined

Via npm script

通过npm脚本

npm run sim
npm run sim

Or directly with flag

或直接使用标记

node dist/main.js --simulation
node dist/main.js --simulation

Monitor output

监控输出

tail -f history.toml
undefined
tail -f history.toml
undefined

Production (Live Trading)

生产模式(实盘交易)

bash
undefined
bash
undefined

Ensure .env has correct PRIVATE_KEY, PROXY_WALLET_ADDRESS, SIGNATURE_TYPE

确保.env中包含正确的PRIVATE_KEY、PROXY_WALLET_ADDRESS、SIGNATURE_TYPE

npm run prod
npm run prod

Or:

或:

PRODUCTION=true node dist/main.js --production
undefined
PRODUCTION=true node dist/main.js --production
undefined

Single Asset, Custom Thresholds

单一资产、自定义阈值

bash
MARKETS=btc \
DUMP_HEDGE_MOVE_THRESHOLD=0.12 \
DUMP_HEDGE_SUM_TARGET=0.93 \
DUMP_HEDGE_SHARES=5 \
npm run prod

bash
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

多资产并行监控

typescript
// 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);
typescript
// main.ts模式:为每个资产启动一个监控器
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}] 未找到活跃市场,30秒后重试`);
      await sleep(30_000);
      continue;
    }
    const [upToken, downToken] = market.tokens;
    const client = createClobClient();
    await runDumpHedgeCycle(client, upToken.token_id, downToken.token_id, !production);
    // 等待周期结束,然后循环进入下一个周期
    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

记录到history.toml

typescript
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');
}

typescript
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

故障排除

IssueCauseFix
Failed to fetch market/orderbook
API/network errorTemporary; check
GAMMA_API_URL
/
CLOB_API_URL
connectivity, retries are built in
Orders fail in productionWrong auth configVerify
PRIVATE_KEY
,
SIGNATURE_TYPE
, and
PROXY_WALLET_ADDRESS
match your Polymarket account
No market found for assetRound gap or unsupported assetOnly use
btc
,
eth
,
sol
,
xrp
; wait for next 15m round to start
Bot never triggers leg 1Threshold too high or quiet marketLower
DUMP_HEDGE_MOVE_THRESHOLD
or increase
DUMP_HEDGE_WINDOW_MINUTES
Combined cost always above targetMarket conditionsLower
DUMP_HEDGE_SUM_TARGET
or adjust
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES
Cannot find module
errors
Missing build stepRun
npm run build
before
npm start
/
npm run prod
Simulation not placing ordersExpected behaviorSimulation mode logs only; switch to
--production
for real orders

问题原因解决方法
Failed to fetch market/orderbook
API/网络错误临时问题;检查
GAMMA_API_URL
/
CLOB_API_URL
的连通性,内置重试机制
生产模式下订单失败认证配置错误验证
PRIVATE_KEY
SIGNATURE_TYPE
PROXY_WALLET_ADDRESS
与你的Polymarket账户匹配
未找到资产对应的市场周期间隙或不支持的资产仅使用
btc
eth
sol
xrp
;等待下一个15分钟周期开始
机器人从未触发方向1阈值过高或市场平静降低
DUMP_HEDGE_MOVE_THRESHOLD
或增加
DUMP_HEDGE_WINDOW_MINUTES
合并成本始终高于目标市场条件限制降低
DUMP_HEDGE_SUM_TARGET
或调整
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES
Cannot find module
错误
缺少构建步骤
npm start
/
npm run prod
前运行
npm run build
模拟模式下未提交订单预期行为模拟模式仅记录日志;切换到
--production
以提交真实订单

Safety Checklist

安全检查清单

  1. Always simulate first — run
    npm run sim
    across multiple rounds and inspect
    history.toml
  2. Start small — use low
    DUMP_HEDGE_SHARES
    (e.g.
    1
    ) in first production runs
  3. Secure credentials — never commit
    .env
    to version control; add it to
    .gitignore
  4. Monitor stop-loss behavior — tune
    DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES
    carefully; forced hedges at bad prices reduce edge
  5. Polygon USDC — ensure sufficient USDC balance on Polygon before running production
  6. Round timing — the bot auto-rolls to the next round; verify rollover logs look correct in simulation first
  1. 始终先进行模拟 — 运行
    npm run sim
    多个周期,并检查
    history.toml
  2. 从小额开始 — 首次实盘运行时使用低
    DUMP_HEDGE_SHARES
    (例如
    1
  3. 保护凭证 — 永远不要将
    .env
    提交到版本控制;将其添加到
    .gitignore
  4. 监控止损行为 — 仔细调整
    DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES
    ;在不利价格下强制对冲会减少收益
  5. Polygon USDC — 运行生产模式前确保Polygon链上有足够的USDC余额
  6. 周期时间 — 机器人会自动切换到下一个周期;先在模拟模式下验证切换日志是否正确