Polymarket 机器人教程 · 第 24 章,共 32 章

Polymarket Perps 提供完整的公开 API,并配有官方 TypeScript 和 Python SDK。本章基于真实接口构建一个机器人,内容包括:使用代理交易密钥进行身份验证,通过 REST 和 WebSocket 获取市场数据,下达带止盈和止损的订单,以及防止杠杆机器人把账户亏空的风控措施。

Build your own theme park, free in your browser广告 在此投放广告
动手之前。美国、加拿大、古巴、伊朗、朝鲜、叙利亚、克里米亚、顿涅茨克和卢甘斯克不允许下达 Perps 订单,polymarket.com 还屏蔽了更多国家。使用机器人或境外服务器并不能改变这一点。在杠杆作用下,一个 bug 就能让你迅速亏钱,所以请用最小仓位规模测试。刚接触 Perps?请先阅读 Polymarket Perps 指南。

本章内容

  • 机器人开发者需要了解的关键数据
  • Perps API 的架构
  • 第 1 步:安装 SDK 并开启会话
  • 第 2 步:通过 REST 和 WebSocket 读取市场数据
  • 第 3 步:下达带止盈和止损的订单
  • 第 4 步:在代码中内置风控的机器人框架
  • 错误处理、失联保护开关、机器人规则与托管

本文中的每个 SDK 方法和端点都来自 Polymarket 的 Perps 文档(docs.polymarket.com/perps,查阅于 2026年11月6日)。我们自己编写的逻辑已在注释中标明,策略函数是一个标注清楚的占位符。

机器人开发者需要了解的关键数据(截至 2026年11月)

项目数值
REST 基础 URLhttps://api.perpetuals.polymarket.com
WebSocketwss://ws.perpetuals.polymarket.com/v1/ws
TypeScript SDK@polymarket/client(签名使用 viem)
Python SDKpolymarket-client(异步)
公开数据/v1/info/*,无需身份验证
私有读取/v1/account/*,需带 polymarket-proxy 和 polymarket-secret 请求头
签名写入/v1/trade/*,由代理密钥进行 EIP-712 签名,有效期 5 分钟
会话凭证默认一周后过期
最小订单名义价值 $10;最低入金 10 pUSD
最大市价单50x 和 20x 市场为 100万美元,大多数其他市场为 50万美元,3x 和 5x 币种为 5万美元
价格和数量格式十进制字符串,小数位不超过该市场的精度,且有效数字不超过 5 位
最高杠杆50x(BTC、ETH、USA500、US100),其他市场分别为 20x、10x、5x 或 3x,大仓位的杠杆上限更低
手续费(基础等级)吃单方 0.040%,挂单方 0.0125%;ANT 半价
资金费率每小时结算,上限为每小时 +/-4%
TP/SL 触发基于标记价格,而非最新成交价
WebSocket 保活约每 25 秒 ping 一次;静默约 65 秒后视为连接失效
Ticker 数据时效最多延迟 10 秒

Perps API 的架构

Perps 是一个混合型交易所:撮合在链下完成,资产托管和结算在 Polygon 上完成。只有入金和出金在链上进行,因此每笔订单都不需要支付 gas。API 分为三层:

  • 公开数据(/v1/info/):合约品种、ticker、订单簿、K 线、成交记录、资金费率、手续费。无需密钥,也没有地区限制。
  • 私有读取(/v1/account/):投资组合、余额、订单、成交、配置。
  • 签名写入(/v1/trade/):下单、撤单、杠杆、逐仓保证金、自动撤单。每次写入都经过 MessagePack 编码、keccak256 哈希,再由代理密钥以 EIP-712 类型化数据签名。

SDK 会替你完成签名。此外还有 OpenAPI 规范(文档站点上的 /api-spec/perps-openapi.json)以及 WebSocket 的 AsyncAPI 规范。

交易代码与文档示例不同。文档中写的是 BTC-PERP,而线上 API 返回的是 BTC-USD、SP500-USD(USA500)、NAS100-USD(US100)、ANT-USD 等。请先获取合约品种列表,匹配线上的 symbol,再保存数字 id。不要从示例中复制 ID:线上 BTC 为 6,SP500 为 1。

第 1 步:安装 SDK 并开启会话

使用 pip install polymarket-client 或 npm install @polymarket/client@latest viem 安装。机器人需要你的 Polymarket 钱包地址,以及拥有该 Perps 账户的签名者私钥(账户绑定的是签名者 EOA,而不是 Safe)。开启会话时只需签名一次,即可授权一个新的代理交易密钥,之后所有订单都由它签名,你的资金密钥永远不会直接参与交易。请将 session.credentials 加密保存,以便日后恢复会话。

import asyncio
import os
from datetime import timedelta

from polymarket import AsyncSecureClient


async def main():
    client = await AsyncSecureClient.create(
        private_key=os.environ["PRIVATE_KEY"],
        wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
    )
    # One signature creates a proxy trading key. Orders are signed by the proxy,
    # not by your wallet key. Credentials expire after one week by default.
    session = await client.open_perps_session(
        expires_in=timedelta(days=1),
        label="perps-bot",
    )
    stored = session.credentials.model_dump(mode="json")  # keep it encrypted

    instruments = await client.fetch_perps_instruments()
    btc = next(i for i in instruments if i.symbol == "BTC-USD")
    print(btc.id, btc.max_leverage, btc.min_notional, btc.price_decimals)


asyncio.run(main())
import { createSecureClient } from "@polymarket/client";
import { privateKey } from "@polymarket/client/viem";

const client = await createSecureClient({
  wallet: process.env.POLYMARKET_WALLET_ADDRESS!,
  signer: privateKey(process.env.PRIVATE_KEY!),
});

// One signature creates a proxy trading key. Orders are signed by the proxy,
// not by your wallet key. Credentials expire after one week by default.
const session = await client.openPerpsSession({
  expiresIn: 24 * 60 * 60 * 1000,
  label: "perps-bot",
});
const stored = session.credentials; // keep it encrypted

const instruments = await client.fetchPerpsInstruments();
const btc = instruments.find((i) => i.symbol === "BTC-USD");
if (!btc) throw new Error("BTC-USD not listed");
console.log(btc.id, btc.maxLeverage, btc.minNotional, btc.priceDecimals);

通过代码入金(deposit_to_perps / depositToPerps)还需要一个 Relayer API 密钥,可在 polymarket.com 的“设置 > API 密钥”中获取。更简单也更安全的做法是:在网页端为账户入金,让机器人完全不碰资金划转。自 2026年11月5日起,启用了出金地址簿的账户只能向已保存的地址出金。仪表盘请使用只读密钥(标签为 read-only: 的代理密钥,会拒绝签名写入),切勿使用交易密钥。密钥存储方法详见我们的钱包与身份验证章节。

第 2 步:通过 REST 和 WebSocket 读取市场数据

Ticker 包含三种价格:指数价格(来自 Pyth、Chainlink Data Streams 和 Hyperliquid 等数据源的公允价值)、标记价格(用于 PnL、保证金、强平和 TP/SL)以及最新成交价(仅反映成交),另外还有资金费率和下一次资金费结算时间。交易信号应基于标记价格或指数价格。

# Inside your async main(), after Step 1 (btc is the BTC-USD instrument)
from polymarket import AsyncPublicClient
from polymarket.streams import PerpsBboSpec

client = AsyncPublicClient()

ticker = await client.fetch_perps_ticker(instrument_id=btc.id)
print(ticker.mark_price, ticker.index_price, ticker.funding_rate)

stream = await client.subscribe(PerpsBboSpec(instrument_id=btc.id))
async for event in stream:
    print(event.payload.bid_price, event.payload.ask_price)
// After Step 1 (btc is the BTC-USD instrument)
import { createPublicClient } from "@polymarket/client";

const client = createPublicClient();

const ticker = await client.fetchPerpsTicker({ instrumentId: btc.id });
console.log(ticker.markPrice, ticker.indexPrice, ticker.fundingRate);

const stream = await client.subscribe([{ topic: "perps.bbo", instrumentId: btc.id }]);
for await (const event of stream) {
  console.log(event.payload.bidPrice, event.payload.askPrice);
}

不使用 SDK 时,可以在原始 WebSocket 上用 {"id": 1, "req": "sub", "chs": ["bbo::6"]} 订阅。频道包括 bbo::<id>、book::<id>、trades::<id>、tickers::<id> 和 klines::<id>::1m。每条更新都带有序列号 sq:出现缺口或重新连接后,请通过 REST 重新读取状态。回测时,list_perps_candles / listPerpsCandles 可分页获取历史数据(REST 端点每次调用最多返回 1,000 根 K 线)。

第 3 步:下达带止盈和止损的订单

一个订单包含 side(BUY 或 SELL)、以基础资产为单位的 quantity、可选的以 pUSD 计价的 price,以及有效期类型(gtc、ioc 或 fok)。只挂单(post-only)必须配合 GTC;只减仓(reduce-only)只能缩小仓位。订单没有多空标志:无仓位时,BUY 即开多,SELL 即开空。

括号订单会为入场单附加一个止盈和一个止损。它们在入场单完全成交之前保持休眠,若入场单被撤销或仅部分成交,则会被取消。以下价格均为假设。

# Inside your async main(), after Step 1 (btc is the BTC-USD instrument)
from polymarket import PerpsTpSlTrigger

result = await session.place_order(
    instrument_id=btc.id,
    side="BUY",
    price="100000",
    quantity="0.01",
    time_in_force="gtc",
    take_profit=PerpsTpSlTrigger(trigger_price="104000"),
    stop_loss=PerpsTpSlTrigger(trigger_price="98000"),
)
print(result.order.id, result.order.status)
// After Step 1 (btc is the BTC-USD instrument)
import { OrderSide, PerpsTimeInForce } from "@polymarket/client";

const result = await session.placeOrder({
  instrumentId: btc.id,
  side: OrderSide.BUY,
  price: "100000",
  quantity: "0.01",
  timeInForce: PerpsTimeInForce.GTC,
  takeProfit: { triggerPrice: "104000" },
  stopLoss: { triggerPrice: "98000" },
});
console.log(result.order.id, result.tpSl.stopLoss?.orderId);

对于已有仓位,place_position_tp_sl / placePositionTpSl 可添加平掉整个仓位的触发单。规则如下:

  • 触发单监控标记价格,触发后发送只减仓订单。仓位 TP/SL 以市价平仓,因此在订单簿较薄时要预期滑点。
  • 每个市场只能设置一个仓位止盈和一个仓位止损。移动止损需要先撤销再新建,这一过程不是原子操作。
  • 如果标记价格已越过触发价,该触发单会被拒绝;限价与标记价格偏离过大时也会被拒绝。

在实现自动化之前,如果想先手动体验下单流程,可以打开 Polymarket Perps 上的 BTC 市场,下一笔允许的最小订单。亲眼看到成交、手续费和持仓面板之后,再读 API 返回的数据会容易得多。

第 4 步:在代码中内置风控的机器人框架

下面的 Python 框架只使用了文档中记载的 SDK 调用,风控逻辑由我们编写。它有三层保护:

  1. 杠杆上限。杠杆设为 3x 与市场上限中较低的一个,采用逐仓保证金;若仓位规模会落入杠杆上限更低的风险档位,则拒绝下单。
  2. 紧急停止开关。包括 HALT 文件、5% 的单日亏损上限,以及在强平时退出。开关触发后,会用只减仓的 IOC 订单平掉仓位,撤销剩余订单,然后确认两者都已清空。
  3. 每个仓位都有止损。入场使用全部成交否则取消(FOK)订单,因此不会出现缺乏保护的部分成交。每次成交后立即设置仓位止损和止盈;如果被拒绝,则立即平仓。
import asyncio
import logging
import os
from datetime import timedelta
from decimal import Decimal, ROUND_DOWN

from polymarket import AsyncSecureClient, PerpsPositionTpSlTrigger

SYMBOL = "BTC-USD"                   # live API symbol; docs examples say "BTC-PERP"
LEVERAGE_CAP = 3                     # our cap, far below the 50x BTC allows
RISK_PER_TRADE = Decimal("20")       # pUSD we accept losing if the stop fills
STOP_PCT = Decimal("0.02")           # stop 2% from entry
TAKE_PCT = Decimal("0.04")           # take profit 4% from entry
MAX_NOTIONAL = Decimal("2000")       # hard cap per position, in pUSD
MAX_SLIPPAGE = Decimal("0.002")      # worst entry price: ask + 0.2%
MAX_FUNDING = Decimal("0.0005")      # skip longs if funding > 0.05% per hour
DAILY_LOSS_LIMIT = Decimal("0.05")   # kill switch at -5% of start equity
HALT_FILE = "/opt/perps-bot/HALT"    # touch this file to stop the bot


def fit(value: Decimal, decimals: int, sig: int = 5) -> str:
    """Round down to the market's decimals and to 5 significant figures."""
    v = value.quantize(Decimal(1).scaleb(-decimals), rounding=ROUND_DOWN)
    if v != 0:
        step = Decimal(1).scaleb(v.adjusted() - sig + 1)
        if step > Decimal(1).scaleb(-decimals):
            v = v.quantize(step, rounding=ROUND_DOWN)
    return format(v, "f")


def tier_cap(inst, notional: Decimal) -> int:
    """Max leverage allowed for this notional by the market's risk tiers."""
    cap = inst.max_leverage
    for tier in inst.risk_tiers:
        if notional >= Decimal(tier.lower_bound):
            cap = tier.max_leverage
    return cap


async def close_all(session):
    """Close every position with reduce-only IOC orders, then cancel the rest."""
    portfolio = await session.fetch_portfolio()
    for pos in portfolio.positions:
        if pos.size != 0:
            await session.place_order(
                instrument_id=pos.instrument_id,
                side="SELL" if pos.size > 0 else "BUY",
                quantity=abs(pos.size),
                time_in_force="ioc",
                reduce_only=True,
            )
    await session.cancel_all_orders()


async def kill_switch(session, reason: str):
    logging.error("KILL SWITCH: %s", reason)
    for attempt in range(3):
        await close_all(session)
        portfolio = await session.fetch_portfolio()
        open_orders = await session.fetch_open_orders()
        if not open_orders and all(p.size == 0 for p in portfolio.positions):
            return
        await asyncio.sleep(2)
    logging.critical("Positions still open after kill switch: act manually")


async def open_long(client, session, inst):
    ticker = await client.fetch_perps_ticker(instrument_id=inst.id)
    if Decimal(ticker.funding_rate) > MAX_FUNDING:
        return                                     # longs would pay too much
    book = await client.fetch_perps_book(instrument_id=inst.id, depth=10)
    if not book.asks:
        return
    ask = Decimal(book.asks[0].price)
    stop = ask * (1 - STOP_PCT)
    qty = RISK_PER_TRADE / (ask - stop)            # size from the stop, not leverage
    qty = min(qty, MAX_NOTIONAL / ask)
    notional = qty * ask
    if notional < Decimal(inst.min_notional):
        return
    if tier_cap(inst, notional) < LEVERAGE_CAP:
        return                                     # size would break our cap

    entry = await session.place_order(
        instrument_id=inst.id,
        side="BUY",
        price=fit(ask * (1 + MAX_SLIPPAGE), inst.price_decimals),
        quantity=fit(qty, inst.quantity_decimals),
        time_in_force="fok",                       # all or nothing
    )
    if entry.order.status != "filled":
        return

    try:                                           # stop on the whole position
        await session.place_position_tp_sl(
            instrument_id=inst.id,
            take_profit=PerpsPositionTpSlTrigger(
                trigger_price=fit(ask * (1 + TAKE_PCT), inst.price_decimals)),
            stop_loss=PerpsPositionTpSlTrigger(
                trigger_price=fit(stop, inst.price_decimals)),
        )
    except Exception:
        logging.exception("TP/SL rejected: closing the unprotected position")
        await close_all(session)


async def my_signal(client, inst) -> bool:
    """PLACEHOLDER: your strategy goes here. Not part of the SDK."""
    return False


async def main():
    logging.basicConfig(level=logging.INFO)
    client = await AsyncSecureClient.create(
        private_key=os.environ["PRIVATE_KEY"],
        wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
    )
    session = await client.open_perps_session(
        expires_in=timedelta(days=1), label="perps-bot")
    inst = next(i for i in await client.fetch_perps_instruments()
                if i.symbol == SYMBOL)

    # Control 1: leverage cap and isolated margin, set before any order
    await session.update_leverage(
        instrument_id=inst.id,
        leverage=min(LEVERAGE_CAP, inst.max_leverage),
        cross_margin=False,
    )

    # Field names below follow the documented portfolio JSON
    # (margin.total_account_value, in_liquidation). Check them in your SDK.
    start_equity = Decimal((await session.fetch_portfolio()).margin.total_account_value)

    while True:
        portfolio = await session.fetch_portfolio()
        equity = Decimal(portfolio.margin.total_account_value)

        # Control 2: kill switches
        if os.path.exists(HALT_FILE):
            await kill_switch(session, "HALT file found")
            break
        if equity < start_equity * (1 - DAILY_LOSS_LIMIT):
            await kill_switch(session, f"daily loss limit hit: {equity}")
            break
        if portfolio.in_liquidation:
            await kill_switch(session, "account in liquidation")
            break

        in_position = any(p.instrument_id == inst.id and p.size != 0
                          for p in portfolio.positions)
        if not in_position and await my_signal(client, inst):
            await open_long(client, session, inst)   # Control 3 lives here

        await asyncio.sleep(5)


asyncio.run(main())

仓位规模的计算。仓位规模由止损距离决定,而不是由杠杆决定。假设 BTC 卖价为 100,000 pUSD,止损设在 2%,则止损价为 98,000。每单位风险为 2,000,因此数量为 20 / 2,000 = 0.01 BTC,名义价值 1,000 pUSD。在 3x 杠杆下,大约需要 333 pUSD 保证金。BTC 的维持保证金率为 1%(0.5 / 50x),所以多头的强平价格为 100,000 x (1 - 1/3) / (1 - 0.01) = 约 67,340,即下跌 32.7%。-2% 的止损会远早于此触发。如果止损在触发价成交,亏损为 20 pUSD 加手续费:按 0.040% 的吃单费率,开仓 0.40,平仓约 0.39,合计滑点前约 20.79 pUSD。杠杆只决定占用多少保证金,并不改变止损时的亏损。

上线之前,请再加上每日交易次数上限、成交与紧急停止开关的告警,以及启动时的状态核对(fetch_portfolio、fetch_open_orders、fetch_orders、list_fills)。另请参阅我们的风控代码和模拟交易章节、Perps 杠杆与强平指南以及 Perps 资金费率指南。

错误、重试与失联保护开关

  • 503 service_unavailable 并不代表请求一定失败。对于读取和杠杆修改,请以退避方式重试,并遵守 Retry-After。对于涉及余额变动的签名请求(保证金、出金),只能重发完全相同的原始签名请求;新的签名会被视为第二次调整。
  • signature expired:签名已超过 5 分钟。请保持系统时钟同步。
  • insufficient_margin / invalid_leverage:校验时会同时计入你的仓位和所有挂单。
  • message_rate_limited:WebSocket 配额已用完。只读密钥每个 IP 每分钟可发送 1,000 条消息(突发上限 50),每个账户在每个网关最多 4 个连接;其余限制请参阅文档中的 Rate Limits 页面。
  • 格式错误:小数位过多或有效数字超过 5 位。这就是需要 fit() 的原因。

自动撤单会在你的机器人停止重新设定它时撤销所有未成交订单。截止时间必须至少在 5 秒之后,且每个 UTC 日的触发次数有限,因此正常关闭时请解除设定。

// For quoting bots: cancel all resting orders if this process dies
await session.armAutoCancel({ cancelAt: Date.now() + 60_000 });
const rearm = setInterval(() => {
  // A missed re-arm is fail-safe: the armed switch still fires.
  session.armAutoCancel({ cancelAt: Date.now() + 60_000 }).catch(console.error);
}, 20_000);

// On a clean shutdown
clearInterval(rearm);
await session.disarmAutoCancel();
自动撤单与止损。文档将自动撤单列为 TP/SL 订单被取消的途径之一,因此程序崩溃可能会移除已有仓位上的止损。自动撤单适合做市商等持续报价的机器人。我们的框架没有挂单入场,因此关闭了自动撤单,以保住止损单。无论如何,真正让崩溃的机器人免于强平的,是低杠杆。

交易竞赛、Builder 代码与机器人规则

文档将“对市场信号做出反应的交易机器人”和做市商列为 API 的用途。有两套规则需要遵守。

  • 交易竞赛。禁止对敲交易、自成交、多账户、主要为刷成交量而交易,以及“意图操纵该活动的机器人、脚本或自动化手段”。真实交易的机器人没有问题;刷成交量的机器人可能被取消资格。
  • Builder 代码。为其他用户执行交易的应用,在每位交易者同意的前提下,可额外收取最多 10 个基点的手续费。应用必须核验每个用户的所在地,并拦截来自受限司法管辖区的订单,而不能仅仅发出警告。

挂单方流动性奖励和交易竞赛的内容,请参阅 Polymarket Perps 指南。

托管机器人

杠杆机器人不应该跑在笔记本电脑上。它必须保持 WebSocket 连接、在数秒内响应成交,并且能扛住家庭网络断线。请在 VPS 上用 systemd 运行并开启自动重启,将密钥放在只有机器人用户才能读取的环境变量文件中,并同步系统时钟。受限地区境内不允许下单,因此不要把机器人托管在美国或加拿大,也绝不要用 VPS 绕过适用于你的限制。我们自己的机器人运行在 TradingVPS 上(推广链接:我们会获得少量佣金,你支付的价格不变)。我们的 VPS 托管章节从延迟、价格和可用地区等方面将它与其他服务商做了对比。

完成模拟交易后,请在 Polymarket Perps 页面向账户存入少量资金,以最小仓位规模运行一周,再考虑扩大规模。

要点总结

  • 使用官方 SDK:@polymarket/client 或 polymarket-client。你的钱包只签名一次,之后由代理密钥签署订单。
  • 按线上交易代码匹配市场(BTC-USD,而不是 BTC-PERP),并保存数字 ID。
  • 根据止损距离确定仓位规模,在代码中限制杠杆,使用逐仓保证金,并在成交后立即为每个仓位设置止损。
  • TP/SL 按标记价格触发,以市价平仓。自动撤单可能会移除止损。
  • 允许使用机器人,但不允许操纵竞赛。绝不要在受限国家进行交易。

常见问题

Polymarket Perps 有供交易机器人使用的 API 吗?
有。REST API 地址为 api.perpetuals.polymarket.com,WebSocket 地址为 ws.perpetuals.polymarket.com,并提供官方 SDK:TypeScript 版 @polymarket/client 和 Python 版 polymarket-client。Polymarket 文档将交易机器人和做市系统列为支持的用途。
应该用 TypeScript SDK 还是 Python SDK?
两者都可以。它们覆盖相同的流程:创建安全客户端、开启 Perps 会话、获取合约品种、下达带 TP/SL 的订单,以及接收事件流。TypeScript SDK 使用 viem 签名,Python SDK 为异步实现。选择你的机器人已在使用的语言即可。
我的机器人需要主钱包私钥吗?
只在开启会话时需要。这一次签名会授权一个代理交易密钥,此后所有订单都由代理密钥签名。会话凭证默认一周后过期。请加密保存凭证,仪表盘使用只读密钥。
如何通过 Perps API 设置止损?
可以将 takeProfit 和 stopLoss 触发单作为括号订单附加到入场单上,也可以对已有仓位调用 placePositionTpSl(Python 中为 place_position_tp_sl)。触发单监控标记价格,并以只减仓订单平仓。仓位 TP/SL 会以市价平掉整个仓位。
Polymarket Perps 的最小订单规模是多少?
每笔订单名义价值 $10,最低入金 10 pUSD(截至 2026年11月)。价格和数量还必须符合各市场的小数位精度,且有效数字不超过 5 位,否则订单会被拒绝。
有紧急停止开关或失联保护开关吗?
有。自动撤单会在你的机器人停止重新设定它时撤销所有未成交订单,cancelAllOrders 则可随时清空订单。文档将自动撤单列为 TP/SL 订单可能被取消的途径之一,因此程序崩溃也可能移除你的止损。请同时在代码中实现自己的紧急停止开关。
我可以在美国或加拿大运行 Polymarket Perps 机器人吗?
不可以。美国、加拿大、古巴、伊朗、朝鲜、叙利亚、克里米亚、顿涅茨克和卢甘斯克不允许下达 Perps 订单。不要把机器人托管在这些地区,也不要用 VPS 或 VPN 绕过适用于你的限制。
Polymarket Perps 交易竞赛允许使用机器人吗?
允许使用机器人进行交易。竞赛规则禁止对敲交易、自成交、多账户、主要为制造成交量而交易,以及意图操纵该活动的机器人或脚本。违反规则可能导致被取消资格。