本指南详细介绍了如何构建一个Polymarket交易机器人,该机器人能够监控目标钱包的实时交易并镜像其买单。文章涵盖了Polymarket开发基础、Quicknode端点设置、CLOB客户端初始化、通过数据API和WebSocket检测交易、头寸跟踪、风险限制以及一个仅限买入的复制交易流程。
本指南中的代码尚未经过审计。强烈建议你在部署到生产环境之前对任何代码进行审计。
不久前,押注现实世界事件意味着赌场或体育博彩,它们需要完整的身份验证,即便如此,你也只能限于狭窄的市场范围。如今,像 Polymarket 这样的链上预测市场让你可以交易各种结果,从选举结果到某人在直播期间说某个特定单词的次数,或者某种资产在未来 15 分钟内的价格。没有账户表格,只有智能合约和公共区块链账本。
在本指南中,我们将向你展示如何构建一个 Polymarket 交易机器人。具体来说,我们将构建一个机器人,它会监控目标钱包,实时检测其交易,并镜像买单。
让我们开始吧!
在编码之前的 TLDR:
欢迎你使用公共节点或管理自己的基础设施;但是,如果你想要更快的响应时间,可以将繁重的工作交给我们。在此处注册一个免费的 Quicknode 账户并创建一个 Polygon Mainnet 端点。将你的 HTTP 和 WSS URL 保存在 .env 文件中备用。

克隆 Quicknode 示例仓库并进入项目目录:
git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/defi/polymarket-copy-bot
Polymarket 的 SDK 目前在此设置中预期使用 ethers v5。
npm install
使用这个最小的 .env 文件进行 EOA 模式配置:
## Required
TARGET_WALLET=0xTARGET_WALLET_TO_COPY
PRIVATE_KEY=0xYOUR_PRIVATE_KEY
RPC_URL=https://polygon-mainnet.quiknode.pro/YOUR_KEY
## Optional geo token (if provided by Polymarket for your account/region)
POLYMARKET_GEO_TOKEN=
## Trading
POSITION_MULTIPLIER=0.1
MAX_TRADE_SIZE=100
MIN_TRADE_SIZE=1
SLIPPAGE_TOLERANCE=0.02
ORDER_TYPE=FOK
## Risk (0 disables the cap)
MAX_SESSION_NOTIONAL=0
MAX_PER_MARKET_NOTIONAL=0
## Monitoring
USE_WEBSOCKET=true
USE_USER_CHANNEL=false
POLL_INTERVAL=2000
WS_ASSET_IDS=
WS_MARKET_IDS=
## Optional gas floor overrides for Polygon approval/order txs
MIN_PRIORITY_FEE_GWEI=30
MIN_MAX_FEE_GWEI=60
注意:
signatureType=0)。PRIVATE_KEY 派生/生成用户 API 凭据。MAX_TRADE_SIZE=5 或更低)。机器人代码在 TradeExecutor 内部通过先派生后创建的备用逻辑完成此操作。
代码位于 src/trader.ts。
private async deriveAndReinitApiKeys(funderAddress: string): Promise<void> {
console.log(` Generating API credentials programmatically...`);
let creds = await this.clobClient.deriveApiKey().catch(() => null);
if (!creds || this.isApiError(creds)) {
creds = await this.clobClient.createApiKey();
}
const apiKey = (creds as any)?.apiKey || (creds as any)?.key;
if (this.isApiError(creds) || !apiKey || !creds?.secret || !creds?.passphrase) {
const errMsg = this.getApiErrorMessage(creds);
throw new Error(`Could not create/derive API key: ${errMsg}`);
}
this.clobClient = new ClobClient(
'https://clob.polymarket.com',
137,
this.wallet,
{
key: apiKey,
secret: creds.secret,
passphrase: creds.passphrase,
},
0,
funderAddress,
config.polymarketGeoToken || undefined
);
}
使用 Data API 检测来自目标钱包的新交易。 Data API 是此机器人中的发现层(它发现要复制的内容)。
代码位于 src/monitor.ts。
const response = await axios.get(
'https://data-api.polymarket.com/activity',
{
params: {
user: config.targetWallet.toLowerCase(),
type: 'TRADE',
limit: 100,
sortBy: 'TIMESTAMP',
sortDirection: 'DESC',
start: startSeconds,
},
headers: {
'Accept': 'application/json',
},
}
);
Polymarket 公开 market 和 user WebSocket 通道。
market 通道通过 assets_ids 订阅。user 通道通过 markets 订阅并需要认证。代码位于 src/websocket-monitor.ts。
private buildWsAuth(): { apikey: string; apiKey: string; secret: string; passphrase: string } | undefined {
if (!this.auth) return undefined;
return {
apikey: this.auth.apiKey,
apiKey: this.auth.apiKey,
secret: this.auth.secret,
passphrase: this.auth.passphrase,
};
}
private sendInitialSubscribe(): void {
if (!this.ws) return;
const payload: any = { type: this.channel };
if (this.channel === 'market') {
payload.assets_ids = Array.from(this.subscribedAssets);
} else {
payload.markets = Array.from(this.subscribedMarkets);
payload.auth = this.buildWsAuth();
}
if ((payload.assets_ids && payload.assets_ids.length) || (payload.markets && payload.markets.length)) {
this.ws.send(JSON.stringify(payload));
}
}
附加资源:
头寸在启动时加载(尽力而为),并从成功的填充中更新。
代码位于 src/positions.ts。
recordFill(params: {
trade: Trade;
notional: number;
shares: number;
price: number;
side: 'BUY' | 'SELL';
}): void {
const { trade, notional, shares, price, side } = params;
const key = trade.tokenId;
const existing = this.positions.get(key);
const sign = side === 'BUY' ? 1 : -1;
const deltaShares = shares * sign;
const deltaNotional = notional * sign;
const nextShares = (existing?.shares || 0) + deltaShares;
const nextNotional = (existing?.notional || 0) + deltaNotional;
const avgPrice = nextShares !== 0 ? Math.abs(nextNotional / nextShares) : 0;
const updated: PositionState = {
tokenId: trade.tokenId,
market: trade.market,
outcome: trade.outcome,
shares: Math.max(0, nextShares),
notional: Math.max(0, nextNotional),
avgPrice: nextShares !== 0 ? avgPrice : 0,
lastUpdated: Date.now(),
};
this.positions.set(key, updated);
}
机器人在执行前强制执行最大会话名义金额和每个市场最大名义金额。
代码位于 src/risk-manager.ts。
checkTrade(trade: Trade, copyNotional: number): RiskCheckResult {
if (copyNotional <= 0) {
return { allowed: false, reason: 'Copy notional is <= 0' };
}
if (config.risk.maxSessionNotional > 0) {
const nextSession = this.sessionNotional + copyNotional;
if (nextSession > config.risk.maxSessionNotional) {
return {
allowed: false,
reason: `Session notional cap exceeded (${nextSession.toFixed(2)} > ${config.risk.maxSessionNotional})`,
};
}
}
if (config.risk.maxPerMarketNotional > 0) {
const current = this.positions.getNotional(trade.tokenId);
const next = current + copyNotional;
if (next > config.risk.maxPerMarketNotional) {
return {
allowed: false,
reason: `Per-market notional cap exceeded (${next.toFixed(2)} > ${config.risk.maxPerMarketNotional})`,
};
}
}
return { allowed: true };
}
机器人中的运行时防护检查:
const copyNotional = this.executor.calculateCopySize(trade.size);
const riskCheck = this.risk.checkTrade(trade, copyNotional);
if (!riskCheck.allowed) {
console.log(`⚠️ Risk check blocked trade: ${riskCheck.reason}`);
return;
}
单笔交易生命周期集中在一处(src/index.ts 中的 handleNewTrade):
if (trade.side === 'SELL') {
console.log('⚠️ Skipping SELL trade (BUY-only safeguard enabled)');
return;
}
if (this.wsMonitor) {
await this.wsMonitor.subscribeToMarket(trade.tokenId);
}
const copyNotional = this.executor.calculateCopySize(trade.size);
const riskCheck = this.risk.checkTrade(trade, copyNotional);
if (!riskCheck.allowed) {
console.log(`⚠️ Risk check blocked trade: ${riskCheck.reason}`);
return;
}
try {
const result = await this.executor.executeCopyTrade(trade, copyNotional);
this.risk.recordFill({
trade,
notional: result.copyNotional,
shares: result.copyShares,
price: result.price,
side: result.side,
});
this.stats.tradesCopied++;
this.stats.totalVolume += result.copyNotional;
console.log(`✅ Successfully copied trade!`);
} catch (error: any) {
this.stats.tradesFailed++;
console.log(`❌ Failed to copy trade`);
if (error?.message) {
console.log(` Reason: ${error.message}`);
}
}
重试策略(来自 src/trader.ts 中的 isRetryableError):
if (responseStatus === 401 || errorMsg.includes('unauthorized') || responseData.includes('unauthorized')) {
return false;
}
if (responseStatus === 403 || errorMsg.includes('cloudflare') || responseData.includes('blocked')) {
return false;
}
if (errorMsg.includes('network') || errorMsg.includes('timeout') || errorMsg.includes('econnreset')) {
return true;
}
if (errorMsg.includes('rate limit') || responseData.includes('rate limit')) {
return true;
}
if (errorMsg.includes('502') || errorMsg.includes('503') || errorMsg.includes('504')) {
return true;
}
if (errorMsg.includes('insufficient') || responseData.includes('allowance')) {
return false;
}
if (errorMsg.includes('invalid') || responseData.includes('bad request')) {
return false;
}
if (errorMsg.includes('duplicate') || responseData.includes('duplicate')) {
return false;
}
确保 .env 值已设置,然后启动机器人:
npm start
启动输出示例:
🤖 Polymarket Copy Trading Bot
================================
Target wallet: 0x...
Order type: FOK
WebSocket: Enabled
Auth mode: EOA (signature type 0)
================================
ℹ️ API credentials will be derived/generated from PRIVATE_KEY at startup
✅ Configuration validated
🔧 Initializing trader...
✅ Trader initialized
ℹ️ WebSocket waiting for first subscription before connecting
🚀 Bot started! Monitoring via: WebSocket + REST API
当目标钱包进行新交易时,机器人会检测到它,评估风险,并尝试下达复制订单。
🎯 New trade detected: BUY 12.5 USDC @ 0.62
Time: 2026-02-17T14:03:11.000Z
==================================================
🎯 NEW TRADE DETECTED
Time: 2026-02-17T14:03:11.000Z
Market: 0xabc123...
Side: BUY YES
Size: 12.5 USDC @ 0.620
Token ID: 1234567890...
==================================================
📈 Executing copy trade (FOK):
Market: 0xabc123...
Side: BUY
Original size: 12.5 USDC
Token ID: 1234567890...
Copy notional: 1.25 USDC
Balance/allowance check passed
Market price: 0.6300
Copy shares: 1.9841
✅ FOK order executed: 0xorderid...
✅ Successfully copied trade!
📊 Session Stats: 1/1 copied, 0 failed
当检测到目标钱包进行卖出交易时,此演示会故意跳过它:
⚠️ Skipping SELL trade (BUY-only safeguard enabled)
你现在拥有了一个可工作的复制交易机器人,它能够监控目标钱包,强制执行风险限制,并以 EOA 模式执行。这是一个快速演示实现,所以请保持交易规模较小,并将其视为继续学习的基础。
订阅我们的 Quicknode 新闻通讯 以获取更多关于区块链基础设施的指南。你还可以在 Twitter 和 Discord 上与我们联系。
告诉我们你是否有任何反馈或对新主题的请求。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/def...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!