[原创] Polymarket 多角色智能分析交易系统设计与实现

策略背景

今年年初,一条关于有人在 Polymarket 一夜大赚一套房的消息在社区里广泛流传。带着好奇去搜具体的交易框架,发现全是标题党,一行代码都没有。没办法,只能自己动手。

刚开始其实挺没头绪的。Polymarket 是目前规模最大的去中心化预测市场平台,挂牌标的超过 6 万个,涵盖**、体育、宏观经济等几乎所有可预测事件。每个标的本质上是一份二元期权合约——结果发生,价格收敛至 1;不发生,归零。要说什么最赚钱,当然是内幕消息了。但普通人不知道老马下周发几条推文,不知道美联储下次会不会降息,也没有时间和精力盯盘、分析行情、做决策。

于是就想,能不能换个角度:不追信息,追资金。掌握信息优势的人会提前布局,买入动作必然会在价格和成交量上留下痕迹。即便不知道「内幕」,也可以通过观察资金行为来推断是否有人在悄悄建仓。顺着这个思路,搭了一套多角色智能分析交易系统,把 K 线找信号、舆情做验证、AI 做判断、自动下单风控这几件事串成一条完整的流水线。

系统架构

整套系统运行在发明者工作流上,分为主线和副线两条并行调度路径,两者完全解耦,互不阻塞。

调度路径 触发频率 负责模块 核心任务
交易主线 每 10 分钟 筛选标的 → 循环处理 → AI 智能体 → 交易执行 寻找机会、下单建仓
交易副线 每 30 秒 止盈止损 持仓监控、移动止损、到期赎回

主线负责「找机会、开仓」,每轮执行包含四个串行阶段:初步筛选 → K 线异常检测 → 新闻搜索 → AI 多角色分析,最终输出买入信号并进入订单执行。副线只管「存量持仓」,以 30 秒为周期持续运行,无论主线当前是否有在途任务,副线都不会停。

这个分离设计解决了一个现实问题:预测市场的价格变动是连续的,但选股分析是重计算、高延迟的。如果把止损监控塞进主线,遇到 AI 分析慢的情况,止损响应就会严重滞后。独立调度是必要的工程取舍。


各模块详解

初步筛选:先把垃圾标的排掉

6 万个标的不可能逐一分析,所以第一道关卡是纯规则筛选,目的只有一个:快速剔除明显不值得看的品种,把后续的计算资源留给真正有价值的标的。

筛选条件包括:流动性下限(过低意味着买卖困难)、24 小时成交量下限(基本无人交易的标的缺乏参考价值)、买卖价差上限(价差过大意味着滑点成本高)、竞争度上限,以及价格区间——只关注市场隐含概率在 5%~50% 之间的标的。太高的已经充分定价、上涨空间有限;太低的可信度存疑,更接近彩票而非套利机会。

通过筛选后,同一事件通常有 Yes 和 No 两个方向同时挂牌,策略只保留价格更低的那个,因为低价端的赔率弹性更大、潜在回报更高。

let truePrice = parseFloat(outcomePrices[outcomeIndex] || 0)
if (truePrice < CONFIG.MIN_TRUE_PRICE || truePrice > CONFIG.MAX_TRUE_PRICE) continue

let isActive = (
    info.active === true && info.approved === true &&
    info.closed === false && info.archived === false &&
    info.acceptingOrders === true && info.pendingDeployment === false
)
if (!isActive) continue

let spreadPct = (ask - bid) / ask
if (spreadPct > CONFIG.MAX_SPREAD_PCT) continue

// 同一事件只保留价格更低的方向
if (!marketMap[baseSymbol]) {
    marketMap[baseSymbol] = entry
} else if (truePrice < marketMap[baseSymbol].price) {
    marketMap[baseSymbol] = entry
}

所有筛选阈值都通过 $vars 变量暴露为外部参数,可以根据自己的风险偏好和资金体量自由调整,不需要改代码。

K 线异常检测:找资金留下的痕迹

粗筛完成后,对每个通过的标的拉取 60 分钟级别的最近 240 根 K 线,运行五类异常检测。这是策略的核心信号层,也是整个系统和「直接把事件丢给 AI 分析」最本质的区别——我们不猜事件结果,我们找资金行为。

缓慢爬升:过去 120 根 K 线总涨幅超过 5%,但单根最大涨幅低于 1.5%。这个特征描述的是资金刻意压住节奏、避免引起注意的建仓姿态。

成交量线性增长:对最近 60 根的成交量序列做线性回归,斜率为正且 R² 大于 0.5,说明成交量在持续、稳定地放大,而非随机脉冲。R² 是关键,它过滤掉了那种「单根暴量、其余沉寂」的噪音情况。

回调收窄:统计历史高点之后每次回调的最大深度,若近期平均回调低于早期的 60%,说明抛压在减少、**趋于稳定,有人在承接。每次回调期间持续追踪最低价,以完整回调幅度作为计算依据。

横盘突破:MA60 和 MA120 偏差小于 2%(横盘状态),而当前价格已超过 MA60 的 3%,判断为突破前期压力位。

成交量突增:近 5 根均量超过过去 60 根基准量的 2.5 倍,判断为资金加速入场。

function linearRegression(values) {
    let n = values.length
    let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0
    for (let i = 0; i < n; i++) {
        sumX += i; sumY += values[i]
        sumXY += i * values[i]; sumX2 += i * i
    }
    let slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
    let intercept = (sumY - slope * sumX) / n
    let meanY = sumY / n
    let ssTot = 0, ssRes = 0
    for (let i = 0; i < n; i++) {
        ssTot += Math.pow(values[i] - meanY, 2)
        ssRes += Math.pow(values[i] - (slope * i + intercept), 2)
    }
    let r2 = ssTot === 0 ? 0 : 1 - ssRes / ssTot
    return { slope, intercept, r2 }
}

function detectAnomaly(closes, volumes) {
    if (closes.length < 60) return null
    let anomalies = [], score = 0

    let slice120 = closes.slice(-Math.min(120, closes.length))

    // ── 异常1:缓慢爬升 ──────────────────────────────────────
    let totalChange = (slice120[slice120.length - 1] - slice120[0]) / slice120[0]
    let maxSingle = 0
    for (let i = 1; i < slice120.length; i++) {
        let change = Math.abs(slice120[i] - slice120[i - 1]) / slice120[i - 1]
        if (change > maxSingle) maxSingle = change
    }
    if (totalChange > 0.05 && maxSingle < 0.015) {
        score++
        anomalies.push(`缓慢爬升 总涨${(totalChange * 100).toFixed(1)}% 单根最大${(maxSingle * 100).toFixed(2)}%`)
    }

    // ── 异常2:成交量线性增长 ─────────────────────────────────
    let volSlice = volumes.slice(-60)
    let volReg = linearRegression(volSlice)
    if (volReg.slope > 0 && volReg.r2 > 0.5) {
        score++
        anomalies.push(`成交量线性增长 斜率${volReg.slope.toFixed(4)} R²${volReg.r2.toFixed(2)}`)
    }

    // ── 异常3:回调收窄(追踪每次回调最低价,计算完整深度)────
    let pullbacks = [], localHigh = closes[0]
    let inPullback = false, pullbackLow = closes[0]
    for (let i = 1; i < slice120.length; i++) {
        if (closes[i] > localHigh) {
            if (inPullback) {
                pullbacks.push((localHigh - pullbackLow) / localHigh)
                inPullback = false
            }
            localHigh = closes[i]
        } else if (closes[i] < localHigh * 0.99) {
            if (!inPullback) { inPullback = true; pullbackLow = closes[i] }
            else if (closes[i] < pullbackLow) { pullbackLow = closes[i] }
        }
    }
    if (pullbacks.length >= 3) {
        let first = pullbacks.slice(0, Math.floor(pullbacks.length / 2))
        let last  = pullbacks.slice(Math.floor(pullbacks.length / 2))
        let firstAvg = first.reduce((a, b) => a + b, 0) / first.length
        let lastAvg  = last.reduce((a, b) => a + b, 0) / last.length
        if (lastAvg < firstAvg * 0.6) {
            score++
            anomalies.push(`回调收窄 早期${(firstAvg * 100).toFixed(2)}% 近期${(lastAvg * 100).toFixed(2)}%`)
        }
    }

    // ── 异常4:横盘突破 ───────────────────────────────────────
    let ma60  = closes.slice(-60).reduce((a, b) => a + b, 0) / 60
    let ma120 = closes.length >= 120
        ? closes.slice(-120).reduce((a, b) => a + b, 0) / 120
        : ma60
    let currentPrice = closes[closes.length - 1]
    if (Math.abs(ma60 - ma120) / ma120 < 0.02 && currentPrice > ma60 * 1.03) {
        score++
        anomalies.push(`横盘突破 MA60=${ma60.toFixed(3)} MA120=${ma120.toFixed(3)} 当前=${currentPrice.toFixed(3)}`)
    }

    // ── 异常5:成交量突增 ─────────────────────────────────────
    let recentVol = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5
    let baseVol   = volumes.slice(-65, -5).reduce((a, b) => a + b, 0) / 60
    if (baseVol > 0 && recentVol > baseVol * 2.5) {
        score++
        anomalies.push(`成交量突增 近5根均量是基准${(recentVol / baseVol).toFixed(1)}倍`)
    }

    return { score, anomalies }
}

每命中一种异常加一分,满分 5 分,只有总分不低于 2 才进入下一阶段。单一信号可能是随机噪音,叠加才说明资金行为有一定可信度——这是整个 K 线检测逻辑的核心假设。

新闻搜索:给异常找一个基本面解释

K 线信号是「有资金在动」的证据,但资金为什么动、动得是否有依据,需要信息面来验证。这一步就是给每个 K 线异常标的搜新闻,看有没有对应的事件支撑。

这里使用 Brave Search API,每月提供 2000 次免费额度,对于每 10 分钟一轮的主线节奏基本够用。搜索关键词从 symbol 里提取,去掉方向后缀和分隔符后还原成可读的事件描述词。

const q = item.symbol
    .replace("_USDC.No", "")
    .replace("_USDC.Yes", "")
    .replace(/-/g, "+")

const rawResponse = HttpQuery(
    "https://api.search.brave.com/res/v1/web/search?q=" + q,
    { method: "GET", headers: { "X-Subscription-Token": CONFIG.BRAVE_API_KEY } }
)

// 遍历所有返回分区(网页、新闻等),统一提取结构化字段
Object.keys(data).forEach(key => {
    const section = data[key]
    if (section && section.results && Array.isArray(section.results)) {
        section.results.forEach(r => {
            news.push({ type: key, title: r.title, description: r.description, age: r.age })
        })
    }
})

搜出来的新闻和 K 线数据一起打包,进入下一步的 AI 分析——两条线同时验证,比只看一边可靠得多。

AI 多角色分析:四个视角,防止自我说服

数据备好后进入 AI 分析节点,模型根据自己的需要进行选择。提示词里定义了四个独立角色,这是整个系统最核心的设计决策,值得单独说清楚。

如果让一个 AI 同时看 K 线和新闻,它很容易在两者之间相互印证,得出一个表面合理但实际缺乏独立验证的结论——用资金信号给新闻背书,又用新闻给资金信号背书,最后输出一个「我觉得可以买」的自我强化结果。角色隔离强制了信息源的独立性:每个角色只看自己那份数据,只输出自己维度的结论,最后的决策角色只看结论、不看过程。

角色一:K 线资金分析师,不看新闻,只看价格和成交量异常,判断是否有真实资金在持续买入,输出资金信号强度(强/中/基准)。

角色二:新闻舆论分析师,不看 K 线,只评估信息面,判断是否有利好事件尚未被市场定价,输出新闻支撑等级(强/弱/无)。

角色三:价格评估分析师,综合前两个角色结论,估算事件真实概率区间,对比当前市场价,给出低估/合理/高估的评价。这个角色内置了到期日权重规则:距今超过 7 天正常估算;3 天以内且市价高于 0.15 时,强制收窄概率区间、禁止输出「严重低估」。理由很直接——临近到期,市场参与者掌握的实时信息比 AI 的训练数据更完整,此时 AI 的判断应该向市场价格让步。

角色四:最终决策分析师,按决策矩阵汇总前三个角色结论,输出最终动作。矩阵的逻辑是:资金信号是前提,新闻是验证,两者共同决定置信度;但无论置信度多高,只要价格评估达不到「低估」,一律不操作。

这里的新闻验证并不是在追信息,而是用来判断资金行为是否已经有了基本面支撑。内幕资金建仓时信息不会出现在公开新闻里,但资金信号叠加新闻支撑,说明信息正处于扩散中段、市场仍有定价滞后空间——这才是策略真正想捕捉的窗口。

资金信号 \ 新闻支撑 强验证 弱验证 无验证
强(score 4~5) 买入,confidence=高 买入,confidence=中 不操作
中(score 3) 买入,confidence=中 观望 不操作
基准(score 2) 观望 不操作 不操作

AI 的输出格式是严格的 JSON,包含各角色结论、真实概率区间、价格评估、最终决策、置信度和风险列表,直接供下游执行节点消费。

交易执行:只买最优的那一个

AI 对每个批次的标的逐一打分后,执行层不会把所有买入信号都下单——系统只挑评分最高的那一个。排序规则依次是:置信度(高 > 中 > 低)→ 价格评估(严重低估 > 低估)→ 真实概率区间宽度(越窄代表判断越确定)。

const buySignals = signals.filter(s => s.signal === 'buy')
bestSignal = buySignals.sort((a, b) => {
    const confDiff = (confidenceRank[b.confidence] || 0) - (confidenceRank[a.confidence] || 0)
    if (confDiff !== 0) return confDiff
    const assessDiff = (assessmentRank[b.priceAssessment] || 0) - (assessmentRank[a.priceAssessment] || 0)
    if (assessDiff !== 0) return assessDiff
    // 区间越窄越确定,优先选窄的
    const aWidth = (a.trueProb.max || 0) - (a.trueProb.min || 0)
    const bWidth = (b.trueProb.max || 0) - (b.trueProb.min || 0)
    return aWidth - bWidth
})[0]

选定标的后,实时获取盘口 Ask 价格,确认余额充足,按限价单提交。订单提交后轮询状态,最多等待 30 秒,超时则撤单,保证不留挂单积压。如果当轮 Ask 价格乘以配置份额低于 1 USDC 最小下单门槛,系统会自动按 ceil(1/askPrice) 向上取整到最小可成交份额。

止损模块:移动止损 + 绝对兜底

副线每 30 秒执行一次,逻辑分两步:先处理到期赎回,再对活跃持仓做移动止损检查。

Polymarket 有一个特殊机制——合约到期后资金不会自动回到账户,需要主动调用 redeem。策略在每轮副线开头统一扫一遍可赎回持仓,批量释放资金,避免资金长期锁定。

移动止损的核心是双轨止损线设计:移动止损线(历史最高 bid 价 - 固定跌幅 LOSS_AMOUNT)和绝对兜底线(入场价 × 50%),实际止损价取两者的较大值。

function calcStopPrice(entryPrice, maxPrice) {
    const trailingStopPrice  = maxPrice - CONFIG.LOSS_AMOUNT
    const absoluteStopPrice  = entryPrice * CONFIG.FALLBACK_STOP_RATIO  // 固定 0.50
    const usingFallback      = trailingStopPrice < absoluteStopPrice
    const effectiveStopPrice = usingFallback ? absoluteStopPrice : trailingStopPrice
    return { effectiveStopPrice, trailingStopPrice, absoluteStopPrice, usingFallback }
}

这个双轨设计对应两种典型持仓场景:盈利阶段,移动止损线随价格上移,锁住浮盈;入场即亏损时,移动止损线可能低至负值或接近零,此时绝对兜底线(入场价×50%)接管,防止持仓一路跌到归零才出场。两条线各司其职,缺一不可。

触发止损后调用市价卖出,清除该标的的历史最高价缓存,等待下一轮开仓信号。仪表板实时展示每个持仓距止损线的剩余空间,以颜色区分警戒程度,方便人工复核。


关键设计决策

为什么用多角色,而不是一个综合 Prompt? 这个问题前面在分析模块里提到过一部分,但值得再说清楚一点。单一 Prompt 的问题不仅是「自我说服」,还有一个更隐蔽的风险:当 AI 同时处理 K 线数据和新闻文本,它倾向于用后者解释前者,把 K 线异常「合理化」为新闻的必然反映,而不是把两者作为独立信号分别评估。角色隔离的本质是强制引入两条独立信息链,最终汇总时如果两条链都指向同一方向,可信度才是真实的。

为什么每轮只买最优一个,而不是分散持仓? 分散持仓在传统金融里有成熟的理论支撑,但在预测市场里逻辑不完全适用。预测市场标的的结果是二元的,归零风险是真实存在的。如果同时持有三个中等置信度标的,其中两个归零一个涨,整体未必能回本。当前策略的选择是:宁可在准确性上集中押注,也不用数量来稀释质量参差不齐的信号。这个取舍不一定是最优的,但逻辑上是自洽的。

到期日权重规则为什么要做成硬性约束? 临近到期时,市场价格反映的是参与者的实时预期,而这些参与者里有很**例掌握着比 AI 训练数据更新鲜的信息。如果此时 AI 仍然输出「价格严重低估、应该买入」,很可能是它的信息本身就已经过时。把这个规则做成约束而不是建议,是因为靠 AI 自己识别「我的信息已经不够新了」是不现实的,需要外部强制介入。


实盘观察

从实际运行记录来看,策略的漏斗比例相当陡峭。某次运行 141 个初步筛选通过的标的,K 线异常检测后剩 3 个,最终只有 1 个资金信号、新闻支撑、价格评估三项同时满足而触发下单,其余两个分别因为价格评估「合理」和综合评分不足被跳过。这说明策略整体偏保守,宁可空仓也不降标。在行情平稳、舆情无明显事件的窗口期,系统可能整天不产生任何买入信号。这是设计取向,不是 bug。也遇到过 K 线异常但随后被证明是假突破的情况,资金短暂进场又撤出,移动止损介入出场。新闻分析同样存在噪音,相关事件的报道本身可能就是滞后的或有偏差的,单靠 AI 无法完全识别。

仪表板实时展示账户权益、持仓状态(当前 Bid/Ask、最高价、回撤距止损线距离、止损模式)和 AI 决策详情,便于人工复核。

结尾

说实话,这套系统更多是一个可以跑起来的思路框架,而不是开箱即用的印钞机。核心逻辑——资金行为溯源、舆情双轨验证、多角色 AI 判断——在理念上有一定合理性,但真实市场里信号质量参差不齐,AI 的判断也会出错,临近到期的价格收敛风险始终存在。

可以完善的地方很多:交易标的可以进一步聚焦到自己熟悉的领域,AI 提示词可以按自己的交易风格重新设计,五种 K 线异常的权重和阈值都有调优空间,止损系数也值得根据实际持仓表现迭代。代码全部开源,欢迎拿去改。

风险提示

预测市场标的具有二元结果特性,持仓存在归零风险。K 线异常信号和 AI 分析结论均不构成投资建议,历史信号表现不代表未来收益。策略未经长周期实盘验证,参数设置对结果影响显著。在使用本策略进行实盘交易前,请充分理解 Polymarket 平台规则、合约到期机制及流动性风险,并自行评估本金损失的承受能力。

 

免责声明:信息仅供参考,不构成投资及交易建议。投资者据此操作,风险自担。
如果觉得文章对你有用,请随意赞赏收藏
相关推荐
相关下载
登录后评论
Copyright © 2019 宽客在线