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 @st.cache_resource 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")