File size: 8,417 Bytes
77fd2f6
e48894b
 
77fd2f6
 
 
502f466
 
 
b02119b
 
77fd2f6
 
502f466
 
 
 
 
 
 
 
 
 
77fd2f6
 
 
30c7379
 
 
 
 
 
 
 
19c8e7e
 
30c7379
77fd2f6
 
 
 
 
b02119b
 
77fd2f6
b02119b
5312195
b02119b
 
 
7087a23
b02119b
 
 
7087a23
 
b02119b
 
 
7087a23
 
 
 
 
 
 
 
 
 
 
 
 
b02119b
 
 
 
 
0fd95c5
b02119b
a6c0850
b02119b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5312195
b02119b
0fd95c5
b02119b
0fd95c5
 
 
 
b02119b
 
 
 
 
5312195
b02119b
 
 
 
 
 
 
 
 
 
5312195
77fd2f6
0fd95c5
5312195
0fd95c5
 
 
 
 
 
b02119b
 
0fd95c5
77fd2f6
 
 
19c8e7e
 
 
 
 
77fd2f6
19c8e7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502f466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77fd2f6
19c8e7e
 
 
 
77fd2f6
 
 
 
30c7379
 
 
 
77fd2f6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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")