# arbitrage_bot.py # Scans all Polymarket binary markets for YES + NO < $1.00 opportunities. # Dry‑run mode only logs and sends Telegram alerts. # Also creates virtual positions for found opportunities (strategy = "arbitrage"). import os, time, json, requests from polymarket_client import get_client from datetime import datetime, timezone DRY_RUN = True MIN_PROFIT_USD = float(os.getenv("MIN_PROFIT_USD", "0.10")) SCAN_INTERVAL = 30 TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") def send_alert(msg: str): if not TELEGRAM_TOKEN or not TELEGRAM_CHAT_ID: return try: url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage" requests.post(url, json={"chat_id": TELEGRAM_CHAT_ID, "text": msg}, timeout=10) except Exception as e: print(f"[Arbitrage] Telegram alert failed: {e}", flush=True) def get_all_binary_markets(client): try: result = client.get_markets() if isinstance(result, dict): markets = result.get("data", []) else: markets = result return [m for m in markets if m.get("outcomes") == ["Yes", "No"]] except Exception as e: print(f"[Arbitrage] Failed to fetch markets: {e}", flush=True) return [] def check_market_arbitrage(client, market, max_bet): try: tokens = market.get("tokens", []) if len(tokens) != 2: return None yes_token = next((t for t in tokens if t.get("outcome") == "Yes"), None) no_token = next((t for t in tokens if t.get("outcome") == "No"), None) if not yes_token or not no_token: return None yes_book = client.get_order_book(yes_token["token_id"]) no_book = client.get_order_book(no_token["token_id"]) yes_asks = yes_book.get("asks", []) if isinstance(yes_book, dict) else [] no_asks = no_book.get("asks", []) if isinstance(no_book, dict) else [] if not yes_asks or not no_asks: return None yes_ask = float(yes_asks[0]["price"]) no_ask = float(no_asks[0]["price"]) total = yes_ask + no_ask if total < 1.0: profit_per_unit = round(1.0 - total, 4) profit_on_max = round(profit_per_unit * max_bet, 4) return { "market_slug": market.get("slug", market.get("id")), "yes_ask": yes_ask, "no_ask": no_ask, "total_cost": round(total, 4), "profit_per_unit": profit_per_unit, "profit_on_max_bet": profit_on_max, "token_id_yes": yes_token["token_id"], "token_id_no": no_token["token_id"], "max_bet_used": max_bet } except: pass return None def create_virtual_arbitrage_position(opp): try: from dry_run_simulator import create_position, is_simulate_active if not is_simulate_active(): return create_position( market=opp["market_slug"], action="arbitrage", confidence=1.0, entry_price=opp["total_cost"], signal_summary=f"Arbitrage: YES+NO={opp['total_cost']}", strategy="arbitrage" ) print(f"[Arbitrage] Virtual arbitrage position created for {opp['market_slug']}", flush=True) except Exception as e: print(f"[Arbitrage] Failed to create virtual position: {e}", flush=True) def run_arbitrage_scan(): client = get_client() print(f"[Arbitrage] Scanner started (DRY_RUN = {DRY_RUN})", flush=True) while True: try: markets = get_all_binary_markets(client) if not markets: time.sleep(SCAN_INTERVAL) continue for market in markets: opp = check_market_arbitrage(client, market, 1.0) if opp is None or opp["profit_on_max_bet"] < MIN_PROFIT_USD: continue print(f"[Arbitrage] OPPORTUNITY: {opp['market_slug']} | " f"YES ask ${opp['yes_ask']:.2f}, NO ask ${opp['no_ask']:.2f} | " f"Profit ${opp['profit_per_unit']:.2f} per $1 | " f"Max bet profit ${opp['profit_on_max_bet']:.2f}", flush=True) send_alert( f"🔍 Arbitrage found: {opp['market_slug']}\n" f"YES ask: ${opp['yes_ask']:.2f}, NO ask: ${opp['no_ask']:.2f}\n" f"Cost: ${opp['total_cost']:.2f} → Profit: ${opp['profit_per_unit']:.2f}/$1\n" f"Max trade profit: ${opp['profit_on_max_bet']:.2f}" ) create_virtual_arbitrage_position(opp) except Exception as e: print(f"[Arbitrage] Scan error: {e}", flush=True) time.sleep(SCAN_INTERVAL) if __name__ == "__main__": run_arbitrage_scan()