arbintel / app.py
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
@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")