polymarket-arbitrage-trading-bot
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePolymarket 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 . 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.
@polymarket/clob-client由ara.so提供的Skill — 2026每日技能合集。
这是一款针对Polymarket 15分钟加密货币涨跌预测市场的自动抛售对冲套利机器人,使用官方以TypeScript编写。它会监控BTC、ETH、SOL和XRP市场,当某一方向的价格出现大幅下跌时,若两个方向的合并成本低于目标阈值,就会同时买入两个方向,从而在结算前锁定结构性收益。
@polymarket/clob-clientInstallation
安装
bash
git clone https://github.com/apechurch/polymarket-arbitrage-trading-bot.git
cd polymarket-arbitrage-trading-bot
npm install
cp .env.example .envbash
git clone https://github.com/apechurch/polymarket-arbitrage-trading-bot.git
cd polymarket-arbitrage-trading-bot
npm install
cp .env.example .envConfigure .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 loggingtext
src/
main.ts # 入口文件:市场发现、监控、周期切换
monitor.ts # 价格轮询与快照
dumpHedgeTrader.ts # 核心策略:抛售 → 对冲 → 止损 → 结算
api.ts # Gamma API、CLOB API、订单提交、赎回
config.ts # 环境变量加载
models.ts # 共享TypeScript类型
logger.ts # 历史文件(history.toml)+ 标准错误日志Key Commands
关键命令
| Command | Purpose |
|---|---|
| Run via |
| Compile TypeScript to |
| Type-check without emitting output |
| Remove |
| Simulation mode — logs trades, no real orders |
| Production mode — places real CLOB orders |
| Run compiled output (defaults to simulation unless |
| 命令 | 用途 |
|---|---|
| 通过 |
| 将TypeScript编译到 |
| 类型检查,不生成输出 |
| 删除 |
| 模拟模式 — 仅记录交易,不提交真实订单 |
| 生产模式 — 提交真实CLOB订单 |
| 运行编译后的代码(默认模拟模式,除非传入 |
Configuration (.env
)
.env配置(.env
)
.envbash
undefinedbash
undefinedWallet / 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加载配置(src/config.ts
模式)
src/config.tstypescript
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
undefinedbash
undefinedVia 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
undefinedtail -f history.toml
undefinedProduction (Live Trading)
生产模式(实盘交易)
bash
undefinedbash
undefinedEnsure .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
undefinedPRODUCTION=true node dist/main.js --production
undefinedSingle Asset, Custom Thresholds
单一资产、自定义阈值
bash
MARKETS=btc \
DUMP_HEDGE_MOVE_THRESHOLD=0.12 \
DUMP_HEDGE_SUM_TARGET=0.93 \
DUMP_HEDGE_SHARES=5 \
npm run prodbash
MARKETS=btc \
DUMP_HEDGE_MOVE_THRESHOLD=0.12 \
DUMP_HEDGE_SUM_TARGET=0.93 \
DUMP_HEDGE_SHARES=5 \
npm run prodCommon 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
故障排除
| Issue | Cause | Fix |
|---|---|---|
| API/network error | Temporary; check |
| Orders fail in production | Wrong auth config | Verify |
| No market found for asset | Round gap or unsupported asset | Only use |
| Bot never triggers leg 1 | Threshold too high or quiet market | Lower |
| Combined cost always above target | Market conditions | Lower |
| Missing build step | Run |
| Simulation not placing orders | Expected behavior | Simulation mode logs only; switch to |
| 问题 | 原因 | 解决方法 |
|---|---|---|
| API/网络错误 | 临时问题;检查 |
| 生产模式下订单失败 | 认证配置错误 | 验证 |
| 未找到资产对应的市场 | 周期间隙或不支持的资产 | 仅使用 |
| 机器人从未触发方向1 | 阈值过高或市场平静 | 降低 |
| 合并成本始终高于目标 | 市场条件限制 | 降低 |
| 缺少构建步骤 | 在 |
| 模拟模式下未提交订单 | 预期行为 | 模拟模式仅记录日志;切换到 |
Safety Checklist
安全检查清单
- Always simulate first — run across multiple rounds and inspect
npm run simhistory.toml - Start small — use low (e.g.
DUMP_HEDGE_SHARES) in first production runs1 - Secure credentials — never commit to version control; add it to
.env.gitignore - Monitor stop-loss behavior — tune carefully; forced hedges at bad prices reduce edge
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES - 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
- 始终先进行模拟 — 运行多个周期,并检查
npm run simhistory.toml - 从小额开始 — 首次实盘运行时使用低(例如
DUMP_HEDGE_SHARES)1 - 保护凭证 — 永远不要将提交到版本控制;将其添加到
.env.gitignore - 监控止损行为 — 仔细调整;在不利价格下强制对冲会减少收益
DUMP_HEDGE_STOP_LOSS_MAX_WAIT_MINUTES - Polygon USDC — 运行生产模式前确保Polygon链上有足够的USDC余额
- 周期时间 — 机器人会自动切换到下一个周期;先在模拟模式下验证切换日志是否正确