AJAY KASU
Switch Polymarket integration to Gamma API for better reliability and add price bounds filter
7087a23 | import streamlit as st | |
| st.set_page_config(page_title="ArbIntel Scanner", layout="wide", page_icon="📈") | |
| import asyncio | |
| import pandas as pd | |
| from datetime import datetime, timezone | |
| import os | |
| from supabase import create_client | |
| import aiohttp | |
| from difflib import SequenceMatcher | |
| from src.strategies.arbitrage import CrossPlatformArbitrage | |
| def init_supabase(): | |
| url = st.secrets.get("SUPABASE_URL") or os.environ.get("SUPABASE_URL") | |
| key = st.secrets.get("SUPABASE_KEY") or os.environ.get("SUPABASE_KEY") | |
| if url and key: | |
| return create_client(url, key) | |
| return None | |
| supabase = init_supabase() | |
| # We would import the live clients here, but for the dashboard | |
| # we can instantiate the scanner and populate it directly for testing. | |
| if "capital" not in st.session_state: | |
| st.session_state.capital = 10000.00 | |
| if "pnl" not in st.session_state: | |
| st.session_state.pnl = 0.00 | |
| if "positions" not in st.session_state: | |
| st.session_state.positions = [] | |
| if "trades" not in st.session_state: | |
| st.session_state.trades = [] | |
| if "opps" not in st.session_state: | |
| st.session_state.opps = None | |
| st.title("ArbIntel: Prediction Markets Alpha Engine") | |
| st.markdown("### Live Cross-Platform Arbitrage Scanner") | |
| st.write("Detecting price inefficiencies between Polymarket and Kalshi in real-time.") | |
| async def fetch_and_scan(): | |
| arb_engine = CrossPlatformArbitrage(min_profit_threshold=0.005) | |
| arb_engine.market_map = {} | |
| kalshi_key = st.secrets.get("KALSHI_API_KEY") or os.environ.get("KALSHI_API_KEY") | |
| poly_markets = {} | |
| kalshi_markets = {} | |
| # REAL Polymarket Gamma API (more reliable) | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get( | |
| "https://gamma-api.polymarket.com/markets", | |
| params={"active": "true", "closed": "false", "limit": 100}, | |
| timeout=aiohttp.ClientTimeout(total=10) | |
| ) as resp: | |
| data = await resp.json() | |
| for m in data: | |
| if m.get("outcomePrices") and len(m["outcomePrices"]) > 0: | |
| try: | |
| yes_price = float(m["outcomePrices"][0]) | |
| if 0.02 < yes_price < 0.98: | |
| poly_markets[m["conditionId"]] = { | |
| "name": m.get("question", ""), | |
| "bid": yes_price - 0.01, | |
| "ask": yes_price + 0.01, | |
| "size": float(m.get("volume", 1000)) | |
| } | |
| except ValueError: | |
| pass | |
| except Exception as e: | |
| st.warning(f"Polymarket API: {e}") | |
| # REAL Kalshi REST API (with key) | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get( | |
| "https://api.elections.kalshi.com/trade-api/v2/markets", | |
| params={"status": "open", "limit": 100}, | |
| timeout=aiohttp.ClientTimeout(total=10) | |
| ) as resp: | |
| data = await resp.json() | |
| for m in data.get("markets", []): | |
| yes_bid = m.get("yes_bid", 0) / 100 | |
| yes_ask = m.get("yes_ask", 0) / 100 | |
| if yes_bid > 0: | |
| kalshi_markets[m["ticker"]] = { | |
| "name": m.get("title", ""), | |
| "bid": yes_bid, | |
| "ask": yes_ask, | |
| "size": float(m.get("volume", 500)) | |
| } | |
| except Exception as e: | |
| st.warning(f"Kalshi API: {e}") | |
| # CROSS-MATCH by name similarity | |
| FEE = 0.02 | |
| for pm_id, pm in poly_markets.items(): | |
| pm_words = set(pm["name"].lower().split()) | |
| for k_ticker, km in kalshi_markets.items(): | |
| km_words = set(km["name"].lower().split()) | |
| if not pm_words.intersection(km_words): | |
| continue | |
| score = SequenceMatcher( | |
| None, | |
| pm["name"].lower()[:80], | |
| km["name"].lower()[:80] | |
| ).ratio() | |
| if score > 0.40: | |
| arb_engine.market_map[pm_id] = pm["name"] | |
| arb_engine.update_state( | |
| "polymarket", pm_id, | |
| pm["bid"], pm["size"], pm["ask"], pm["size"] | |
| ) | |
| arb_engine.update_state( | |
| "kalshi", k_ticker, | |
| km["bid"], km["size"], km["ask"], km["size"] | |
| ) | |
| opportunities = arb_engine.scan_opportunities() | |
| filtered_opps = [] | |
| for opp in opportunities: | |
| if opp.expected_profit_margin > (FEE + 0.005): | |
| opp.event_name = arb_engine.market_map.get( | |
| opp.market_id_pm, | |
| f"{opp.market_id_pm}<->{opp.market_id_kalshi}" | |
| ) | |
| filtered_opps.append(opp) | |
| await asyncio.sleep(0.5) | |
| return filtered_opps | |
| if st.button("Run Live Arbitrage Scan", type="primary"): | |
| with st.spinner("Fetching order books from Polymarket and Kalshi APIs..."): | |
| st.session_state.opps = asyncio.run(fetch_and_scan()) | |
| if st.session_state.opps is not None: | |
| if len(st.session_state.opps) > 0: | |
| st.success(f"Detected {len(st.session_state.opps)} Arbitrage Opportunities!") | |
| data = [] | |
| for o in st.session_state.opps: | |
| data.append({ | |
| "Time (UTC)": o.timestamp.strftime("%H:%M:%S"), | |
| "Event Mapped": o.event_name, | |
| "Buy On": o.buy_platform.title(), | |
| "Buy Price": f"${o.buy_price:.3f}", | |
| "Max Size": f"${o.buy_size:.2f}", | |
| "Sell On": o.sell_platform.title(), | |
| "Sell Price": f"${o.sell_price:.3f}", | |
| "Net Edge": f"{o.expected_profit_margin*100:.2f}%" | |
| }) | |
| st.dataframe(pd.DataFrame(data), use_container_width=True) | |
| st.markdown("#### Execute Paper Trade") | |
| if st.button("Auto-Execute All"): | |
| for o in st.session_state.opps: | |
| profit = o.expected_profit_margin * o.buy_size | |
| st.session_state.pnl += profit | |
| st.session_state.capital += profit | |
| if o.market_id_pm not in st.session_state.positions: | |
| st.session_state.positions.append(o.market_id_pm) | |
| st.session_state.trades.append(o) | |
| if supabase: | |
| try: | |
| supabase.table("trades").insert({ | |
| "event_id": o.event_name, | |
| "buy_platform": o.buy_platform, | |
| "sell_platform": o.sell_platform, | |
| "buy_price": o.buy_price, | |
| "sell_price": o.sell_price, | |
| "size": o.buy_size, | |
| "net_edge": o.expected_profit_margin, | |
| "pnl": profit | |
| }).execute() | |
| except Exception as e: | |
| st.error(f"Failed to write trade to Supabase: {e}") | |
| if supabase: | |
| try: | |
| supabase.table("portfolio").insert({ | |
| "capital": st.session_state.capital, | |
| "pnl": st.session_state.pnl, | |
| "active_positions": len(st.session_state.positions), | |
| "market_regime": "Stable" | |
| }).execute() | |
| except Exception as e: | |
| st.error(f"Failed to write portfolio snapshot to Supabase: {e}") | |
| st.session_state.opps = None | |
| st.rerun() | |
| else: | |
| st.info("No active opportunities detected above risk-free threshold (markets efficient).") | |
| st.markdown("---") | |
| st.markdown("### Portfolio & Risk Metrics") | |
| col1, col2, col3, col4 = st.columns(4) | |
| pnl_sign = "+" if st.session_state.pnl >= 0 else "-" | |
| col1.metric("Available Capital", f"${st.session_state.capital:,.2f}", f"{pnl_sign}${abs(st.session_state.pnl):,.2f}") | |
| col2.metric("Active Positions", str(len(st.session_state.positions))) | |
| col3.metric("24h PnL", f"${st.session_state.pnl:,.2f}") | |
| col4.metric("Market Regime", "Stable", "-Vol") | |