Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions tools/synth-overlay/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,66 @@
# Synth Overlay — Polymarket Edge & Position Sizing Extension
# Synth Overlay — Polymarket & Kalshi Edge & Position Sizing Extension

Chrome extension that uses Chrome's **native Side Panel** to show Synth market context on Polymarket and convert that edge into a **concrete position size**. The panel is data-first: Synth Up/Down prices, edge, confidence, signal explanation, invalidation conditions, and a **Kelly-based position sizing calculator**.
Chrome extension that uses Chrome's **native Side Panel** to show Synth market context on **Polymarket** and **Kalshi**, and convert that edge into a **concrete position size**. The panel is data-first: Synth Up/Down prices, edge, confidence, signal explanation, invalidation conditions, and a **Kelly-based position sizing calculator**.

## What it does

- **Native Side Panel**: Uses Chrome Side Panel API (`chrome.sidePanel`) instead of an in-page floating overlay.
- **Data-focused edge UI**: Shows Synth Up/Down prices, YES edge, confidence, explanation, and what would invalidate the signal.
- **Balance-aware position sizing**: Reads (or lets the user set) wallet/account balance and recommends a **Kelly-optimal position size** based on Synth vs Polymarket probabilities and forecast confidence.
- **Multi-platform**: Works on both **Polymarket** (polymarket.com) and **Kalshi** (kalshi.com) — the two largest prediction markets. The side panel dynamically adapts column labels and scraping strategy per platform.
- **Balance-aware position sizing**: Reads (or lets the user set) wallet/account balance and recommends a **Kelly-optimal position size** based on Synth vs market probabilities and forecast confidence.
- **Synth-sourced prices only**: Displays prices from the Synth API to avoid sync issues with DOM-scraped market data.
- **Manual + auto refresh**: Refresh button in panel plus automatic 15s refresh. "Data as of" timestamp shows when the Synth data was generated.
- **Clear confidence colors**: red (<40%), amber (40–70%), green (≥70%).
- **Contextual only**: Enabled on Polymarket pages; panel shows guidance when page/slug is unsupported.
- **Contextual only**: Enabled on Polymarket and Kalshi pages; panel shows guidance when page/slug is unsupported.

## How it works

1. **Content script** (on `polymarket.com`) reads the market slug from the page URL and scrapes **live prices** and **balance** from the DOM.
2. **Side panel page** requests context from the content script and fetches Synth edge data from local API (`GET /api/edge?slug=...`).
3. **Panel rendering** displays Synth forecast data (prices, edge, signal, confidence, analysis, invalidation) and updates every 15s or on manual refresh.
4. **Position Sizing card** combines Synth probabilities, Polymarket-implied odds, forecast confidence, and user balance into a Kelly-based recommendation.
5. **Background service worker** enables/disables side panel per-tab based on URL and runs the alert polling engine.
6. **Edge alerts** poll watched markets every 60s via `chrome.alarms`. When edge exceeds the user's threshold, a browser notification fires with asset, edge size, signal direction, and confidence. Clicking the notification focuses or opens the relevant Polymarket page. Notifications are suppressed when the user is already viewing the market and have a 5-minute cooldown per market to avoid spam.
1. **Content script** (on `polymarket.com` and `kalshi.com`) reads the market slug from the page URL and scrapes **live prices** and **balance** from the DOM. Platform detection is automatic. The content script guards all `chrome.runtime` calls against extension context invalidation (e.g. after extension reload) — when the context becomes invalid, the observer, polling interval, and message listeners stop gracefully.
- **Kalshi multi-strike scraping**: On multi-strike pages (e.g. daily above/below with multiple price thresholds), the DOM contains prices for every strike in a list. The scraper uses a **two-pass approach**: Pass 1 scans only elements inside the **trading panel** (detected by walking up ancestors for a container with "Buy"/"Sell"/"Amount" text — the order form for the selected contract). Pass 2 falls back to general DOM scanning if no trading panel is found (e.g. single-strike pages). This ensures the displayed price always matches the user's **selected** contract, not the first strike in the list.
- A `MutationObserver` detects DOM changes (e.g. user clicking a different strike) and broadcasts updated prices to the side panel via `chrome.runtime.sendMessage`. The side panel recalculates edge client-side using `updateWithLivePrice()` without re-fetching from the server.
2. **Side panel page** requests context from the content script and fetches Synth edge data from local API (`GET /api/edge?slug=...&platform=...`).
3. **Panel rendering** displays Synth forecast data (prices, edge, signal, confidence, analysis, invalidation) and updates every 15s or on manual refresh. Column headers adapt to show "Poly" or "Kalshi" based on the active platform.
4. **Position Sizing card** combines Synth probabilities, market-implied odds, forecast confidence, and user balance into a Kelly-based recommendation.
5. **Background service worker** enables/disables side panel per-tab based on URL (both platforms) and runs the alert polling engine.
6. **Edge alerts** poll watched markets every 60s via `chrome.alarms`. When edge exceeds the user's threshold, a browser notification fires with asset, edge size, signal direction, and confidence. Clicking the notification focuses or opens the relevant market page on the correct platform. Notifications are suppressed when the user is already viewing the market and have a 5-minute cooldown per market to avoid spam.

## Platform support

| Platform | Domain | Market types | Assets |
|----------|--------|-------------|--------|
| **Polymarket** | polymarket.com | daily, hourly, 15min, 5min, range | BTC, ETH, SOL, XRP |
| **Kalshi** | kalshi.com | daily, 15min, range | BTC, ETH, SOL, SPY, NVDA, TSLA, AAPL, GOOGL, XAU |

### Kalshi market matching

Kalshi uses structured ticker names instead of descriptive slugs:
- `KXBTCD-26MAR1317` → BTC daily above/below (event ticker)
- `KXBTCD-26MAR1317-T70499.99` → BTC daily above/below at $70,499.99 strike (contract ticker, `-T` = threshold)
- `KXBTC-26MAR1317-B76750` → BTC range bracket at $76,750 (contract ticker, `-B` = bracket)
- `KXBTC15M-26MAR121930-30` → BTC 15-minute market
- `KXETHD-26MAR1317` → ETH daily above/below
- `KXSOLD-26MAR1317` → SOL daily above/below

Series tickers (used for browsing): `kxbtcd`, `kxbtc`, `kxbtc15m`, `kxethd`, `kxeth`, `kxsold`, `kxsol`, etc.

Legacy tickers without `KX` prefix also exist: `BTCD`, `BTC`, `ETH`, `ETHD`. To avoid collisions with Polymarket slugs (e.g. `btc-updown-5m-...`), legacy tickers are only matched when followed by a Kalshi-style date suffix (digits + letters, e.g. `-26MAR1317`).

The matcher extracts the asset from the series prefix (e.g. `KXBTCD` → BTC) and infers market type:
- Series ending in `D` (KXBTCD, KXETHD, KXSOLD) → daily above/below
- Series without `D` (KXBTC, KXETH, KXSOL) → range
- Series ending in `15M` (KXBTC15M, KXETH15M) → 15-minute

Kalshi URLs can have multiple path segments (e.g. `/markets/kxsol15m/solana-15-minutes/kxsol15m-26mar121945`); both `normalize_slug` and the content script extract the **last segment** as the ticker.

### Kalshi price scraping

The content script (`content.js`) uses a multi-strategy approach to scrape live Yes/No prices from Kalshi's DOM:

1. **`extractPrice(text, side)`** — helper that matches cent format (`Yes 80¢`, `Buy Yes 80¢`) and dollar format (`Yes $0.80`, `Buy Yes $0.80`).
2. **`isInTradingPanel(el)`** — walks up the DOM tree (up to 8 ancestors) looking for a container whose text includes "Buy", "Sell", and "Amount" (the order form). Also matches "sign up to trade" for logged-out users.
3. **Pass 1 (trading panel priority)** — scans `button, a, span, div, p, [role='button'], [role='cell'], td` elements, but only accepts prices from elements inside the trading panel. This ensures the selected contract's price is used on multi-strike pages.
4. **Pass 2 (fallback)** — if Pass 1 finds no valid pair, rescans all elements without the trading panel constraint. Includes standalone price leaf-walk (parent context) and order book pattern matching.
5. **Inference** — if only one side (Yes or No) is found, the other is inferred as `1 - found`.

## Synth API usage

Expand Down Expand Up @@ -50,7 +91,7 @@ If no balance can be detected or stored, the user can still enter it by hand and
For a given up/down market, the side panel uses:

- `p_synth` — Synth probability of **YES** (`synth_probability_up` from `/api/edge`).
- `p_market` — Polymarket-implied probability of **YES** (from Synth server or live DOM price).
- `p_market` — Market-implied probability of **YES** (from Synth server or live DOM price on Polymarket/Kalshi).
- `confidence_score` — forecast confidence from `EdgeAnalyzer` in \[0, 1].
- `balance` — user bankroll in USD/USDC (scraped or user-entered).

Expand Down Expand Up @@ -115,20 +156,28 @@ The UI shows:
1. Install: `pip install -r requirements.txt` (from repo root: `pip install -r tools/synth-overlay/requirements.txt`).
2. Start server (from repo root): `python tools/synth-overlay/server.py` (or from `tools/synth-overlay`: `python server.py`). Listens on `127.0.0.1:8765`.
3. Load extension: Chrome → Extensions → Load unpacked → select `tools/synth-overlay/extension`.
4. Click the extension icon to open **Chrome Side Panel** (or pin and open from Side Panel UI). On Polymarket pages, the panel auto-enables.
4. Click the extension icon to open **Chrome Side Panel** (or pin and open from Side Panel UI). On Polymarket and Kalshi pages, the panel auto-enables.

## Verify the side panel (before recording)

1. **Check the API** (server must be running):
```bash
# Polymarket slug
curl -s "http://127.0.0.1:8765/api/edge?slug=bitcoin-up-or-down-on-february-26" | head -c 200
# Kalshi ticker
curl -s "http://127.0.0.1:8765/api/edge?slug=KXBTCD-26MAR1317&platform=kalshi" | head -c 200
```
You should see JSON with `"signal"`, `"edge_pct"`, etc. If you see `"error"` or 404, the slug is not supported for the current mock/API.
You should see JSON with `"signal"`, `"edge_pct"`, `"platform"`, etc. If you see `"error"` or 404, the slug is not supported for the current mock/API.

2. **Open the exact URL** in Chrome (with the extension loaded from `extension/`):
- Daily (BTC): `https://polymarket.com/event/bitcoin-up-or-down-on-february-26`
- Hourly (ETH): `https://polymarket.com/event/ethereum-up-or-down-february-25-6pm-et`
- 15-Min (SOL): `https://polymarket.com/event/sol-updown-15m-1772204400`
- **Polymarket:**
- Daily (BTC): `https://polymarket.com/event/bitcoin-up-or-down-on-february-26`
- Hourly (ETH): `https://polymarket.com/event/ethereum-up-or-down-february-25-6pm-et`
- 15-Min (SOL): `https://polymarket.com/event/sol-updown-15m-1772204400`
- **Kalshi:**
- Daily (BTC): `https://kalshi.com/markets/kxbtcd`
- Daily (ETH): `https://kalshi.com/markets/kxethd`
- 15-Min (BTC): `https://kalshi.com/markets/kxbtc15m`
- The side panel requests the slug from the page and fetches Synth data from the local API. If API returns 200, panel fields populate.

3. **Interaction:**
Expand Down
19 changes: 14 additions & 5 deletions tools/synth-overlay/extension/background.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var SUPPORTED_ORIGINS = [
"https://polymarket.com/"
"https://polymarket.com/",
"https://kalshi.com/"
];

var API_BASE = "http://127.0.0.1:8765";
Expand All @@ -20,6 +21,7 @@ var STORE_KEYS = {
var COOLDOWN_MS = 5 * 60 * 1000;

function isSupportedUrl(url) {
if (!url) return false;
for (var i = 0; i < SUPPORTED_ORIGINS.length; i++) {
if (url.indexOf(SUPPORTED_ORIGINS[i]) === 0) return true;
}
Expand Down Expand Up @@ -170,7 +172,9 @@ function suppressAndNotify(item, data, cooldowns) {

chrome.tabs.query({ active: true, lastFocusedWindow: true }, function (tabs) {
var activeUrl = (tabs && tabs[0] && tabs[0].url) || "";
if (activeUrl.indexOf("polymarket.com") !== -1 && activeUrl.indexOf(item.slug) !== -1) {
var onMarketPage = (activeUrl.indexOf("polymarket.com") !== -1 || activeUrl.indexOf("kalshi.com") !== -1)
&& activeUrl.indexOf(item.slug) !== -1;
if (onMarketPage) {
return;
}
cooldowns[item.slug] = Date.now();
Expand Down Expand Up @@ -242,15 +246,20 @@ function createEdgeNotification(notifId, item, data) {
});
}

// Focus or open the Polymarket page for the clicked notification
// Focus or open the market page for the clicked notification
chrome.notifications.onClicked.addListener(function (notifId) {
if (notifId.indexOf("synth-edge::") !== 0) return;
var slug = notifId.replace("synth-edge::", "");
if (!slug) { chrome.notifications.clear(notifId); return; }

var targetUrl = "https://polymarket.com/event/" + slug;
// Determine platform from slug format
var isKalshi = slug.toLowerCase().indexOf("kx") === 0;
var targetUrl = isKalshi
? "https://kalshi.com/markets/" + slug
: "https://polymarket.com/event/" + slug;
var searchPattern = isKalshi ? "https://kalshi.com/*" : "https://polymarket.com/*";

chrome.tabs.query({ url: "https://polymarket.com/*" }, function (tabs) {
chrome.tabs.query({ url: searchPattern }, function (tabs) {
var match = null;
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].url && tabs[i].url.indexOf(slug) !== -1) {
Expand Down
Loading