폴리마켓 봇 튜토리얼 · 32장 중 24장

폴리마켓 Perps는 공식 TypeScript 및 Python SDK를 갖춘 완전한 공개 API를 제공합니다. 이 장에서는 실제 인터페이스로 봇을 만듭니다. 프록시 거래 키를 이용한 인증, REST와 WebSocket을 통한 시장 데이터 조회, 익절과 손절을 붙인 주문, 그리고 레버리지 봇이 계정을 비우지 않도록 막아 주는 리스크 관리 장치까지 다룹니다.

나만의 테마파크, 브라우저에서 무료로광고 광고 문의
개발을 시작하기 전에. Perps 주문은 미국, 캐나다, 쿠바, 이란, 북한, 시리아, 크림, 도네츠크, 루한스크에서 할 수 없으며, polymarket.com은 이 밖에도 여러 국가를 차단합니다. 봇이나 해외 서버를 쓴다고 이 제한이 달라지지 않습니다. 레버리지를 쓰면 버그 하나로도 돈을 빠르게 잃을 수 있으니 최소 규모로 테스트하세요. Perps가 처음이라면 폴리마켓 Perps 가이드부터 읽어 보세요.

이 장에서 다루는 내용

  • 봇 개발자를 위한 핵심 수치
  • Perps API의 구조
  • 1단계: SDK 설치와 세션 열기
  • 2단계: REST와 WebSocket으로 시장 데이터 읽기
  • 3단계: 익절과 손절을 붙여 주문하기
  • 4단계: 리스크 관리를 코드에 담은 봇 뼈대
  • 오류, 데드맨 스위치, 봇 관련 규칙과 호스팅

여기 나오는 모든 SDK 메서드와 엔드포인트는 폴리마켓의 Perps 문서(docs.polymarket.com/perps, 2026-11-06 확인)를 근거로 합니다. 저희가 직접 작성한 로직은 주석으로 표시했고, 전략 함수는 자리 표시용임을 명시했습니다.

봇 개발자를 위한 핵심 수치(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분간 유효
세션 자격 증명기본적으로 1주일 후 만료
최소 주문명목 금액 $10, 최소 입금 10 pUSD
최대 시장가 주문50x 및 20x 시장은 $1M, 대부분의 다른 시장은 $500K, 3x 및 5x 코인은 $50K
가격 및 수량 형식10진수 문자열, 시장의 소수 자릿수와 유효 숫자 5자리 이내
최대 레버리지50x(BTC, ETH, USA500, US100), 그 외 시장별 20x, 10x, 5x 또는 3x, 대규모 포지션은 더 낮음
수수료(기본 등급)테이커 0.040%, 메이커 0.0125%, ANT는 절반
펀딩매시간, 시간당 +/-4% 상한
TP/SL 트리거최근 체결가가 아닌 마크 가격 기준
WebSocket 연결 유지약 25초마다 ping, 약 65초간 응답이 없으면 끊긴 것으로 간주
티커 최신성최대 10초 지연

Perps API의 구조

Perps는 하이브리드 거래소입니다. 매칭은 오프체인에서, 자산 보관과 정산은 Polygon에서 이루어집니다. 온체인에서 처리되는 것은 입금과 출금뿐이라 주문마다 가스비가 들지 않습니다. API는 세 계층으로 나뉩니다.

  • 공개 데이터(/v1/info/): 상품, 티커, 호가창, 캔들, 체결 내역, 펀딩, 수수료. 키가 필요 없고 지역 차단도 없습니다.
  • 비공개 조회(/v1/account/): 포트폴리오, 잔고, 주문, 체결, 설정.
  • 서명 쓰기(/v1/trade/): 주문, 취소, 레버리지, 격리 증거금, 자동 취소. 모든 쓰기 요청은 MessagePack으로 인코딩되고 keccak256으로 해시된 뒤, 프록시 키가 EIP-712 typed data로 서명합니다.

서명은 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로 설치합니다. 봇에는 폴리마켓 지갑 주소와, Perps 계정을 소유한 서명자의 개인 키가 필요합니다(계정은 Safe가 아니라 서명자 EOA에 연결됩니다). 세션을 열 때 한 번 서명하면 새 프록시 거래 키가 승인되고, 이후 모든 주문은 이 키가 서명하므로 자금이 들어 있는 키는 거래에 쓰이지 않습니다. 나중에 세션을 이어 가려면 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) polymarket.com의 설정 > API 키에서 발급하는 Relayer API 키도 필요합니다. 더 간단하고 안전한 방법은 웹 앱에서 계정에 자금을 넣고 봇은 자금 이동에 관여하지 않게 하는 것입니다. 2026-11-05부터 출금 주소록을 사용하는 계정은 저장된 주소로만 출금할 수 있습니다. 대시보드에는 거래 키가 아닌 읽기 전용 키(read-only: 라벨이 붙은 프록시로, 서명 쓰기를 거부합니다)를 쓰세요. 키 보관 방법은 지갑과 인증 장에서 다룹니다.

2단계: REST와 WebSocket으로 시장 데이터 읽기

티커에는 세 가지 가격이 있습니다. 인덱스(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개의 캔들을 반환합니다).

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로 포지션 전체를 청산하는 트리거를 추가합니다. 규칙은 다음과 같습니다.

  • 트리거는 마크 가격을 감시하다가 reduce-only 주문을 보냅니다. 포지션 TP/SL은 시장가로 청산되므로 호가창이 얇으면 슬리피지를 예상해야 합니다.
  • 시장마다 포지션 익절 하나, 손절 하나만 둘 수 있습니다. 손절 가격을 옮기려면 취소 후 새로 생성해야 하며, 이 과정은 원자적이지 않습니다.
  • 마크 가격이 이미 지나간 트리거는 거부되고, 마크 가격에서 너무 먼 지정가도 거부됩니다.

자동화하기 전에 주문 흐름을 직접 해 보고 싶다면 폴리마켓 Perps의 BTC 시장을 열고 허용되는 최소 주문을 넣어 보세요. 체결, 수수료, 포지션 패널을 직접 보면 API 응답을 읽기가 훨씬 쉬워집니다.

4단계: 리스크 관리를 코드에 담은 봇 뼈대

아래 Python 뼈대는 문서화된 SDK 호출만 사용하며, 리스크 로직은 저희가 작성했습니다. 보호 장치는 세 겹입니다.

  1. 레버리지 상한. 레버리지는 3x와 시장 최대치 중 낮은 쪽으로, 격리 증거금으로 설정하며, 상한이 더 낮은 리스크 등급에 해당하는 규모의 주문은 거부합니다.
  2. 킬 스위치. HALT 파일, 일일 손실 한도 5%, 청산 시 종료. 킬 스위치는 reduce-only IOC 주문으로 포지션을 닫고 남은 주문을 취소한 뒤, 둘 다 사라졌는지 확인합니다.
  3. 모든 포지션에 손절. Fill-or-kill 진입을 쓰므로 보호되지 않은 부분 체결이 생기지 않습니다. 체결될 때마다 포지션 손절과 익절을 걸고, 이것이 거부되면 포지션을 즉시 청산합니다.
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 주문이 취소되는 경우 중 하나로 나와 있어, 봇이 멈추면 열린 포지션의 손절이 사라질 수 있습니다. 자동 취소는 마켓 메이커처럼 호가를 내는 봇에 적합합니다. 저희 뼈대에는 대기 중인 진입 주문이 없으므로 손절을 유지하기 위해 자동 취소를 끄고 씁니다. 어느 쪽이든 멈춘 봇이 청산당하지 않게 지켜 주는 것은 낮은 레버리지입니다.

대회, 빌더 코드, 봇 관련 규칙

문서는 API 활용 예로 "시장 신호에 반응하는 트레이딩 봇"과 마켓 메이커를 듭니다. 적용되는 규칙은 두 가지입니다.

  • 대회. 자전 거래, 자기 거래, 다중 계정, 주로 거래량을 위한 거래, 그리고 "프로그램을 조작하려는 목적의 봇, 스크립트 또는 자동화 수단"은 금지됩니다. 실제로 거래하는 봇은 괜찮지만, 거래량만 부풀리는 봇은 실격될 수 있습니다.
  • 빌더 코드. 다른 사용자를 대신해 거래하는 앱은 각 트레이더의 승인을 받아 최대 10bp의 수수료를 붙일 수 있습니다. 앱은 사용자마다 위치를 확인해야 하며, 제한 지역의 주문은 경고만 하는 것이 아니라 차단해야 합니다.

메이커 유동성 보상과 대회는 폴리마켓 Perps 가이드에서 다룹니다.

봇 호스팅

레버리지 봇을 노트북에서 돌려서는 안 됩니다. WebSocket 연결을 유지하고, 몇 초 안에 체결에 반응하고, 집 인터넷이 끊겨도 버텨야 하기 때문입니다. VPS에서 systemd로 자동 재시작을 설정해 실행하고, 키는 봇 사용자만 읽을 수 있는 환경 파일에 두고, 시계를 동기화하세요. 제한 지역에서는 주문할 수 없으므로 미국이나 캐나다에 호스팅하지 말고, 본인에게 적용되는 차단을 우회하려고 VPS를 쓰는 일은 절대 없어야 합니다. 저희는 자체 봇을 TradingVPS에서 운영합니다(제휴 링크: 저희가 소액의 수수료를 받지만 결제 금액은 같습니다). VPS 호스팅 장에서 지연 시간, 가격, 지역 기준으로 다른 업체와 비교합니다.

모의 거래를 마친 뒤 폴리마켓 Perps 페이지에서 계정에 소액을 입금하고, 규모를 키우기 전에 최소 규모로 일주일간 운영해 보세요.

핵심 정리

  • 공식 SDK인 @polymarket/client 또는 polymarket-client를 사용하세요. 지갑은 한 번만 서명하고, 주문 서명은 프록시 키가 맡습니다.
  • 시장은 실제 심볼(BTC-PERP가 아닌 BTC-USD)로 대조하고 숫자 ID를 저장하세요.
  • 규모는 손절 거리로 정하고, 레버리지 상한을 코드에 넣고, 격리 증거금을 쓰고, 체결 직후 모든 포지션에 손절을 거세요.
  • TP/SL은 마크 가격 기준으로 작동하고 시장가로 청산됩니다. 자동 취소가 손절을 지울 수 있습니다.
  • 봇은 허용되지만 대회 조작은 금지됩니다. 제한 국가에서는 절대 거래하지 마세요.

자주 묻는 질문

폴리마켓 Perps에 트레이딩 봇용 API가 있나요?
네. api.perpetuals.polymarket.com의 REST API, ws.perpetuals.polymarket.com의 WebSocket, 그리고 공식 SDK(TypeScript용 @polymarket/client, Python용 polymarket-client)가 있습니다. 폴리마켓의 문서는 트레이딩 봇과 마켓 메이킹 시스템을 지원 용도로 명시합니다.
TypeScript SDK와 Python SDK 중 무엇을 써야 하나요?
어느 쪽이든 됩니다. 둘 다 보안 클라이언트 생성, Perps 세션 열기, 상품 조회, TP/SL을 붙인 주문, 이벤트 스트리밍이라는 같은 흐름을 지원합니다. TypeScript SDK는 서명에 viem을 쓰고, Python SDK는 비동기 방식입니다. 봇을 이미 작성 중인 언어를 고르세요.
봇에 메인 지갑의 개인 키가 필요한가요?
세션을 열 때만 필요합니다. 그 한 번의 서명으로 프록시 거래 키가 승인되고, 이후에는 프록시가 주문에 서명합니다. 세션 자격 증명은 기본적으로 1주일 후 만료됩니다. 자격 증명은 암호화해 보관하고, 대시보드에는 읽기 전용 키를 쓰세요.
Perps API로 손절은 어떻게 거나요?
진입 주문에 takeProfit과 stopLoss 트리거를 브래킷으로 붙이거나, 열린 포지션에 placePositionTpSl(Python에서는 place_position_tp_sl)을 호출하세요. 트리거는 마크 가격을 감시하다가 reduce-only 주문으로 청산합니다. 포지션 TP/SL은 포지션 전체를 시장가로 닫습니다.
폴리마켓 Perps의 최소 주문 규모는 얼마인가요?
주문당 명목 금액 $10이며, 최소 입금액은 10 pUSD입니다(2026년 11월 기준). 가격과 수량은 각 시장의 소수 자릿수와 유효 숫자 5자리 이내에 맞아야 하며, 그렇지 않으면 주문이 거부됩니다.
킬 스위치나 데드맨 스위치가 있나요?
네. 자동 취소는 봇이 재설정을 멈추면 모든 미체결 주문을 취소하고, cancelAllOrders는 필요할 때 주문을 일괄 취소합니다. 문서에는 자동 취소가 TP/SL 주문이 취소되는 경우 중 하나로 나와 있어, 봇이 멈추면 손절도 사라질 수 있습니다. 직접 만든 킬 스위치도 코드에 넣으세요.
미국이나 캐나다에서 폴리마켓 Perps 봇을 돌릴 수 있나요?
아니요. Perps 주문은 미국, 캐나다, 쿠바, 이란, 북한, 시리아, 크림, 도네츠크, 루한스크에서 할 수 없습니다. 그곳에 봇을 호스팅하지 말고, 본인에게 적용되는 차단을 우회하려고 VPS나 VPN을 쓰지 마세요.
폴리마켓 Perps 트레이딩 대회에서 봇을 써도 되나요?
봇으로 거래하는 것은 허용됩니다. 대회 규칙은 자전 거래, 자기 거래, 다중 계정, 주로 거래량을 만들기 위한 거래, 그리고 프로그램을 조작하려는 목적의 봇이나 스크립트를 금지합니다. 이를 어기면 실격될 수 있습니다.