Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- """
- Polymarket BTC Up/Down 5-minute trading bot.
- Dependencias: pip install py-clob-client websocket-client requests eth-account
- """
- import json
- import sys
- import time
- import threading
- from datetime import datetime
- from math import floor
- import requests
- import websocket
- from py_clob_client.client import ClobClient
- from py_clob_client.clob_types import OrderArgs, OrderType
- from py_clob_client.order_builder.constants import BUY
- # ─── Configuración ────────────────────────────────────────────────────────────
- TRIGGER_PRICE = 0.90
- BUY_AMOUNT = 5.0
- PRIVATE_KEY = "TU_PRIVATE_KEY_AQUI"
- PROXY_WALLET = "TU_PROXY_WALLET_AQUI"
- CHAIN_ID = 137
- SIGNATURE_TYPE = 2
- HOST = "https://clob.polymarket.com"
- GAMMA_API = "https://gamma-api.polymarket.com"
- WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
- # ─── Utilidades ───────────────────────────────────────────────────────────────
- def ts() -> str:
- return datetime.now().strftime("%H:%M:%S")
- def log(icon: str, msg: str) -> None:
- print(f"[{ts()}] {icon} {msg}")
- def current_window_ts() -> int:
- return (int(time.time()) // 300) * 300
- def slug_for(window_ts: int) -> str:
- return f"btc-updown-5m-{window_ts}"
- def window_end(window_ts: int) -> float:
- return float(window_ts + 300)
- # ─── Carga de mercado ─────────────────────────────────────────────────────────
- def load_market(slug: str) -> dict | None:
- """
- Devuelve {"up": token_id, "down": token_id} o None si falla.
- """
- url = f"{GAMMA_API}/events?slug={slug}"
- try:
- resp = requests.get(url, timeout=10)
- resp.raise_for_status()
- data = resp.json()
- if not data:
- return None
- market = data[0]["markets"][0]
- # Intento principal: campo "tokens"
- tokens = market.get("tokens")
- if tokens:
- result = {}
- for t in tokens:
- outcome = t.get("outcome", "").upper()
- token_id = t.get("token_id") or t.get("tokenId")
- if outcome in ("UP", "DOWN") and token_id:
- result[outcome.lower()] = token_id
- if "up" in result and "down" in result:
- return result
- # Fallback: clobTokenIds + outcomes
- clob_ids = market.get("clobTokenIds", [])
- outcomes = market.get("outcomes", [])
- if isinstance(clob_ids, str):
- clob_ids = json.loads(clob_ids)
- if isinstance(outcomes, str):
- outcomes = json.loads(outcomes)
- if clob_ids and outcomes and len(clob_ids) == len(outcomes):
- result = {}
- for oc, tid in zip(outcomes, clob_ids):
- oc_up = oc.upper()
- if oc_up in ("UP", "DOWN"):
- result[oc_up.lower()] = tid
- if "up" in result and "down" in result:
- return result
- log("⚠", f"No se pudieron extraer token IDs del mercado {slug}")
- return None
- except Exception as exc:
- log("✗", f"Error cargando mercado {slug}: {exc}")
- return None
- # ─── Price Feed (WebSocket) ───────────────────────────────────────────────────
- class PriceFeed:
- """
- Mantiene los mid-prices de UP y DOWN en tiempo real.
- Corre en un hilo daemon con reconexión automática.
- """
- def __init__(self, up_id: str, down_id: str) -> None:
- self.up_id = up_id
- self.down_id = down_id
- self.prices: dict[str, float | None] = {"up": None, "down": None}
- self._id_to_side = {up_id: "up", down_id: "down"}
- self._stop = threading.Event()
- self._ws = None
- self._thread = threading.Thread(target=self._run, daemon=True)
- def start(self) -> None:
- self._thread.start()
- def stop(self) -> None:
- self._stop.set()
- if self._ws:
- try:
- self._ws.close()
- except Exception:
- pass
- def _mid(self, best_bid, best_ask) -> float | None:
- try:
- b = float(best_bid) if best_bid else None
- a = float(best_ask) if best_ask else None
- if b is not None and a is not None:
- return (b + a) / 2
- return b or a
- except (TypeError, ValueError):
- return None
- def _process_message(self, raw: str) -> None:
- try:
- events = json.loads(raw)
- if not isinstance(events, list):
- events = [events]
- for event in events:
- etype = event.get("event_type") or event.get("type", "")
- if etype == "book":
- asset_id = event.get("asset_id", "")
- side = self._id_to_side.get(asset_id)
- if side:
- bids = event.get("bids", [])
- asks = event.get("asks", [])
- best_bid = bids[0]["price"] if bids else None
- best_ask = asks[0]["price"] if asks else None
- mid = self._mid(best_bid, best_ask)
- if mid is not None:
- self.prices[side] = mid
- elif etype == "price_change":
- for change in event.get("price_changes", []):
- asset_id = change.get("asset_id", "")
- side = self._id_to_side.get(asset_id)
- if side:
- mid = self._mid(
- change.get("best_bid"),
- change.get("best_ask"),
- )
- if mid is not None:
- self.prices[side] = mid
- elif etype == "best_bid_ask":
- asset_id = event.get("asset_id", "")
- side = self._id_to_side.get(asset_id)
- if side:
- mid = self._mid(
- event.get("best_bid"),
- event.get("best_ask"),
- )
- if mid is not None:
- self.prices[side] = mid
- except Exception as exc:
- log("⚠", f"Error procesando mensaje WS: {exc}")
- def _run(self) -> None:
- subscribe_msg = json.dumps({
- "assets_ids": [self.up_id, self.down_id],
- "type": "market",
- "custom_feature_enabled": True,
- })
- while not self._stop.is_set():
- try:
- ws = websocket.WebSocketApp(
- WS_URL,
- on_open=lambda ws: ws.send(subscribe_msg),
- on_message=lambda ws, msg: self._process_message(msg),
- on_error=lambda ws, err: log("⚠", f"WS error: {err}"),
- on_close=lambda ws, code, msg: None,
- )
- self._ws = ws
- ws.run_forever(ping_interval=20, ping_timeout=10)
- except Exception as exc:
- log("⚠", f"WS caído: {exc}")
- if not self._stop.is_set():
- time.sleep(3)
- # ─── Cliente CLOB ─────────────────────────────────────────────────────────────
- def build_client() -> ClobClient:
- client = ClobClient(
- HOST,
- key=PRIVATE_KEY,
- chain_id=CHAIN_ID,
- signature_type=SIGNATURE_TYPE,
- funder=PROXY_WALLET,
- )
- client.set_api_creds(client.create_or_derive_api_creds())
- return client
- # ─── Orden de compra ──────────────────────────────────────────────────────────
- def place_buy(client: ClobClient, token_id: str, side_label: str) -> bool:
- shares = floor((BUY_AMOUNT / TRIGGER_PRICE) * 100) / 100
- log("🛒", f"Comprando {shares} shares de {side_label} @ {TRIGGER_PRICE}")
- try:
- order = client.create_order(
- OrderArgs(
- price=TRIGGER_PRICE,
- size=shares,
- side=BUY,
- token_id=token_id,
- )
- )
- resp = client.post_order(order, OrderType.GTC)
- log("✅", f"Orden enviada: {resp}")
- return True
- except Exception as exc:
- log("✗", f"Error al colocar orden: {exc}")
- return False
- # ─── Ciclo de ventana ─────────────────────────────────────────────────────────
- def run_window(client: ClobClient, window_ts: int) -> int:
- """
- Ejecuta una ventana de 5 minutos.
- Devuelve el timestamp de la siguiente ventana.
- """
- slug = slug_for(window_ts)
- end = window_end(window_ts)
- log("🕐", f"Ventana activa: {slug} (cierre en {int(end - time.time())}s)")
- # ── Cargar mercado con reintentos ────────────────────────────────────────
- tokens = None
- while tokens is None:
- if time.time() >= end:
- log("⏩", "Ventana expiró antes de cargar el mercado, avanzando")
- return window_ts + 300
- # Si la ventana cambió mientras esperábamos, actualizar
- new_ts = current_window_ts()
- if new_ts != window_ts:
- log("🔄", "Ventana cambió durante la carga, actualizando slug")
- window_ts = new_ts
- slug = slug_for(window_ts)
- end = window_end(window_ts)
- tokens = load_market(slug)
- if tokens is None:
- log("⏳", f"Mercado no disponible, reintentando en 3s…")
- time.sleep(3)
- log("📋", f"UP token: {tokens['up']}")
- log("📋", f"DOWN token: {tokens['down']}")
- # ── Iniciar price feed ───────────────────────────────────────────────────
- feed = PriceFeed(tokens["up"], tokens["down"])
- feed.start()
- # ── Esperar primeros precios (máx 5s) ────────────────────────────────────
- deadline = time.time() + 5
- while time.time() < deadline:
- if feed.prices["up"] is not None and feed.prices["down"] is not None:
- break
- time.sleep(0.1)
- else:
- log("⚠", "No se recibieron precios en 5s, continuando de todos modos")
- # ── Monitorización ───────────────────────────────────────────────────────
- bought = False
- log("👀", "Monitorizando precios…")
- while not bought and time.time() < end:
- p_up = feed.prices["up"]
- p_down = feed.prices["down"]
- up_str = f"{p_up:.4f}" if p_up is not None else "----"
- down_str = f"{p_down:.4f}" if p_down is not None else "----"
- remaining = max(0, int(end - time.time()))
- print(
- f"\r[{ts()}] 📊 UP {up_str} DOWN {down_str} "
- f"⏱ {remaining:>3}s restantes ",
- end="",
- flush=True,
- )
- # Comprobar trigger
- for side, price, token_id in (
- ("UP", p_up, tokens["up"]),
- ("DOWN", p_down, tokens["down"]),
- ):
- if price is not None and price >= TRIGGER_PRICE:
- print() # nueva línea tras el \r
- log("🎯", f"¡TRIGGER! {side} @ {price:.4f} >= {TRIGGER_PRICE}")
- if place_buy(client, token_id, side):
- bought = True
- break
- if not bought:
- time.sleep(0.05)
- print() # nueva línea final
- if not bought:
- log("⌛", "Ventana cerrada sin trigger")
- feed.stop()
- return window_ts + 300
- # ─── Main ─────────────────────────────────────────────────────────────────────
- def main() -> None:
- log("🚀", "Iniciando bot Polymarket BTC Up/Down 5m")
- log("⚙", f"Trigger: {TRIGGER_PRICE} | Importe: ${BUY_AMOUNT}")
- try:
- client = build_client()
- log("🔑", "Cliente CLOB autenticado correctamente")
- except Exception as exc:
- log("✗", f"Error al autenticar cliente CLOB: {exc}")
- sys.exit(1)
- next_ts = current_window_ts()
- while True:
- try:
- next_ts = run_window(client, next_ts)
- # Pequeña espera para asegurarse de que la ventana ya está activa
- now = time.time()
- if next_ts > now:
- wait = next_ts - now
- log("💤", f"Esperando {wait:.1f}s al inicio de la próxima ventana…")
- time.sleep(wait)
- except KeyboardInterrupt:
- log("🛑", "Bot detenido por el usuario")
- sys.exit(0)
- except Exception as exc:
- log("✗", f"Error inesperado: {exc}")
- time.sleep(5)
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment