diff --git a/skills/otto-mispricing-assistant/.claude-plugin/plugin.json b/skills/otto-mispricing-assistant/.claude-plugin/plugin.json new file mode 100644 index 000000000..d162ca156 --- /dev/null +++ b/skills/otto-mispricing-assistant/.claude-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "otto-mispricing-assistant", + "description": "Otto Mispricing Assistant \u2014 Scan near-resolution Polymarket markets where implied probability diverges from Otto's news + KOL + funding signals, then trade a single market only after explicit user confirmation. Dry-run by default.", + "version": "0.1.3", + "author": { + "name": "Otto AI" + }, + "homepage": "https://useotto.xyz", + "repository": "https://github.com/useOttoAI/plugin-store", + "license": "MIT", + "keywords": [ + "prediction-markets", + "polymarket", + "polygon", + "onchainos", + "trading-strategy", + "mispricing", + "news-signals" + ] +} diff --git a/skills/otto-mispricing-assistant/LICENSE b/skills/otto-mispricing-assistant/LICENSE new file mode 100644 index 000000000..0208813a9 --- /dev/null +++ b/skills/otto-mispricing-assistant/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Otto AI (@useOttoAI) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/otto-mispricing-assistant/README.md b/skills/otto-mispricing-assistant/README.md new file mode 100644 index 000000000..39aab9298 --- /dev/null +++ b/skills/otto-mispricing-assistant/README.md @@ -0,0 +1,57 @@ +# Otto Mispricing Assistant + +Scan near-resolution Polymarket markets where the implied probability diverges from Otto AI's live news, KOL-sentiment, and funding-rate signals — surface ranked candidates, trade one market at a time with explicit user confirmation. + +**This is a scanner, not a bot.** No batch execution. No autonomous trading. Every trade requires the user to type "confirm". + +## Files + +``` +otto-mispricing-assistant/ +├── .claude-plugin/ +│ └── plugin.json # Claude Skill registration metadata +├── plugin.yaml # Plugin Store manifest +├── SKILL.md # AI agent protocol (9-step reactive flow) +├── SUMMARY.md # User-facing summary +├── README.md # This file +├── LICENSE # MIT +└── scripts/ + ├── config.py # Hot-reloadable params + └── bot.py # Optional notify-only scanner (NEVER trades) +``` + +## How it works + +1. Pull Otto's live signals: news-flash (last 6h, severity ≥ 3), KOL sentiment (24h window), funding extremes (top 5). +2. List active Polymarket markets in crypto / macro / elections, filter by resolution window + liquidity + volume. +3. For each market, derive an Otto probability estimate from matched signals vs. the implied probability (YES-token mid). +4. Rank by `|edge| × signal_confidence × liquidity_score`. Keep top 5 above the 8pp edge threshold. +5. Present the ranked list. User picks one or "none". +6. Re-quote the chosen market right before confirmation — prices move fast near resolution. +7. User types "confirm". Skill places a single `polymarket-plugin buy` with `--strategy-id` attribution. +8. Report back. Remind the user that mispricings can persist or widen. + +## Budget enforcement + +- Per-trade cap: **$50 USDC.e** (hard, no user override). +- Per-session cap: **$200 USDC.e** cumulative. +- Any user request exceeding these is sized down with a warning, or refused if the session cap is spent. + +## Sibling Skills + +Shares the Otto AI signal feed with: + +- **otto-alpha-sniper** — Hyperliquid perp sniper (trending / kol-follow / funding-fade) +- **otto-kol-follow** — Hyperliquid KOL-consensus mirror +- **otto-macro-cross-venue** (Tier 2) — fires HL + Polymarket as matched-pair trades on macro flashes + +## Links + +- Otto AI: https://useotto.xyz +- Signal feed contract: [../SIGNAL_FEED_CONTRACT.md](../SIGNAL_FEED_CONTRACT.md) +- Polymarket Basic Skill: https://github.com/okx/plugin-store/tree/main/skills/polymarket-plugin +- Docs: https://docs.useotto.xyz + +## License + +MIT — see `LICENSE`. diff --git a/skills/otto-mispricing-assistant/SKILL.md b/skills/otto-mispricing-assistant/SKILL.md new file mode 100644 index 000000000..5243e3f0a --- /dev/null +++ b/skills/otto-mispricing-assistant/SKILL.md @@ -0,0 +1,336 @@ +--- +name: otto-mispricing-assistant +description: > + Otto Mispricing Assistant v0.1 — Scans near-resolution Polymarket markets where the implied + probability (mid-price of YES/NO outcome tokens) diverges from Otto AI's real-time news, + KOL-sentiment, and funding-rate signals. Presents ranked candidates to the user. Each trade + requires explicit user confirmation — no batch execution, no autonomous trading. + Trigger when the user mentions Polymarket mispricing, prediction-market edge, late-stage + Polymarket, find undervalued prediction markets, find mispriced prediction markets, + Polymarket news edge, Polymarket odds vs Otto, or wants to find Polymarket markets where + odds don't match the news. +version: "0.1.3" +author: "Otto AI" +updated: 2026-04-24 +tags: + - prediction-markets + - polymarket + - polygon + - onchainos + - trading-strategy + - mispricing + - news-signals +--- + +# Otto Mispricing Assistant — Skill Protocol + +> Real on-chain trading of prediction-market outcome shares. Use paper mode (`DRY_RUN = True`) until you understand the strategy. Capital loss is possible — a "mispriced" market can still resolve against you. + +--- + +## Overview + +Otto Mispricing Assistant is a **scanner, not a bot.** It cross-references active Polymarket markets against Otto AI's live news-flash, KOL-sentiment, and funding-extreme feeds, computes a divergence score between implied probability and Otto's estimate, and presents a ranked list of candidate markets to the user. **Every single trade requires an explicit user confirmation.** There is no batch mode, no autonomous execution, and no "fire-and-forget" flow. + +- **Primary use case.** User asks "find me a Polymarket market where the odds don't match the news" or similar. +- **Decision rule.** Rank by `|otto_estimate - implied_prob|` × `signal_confidence` × `liquidity_score`. Only surface markets above `MIN_EDGE_PCT`. +- **Execution.** All Polymarket actions flow through the Polymarket Basic Skill (`polymarket-plugin`). No raw CLOB interaction. +- **Fund sizing.** Capped per-trade by `MAX_TRADE_SIZE_USD` and per-session by `MAX_SESSION_BUDGET_USD`. +- **Never auto-trades.** Each candidate is presented; the user picks one and confirms. + +--- + +## Pre-flight Checks + +### 1. Install onchainos CLI (≥ 2.0.0-beta) + +```bash +onchainos --version 2>/dev/null || curl -fsSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | sh +npx skills add okx/onchainos-skills --yes --global +npx skills add okx/plugin-store --skill plugin-store --yes --global +``` + +### 2. Install the Polymarket Basic Skill (required) + +```bash +npx skills add okx/plugin-store --skill polymarket-plugin --yes --global +``` + +### 3. Set up Polymarket trading (proxy or EOA) + +```bash +polymarket-plugin quickstart # tells you what to do +polymarket-plugin check-access # verify your region is not restricted +polymarket-plugin setup-proxy # one-time, recommended (gasless mode) +polymarket-plugin deposit --amount 50 # fund the proxy wallet with USDC.e +``` + +### 4. Verify the Otto signal feed is reachable + +```bash +curl -fsS "https://signals.useotto.xyz/v1/news-flash?since_minutes=60" | jq .updated_at +``` + +If this fails, abort and report "Otto signal feed unreachable" — the Skill does not fabricate signals. + +--- + +## Commands + +When the user fires a Mispricing Assistant intent, execute this protocol in order. + +### Step 1 — Readiness check + +```bash +polymarket-plugin quickstart +``` + +- **When to use**: always, first thing. +- **Output**: JSON status (`ready` / `needs_setup` / `needs_deposit` / `region_blocked`). +- **If not ready**: run `polymarket-plugin setup-proxy` or `polymarket-plugin deposit` as indicated. If `region_blocked`, abort with a clear message. + +### Step 2 — Pull Otto's directional signals + +```bash +curl -fsS "https://signals.useotto.xyz/v1/news-flash?since_minutes={WINDOW_MIN}&severity_min=3" | jq . +curl -fsS "https://signals.useotto.xyz/v1/kol-sentiment" | jq . +curl -fsS "https://signals.useotto.xyz/v1/funding-extremes?limit=5" | jq . +``` + +- **When to use**: after readiness. +- **Output**: three JSON payloads combined into an "Otto view" per asset/topic. See `../SIGNAL_FEED_CONTRACT.md` for schemas. +- **Fallback**: if any endpoint is `status: degraded`, use the remaining signals but mark confidence penalty in the rank. + +### Step 3 — List candidate Polymarket markets + +Pull active markets in topics the Otto signals actually cover. Default to the `crypto`, `macro`, and `elections` categories unless user narrows. + +```bash +polymarket-plugin list-markets --limit 40 --category crypto +polymarket-plugin list-markets --limit 40 --category elections +polymarket-plugin list-markets --limit 40 --keyword "fed" +``` + +- **When to use**: after Step 2. +- **Output**: JSON list of markets with `market_id`, `question`, `end_date`, `yes_price`, `no_price`, `volume_usd`, `liquidity_usd`. +- **Filter**: + - `end_date` within `RESOLUTION_WINDOW_DAYS` (default 14). Skip deep-out markets — noise dominates. + - `liquidity_usd >= MIN_LIQUIDITY_USD` (default $5,000). + - `volume_usd >= MIN_VOLUME_USD` (default $1,000 last 24h). + +### Step 4 — Compute mispricing score per market + +For each candidate market, compute: + +``` +otto_estimate = Otto's probability estimate based on matched news-flash + kol + funding signals +implied_prob = mid-price of the YES token (from polymarket-plugin get-market) +edge_pct = otto_estimate - implied_prob # signed, positive = YES undervalued +abs_edge = abs(edge_pct) +signal_conf = max(news_severity_conf, kol_conf, funding_conf) for matched signals +liquidity_score = min(liquidity_usd / 10000, 1.0) + +mispricing_score = abs_edge × signal_conf × liquidity_score +``` + +- Keep only markets with `abs_edge >= MIN_EDGE_PCT` (default 0.08 = 8 percentage points). +- Sort descending by `mispricing_score`. +- Cap the list at `TOP_N_CANDIDATES` (default 5). +- If no candidate meets the threshold, return: "No Polymarket mispricings strong enough right now — try again later or a different category." + +### Step 5 — Present the ranked candidates + +Present a compact table to the user: + +``` +Top mispricing candidates (Otto edge ≥ 8%): + +1. "Will Fed cut rates by July 2026?" + Resolves: 2026-07-31 | YES @ $0.42 | Otto estimate: 0.58 | Edge: +16pp (YES) + Signal: Fed minutes dovish (severity 4, 3h ago), KOL bias 72% dovish + Liquidity: $18,400 | Volume (24h): $3,200 + +2. "Will BTC > $100K by May 2026?" + Resolves: 2026-05-31 | YES @ $0.31 | Otto estimate: 0.44 | Edge: +13pp (YES) + Signal: funding crowded-short (fade long), KOL bullish, no negative news flashes + Liquidity: $12,100 | Volume (24h): $1,900 + +... + +Which market would you like to trade? Reply with the number (1-5), or "none" to exit. +``` + +### Step 6 — Quote the selected market + +Once the user picks `N`: + +```bash +polymarket-plugin get-market --market-id --verbose +``` + +- **When to use**: confirm order book depth + refreshed mid-price before sizing. +- **Output**: JSON with full order book, best bid/ask, current mid-price, implied probability. + +Recompute `edge_pct` with the freshly-fetched mid — prices move quickly near resolution. If `edge_pct` dropped below `MIN_EDGE_PCT`, warn the user and ask whether to proceed anyway. + +### Step 7 — Confirm with the user (MANDATORY) + +``` +I'm about to buy {side} on "{question}". +• Market: {market_id} +• Side: {YES / NO} +• Amount: ${TRADE_SIZE_USD} USDC.e (Otto max: ${MAX_TRADE_SIZE_USD}) +• Price: {price} ({implied_prob:.0%} implied) +• Otto estimate: {otto_estimate:.0%} (edge {edge_pct:+.0%}) +• Session budget remaining: ${SESSION_BUDGET_REMAINING} +• Dry run? {DRY_RUN} + +Reply "confirm" to execute live, "paper" for dry-only, or "cancel". +``` + +**Do NOT proceed to Step 8 without the user's explicit "confirm".** + +### Step 8 — Place the order + +```bash +polymarket-plugin buy --market-id --outcome --amount --price --strategy-id otto-mispricing-assistant --order-type GTC +``` + +- **Size**: `TRADE_SIZE_USD`, always ≤ `MAX_TRADE_SIZE_USD`, and total across the session ≤ `MAX_SESSION_BUDGET_USD`. +- **Dry-run**: if `DRY_RUN = True`, omit `--price` and pass `--dry-run` flag (see `polymarket-plugin buy --help`). +- **Strategy attribution**: always include `--strategy-id otto-mispricing-assistant`. +- **Order type**: GTC for patient limit orders at Otto's suggested price; user may override to FOK if they want immediate fill. + +### Step 9 — Report back + +``` +✓ Otto Mispricing Assistant + {side} ${amount} on "{question}" @ ${price} + Market: {market_id} Resolves: {end_date} + Otto edge at entry: {edge_pct:+.0%} Confidence: {signal_conf:.2f} + Order id: {order_id} + Session budget used: ${used} / ${MAX_SESSION_BUDGET_USD} +``` + +Always remind the user: + +> "This is a single-market prediction-market trade. Prices can move against you even when news seems supportive. Polymarket markets resolve on a specific source — verify the resolution criteria before betting large." + +### Configuration commands + +Tunable parameters live in `scripts/config.py`: + +```bash +grep -E "^[A-Z_]+ " scripts/config.py # view defaults +``` + +### Optional autonomous poller (scanner only — never auto-buys) + +```bash +python3 scripts/bot.py --interval 900 --notify-only # print candidates every 15m, never trades +``` + +- **When to use**: user explicitly wants a background watcher that surfaces mispricings but never places orders. +- **Output**: JSONL log of scanned candidates; user pastes a market ID back into the reactive flow to trade. + +--- + +## Error Handling + +| Error | Cause | Resolution | +|---|---|---| +| `polymarket-plugin: command not found` | Basic Skill not installed | Run Pre-flight step 2. | +| `status: region_blocked` | Polymarket geofencing | Abort with clear message; do NOT suggest workarounds. | +| `status: needs_setup` | No proxy wallet yet | `polymarket-plugin setup-proxy`. | +| `status: needs_deposit` | Proxy wallet empty | `polymarket-plugin deposit --amount N`. | +| Signal feed 5xx / 503 | Otto backend transient failure | Retry ONCE after 3s. If still failing, abort. | +| Signal feed 429 | Rate-limited | Back off 30s, retry ONCE. | +| All candidate markets below `MIN_EDGE_PCT` | No edge available right now | Tell user "no Polymarket mispricings strong enough right now". | +| User picks a number outside 1..TOP_N | Typo or mis-selection | Ask again; do not default to any market. | +| `edge_pct` drops below threshold between Step 5 and Step 6 | Price moved | Warn user with updated numbers; ask whether to proceed. | +| `polymarket-plugin buy` returns insufficient liquidity | Order book thinner than list-markets showed | Offer to split the order or lower size; do NOT auto-split. | +| `MAX_SESSION_BUDGET_USD` reached | Session cap hit | Refuse further trades this session. | +| User explicitly aborts at Step 7 | Declined | Do not place. Do not retry. | + +--- + +## Security Notices + +**Risk level: `advanced`**. This Skill moves user funds on a public prediction market. Loss of capital is possible, including total loss of any given position. + +### Safeguards enforced by this Skill + +- **Explicit per-trade confirmation.** No batch mode, no autonomous orders. Every single trade requires the user to type "confirm" at Step 7. +- **Dry-run default.** `DRY_RUN = True` in `scripts/config.py`. Live orders require both `DRY_RUN = False` AND per-trade `--confirm`. +- **Per-trade cap.** `MAX_TRADE_SIZE_USD = 50` — no single trade above this regardless of user ask. +- **Per-session cap.** `MAX_SESSION_BUDGET_USD = 200` — cumulative trades refused once the budget is spent. +- **Resolution-window filter.** Markets with `end_date > RESOLUTION_WINDOW_DAYS` are filtered out — Otto's edge estimates degrade on long-dated markets. +- **Liquidity + volume floors.** Markets below `MIN_LIQUIDITY_USD` / `MIN_VOLUME_USD` are filtered — thin markets have poor exit paths. +- **Edge threshold.** Markets below `MIN_EDGE_PCT = 0.08` are filtered — smaller edges are dominated by noise + fees. +- **Price-refresh check.** Step 6 re-quotes between Step 5 ranking and Step 7 confirmation. User sees updated edge before confirming. +- **No key handling.** All signing flows through `polymarket-plugin`'s onchainos TEE-backed wallet. This Skill never touches private keys. +- **No credentials in source.** No API keys, tokens, or secrets committed. +- **Declared network surface.** Only `signals.useotto.xyz` + `gamma-api.polymarket.com` (via `polymarket-plugin`). Listed in `api_calls`. + +### Things this Skill will NOT do + +- **Never** place a trade without explicit user "confirm" at Step 7. +- **Never** execute batch orders — one market per user intent. +- **Never** bypass the Polymarket Basic Skill. All CLOB interactions MUST flow through `polymarket-plugin`. +- **Never** fabricate signals. If Otto's feed is unreachable or `status: degraded`, the Skill abstains. +- **Never** exceed `MAX_TRADE_SIZE_USD` or `MAX_SESSION_BUDGET_USD` — not even with user override. +- **Never** suggest workarounds for region blocks. +- **Never** re-submit a failed order automatically. + +### Risk disclaimer + +**This Skill is provided solely for educational research and technical reference purposes. It does not constitute investment advice, trading guidance, betting advice, or financial recommendations.** + +1. **Prediction markets can resolve against "obvious" signals.** Mispricings can persist or widen before they close. A positive expected-value trade can still lose. +2. **Resolution risk.** Polymarket markets resolve on specific oracle sources. Ambiguous outcomes may resolve differently than news coverage suggests. +3. **Liquidity risk.** Exit liquidity can disappear near resolution. You may have to hold a position to settlement whether you want to or not. +4. **Otto signals are aggregated, not predictive.** Otto's news + KOL + funding feeds reflect past and present behavior. They are NOT forecasts. +5. **Parameters are reference-only.** Defaults in `scripts/config.py` are not tuned for your risk tolerance. +6. **Regulatory risk.** Prediction markets are restricted or prohibited in many jurisdictions. Polymarket enforces geofencing. User is solely responsible for compliance, taxes, KYC. +7. **No profit guarantee.** Past mispricing detection accuracy ≠ future accuracy. +8. **Assumption of responsibility.** Strategy is AS-IS. Authors, Otto AI, OKX, Polymarket, and affiliates are not liable for losses. + +### No claim of OKX or Polymarket endorsement + +This Skill is authored by Otto AI, a community developer submitting to the OKX Plugin Store Developer Challenge. It is not endorsed by OKX or Polymarket. "OKX Onchain OS" and "Polymarket" are referenced as execution venues, not as affiliated entities. + +--- + +## Config reference + +See `scripts/config.py`. Key defaults: + +- `DRY_RUN = True` +- `MAX_TRADE_SIZE_USD = 50` +- `MAX_SESSION_BUDGET_USD = 200` +- `DEFAULT_TRADE_SIZE_USD = 10` +- `MIN_EDGE_PCT = 0.08` (8 percentage points) +- `RESOLUTION_WINDOW_DAYS = 14` +- `MIN_LIQUIDITY_USD = 5_000` +- `MIN_VOLUME_USD = 1_000` +- `TOP_N_CANDIDATES = 5` +- `WINDOW_MIN_NEWS = 360` (6h lookback for news flashes) +- `PRICE_STALENESS_SEC = 60` + +--- + +## Onchain OS Integration + +This Skill runs inside Onchain OS Agentic Wallet. All Polymarket interactions go through `polymarket-plugin`, which uses the TEE-backed signing context of the user's connected wallet. No private keys leave Onchain OS. + +Otto Mispricing Assistant does not provision its own wallet. It scans, ranks, and proposes — the user executes through the Basic Skill after explicit confirmation. + +--- + +## Links + +- Otto AI: https://useotto.xyz +- Signal feed contract: [../SIGNAL_FEED_CONTRACT.md](../SIGNAL_FEED_CONTRACT.md) +- Polymarket Basic Skill: https://github.com/okx/plugin-store/tree/main/skills/polymarket-plugin +- Docs: https://docs.useotto.xyz +- Source: https://github.com/useOttoAI/plugin-store diff --git a/skills/otto-mispricing-assistant/SUMMARY.md b/skills/otto-mispricing-assistant/SUMMARY.md new file mode 100644 index 000000000..d1bbefd3d --- /dev/null +++ b/skills/otto-mispricing-assistant/SUMMARY.md @@ -0,0 +1,92 @@ +# otto-mispricing-assistant + +## 1. Overview + +Otto Mispricing Assistant is a Strategy Skill for [OKX Onchain OS](https://web3.okx.com/onchainos) Agentic Wallet that scans active Polymarket prediction markets for statistical divergence between implied odds and Otto AI's real-time news, KOL-sentiment, and funding signals — then surfaces ranked candidates for the user to trade one at a time with explicit confirmation. + +Core operations: + +- Pull live Otto signals (news-flash, KOL-sentiment, funding-extremes) for macro + crypto topics +- List active Polymarket markets within a configurable resolution window, filtered for liquidity and volume +- Compute a mispricing score per market: `|otto_estimate − implied_prob| × signal_confidence × liquidity_score` +- Present a ranked shortlist (top 5) to the user with per-market reasoning +- Execute a single buy via the Polymarket Basic Skill after the user types "confirm" +- Enforce per-trade and per-session budget caps regardless of user ask + +This Skill is a **scanner, not a bot.** It never batch-executes, never trades without the user on a keyboard, and never re-submits failed orders. + +Tags: `prediction-markets` `polymarket` `polygon` `onchainos` `trading-strategy` `mispricing` `news-signals` + +## 2. Prerequisites + +- US and OFAC-sanctioned users are restricted from trading on Polymarket (geofenced) +- Supported chain: Polygon (MATIC). Supported tokens: USDC.e (collateral), POL (gas, or $0 in proxy mode) +- onchainos CLI ≥ 2.0.0 installed and authenticated (`onchainos wallet status`) +- Polymarket Basic Skill installed (`npx skills add okx/plugin-store --skill polymarket-plugin`) +- Polymarket proxy wallet set up (`polymarket-plugin setup-proxy`) — one-time ~$0.01 POL, then gasless trading +- USDC.e funded into the proxy via `polymarket-plugin deposit --amount N` +- Python 3.8+ for optional `scripts/bot.py` scanner (stdlib only, no pip dependencies) + +## 3. Quick Start + +1. **Dry-run first.** Paper mode is the default. + + ``` + otto-mispricing-assistant quickstart + ``` + +2. **Set up Polymarket.** + + ``` + polymarket-plugin check-access + polymarket-plugin setup-proxy + polymarket-plugin deposit --amount 50 + ``` + +3. **Scan for mispricings (dry-run).** + + ``` + otto-mispricing-assistant scan --category crypto + ``` + +4. **Pick a market from the ranked list, confirm, and go live** after reviewing paper runs: + + ``` + otto-mispricing-assistant trade --market-id --amount 10 --confirm + ``` + +5. **Review open positions.** + + ``` + polymarket-plugin get-positions + ``` + +## Safety defaults + +- Dry-run is the default. `--confirm` required for every live trade. +- Per-trade cap: $50 USDC.e. No single trade above this even with user override. +- Per-session cap: $200 USDC.e cumulative. +- Edge threshold: 8 percentage points minimum — smaller edges dominated by noise + fees. +- Resolution window: 14 days — longer-dated markets filtered out. +- Liquidity floor: $5,000 per market. Volume floor: $1,000 (24h). +- Single-market execution only — no batch orders, ever. + +## Trigger phrases + +The AI agent will route to Otto Mispricing Assistant on intents like: + +> "Find me a Polymarket market where the odds don't match the news" +> "Scan for Polymarket mispricings" +> "Where's the Polymarket edge right now?" +> "Show me late-stage Polymarket markets where Otto sees an edge" +> "Polymarket probability vs Otto's view" + +## Data moat + +Otto's signal feed combines 7+ news sources, top-50 KOL sentiment on Twitter/X, and live funding-rate skews from major CEXs. The mispricing score cross-references these with Polymarket's implied probabilities over a configurable resolution window. Same production pipeline that drives Otto AI's Market Alpha Agent. + +## Risk + +Prediction markets can resolve against "obvious" signals. Mispricings can persist or widen before closing. A positive-expected-value trade can still lose. Polymarket resolves on specific oracle sources — verify resolution criteria before sizing up. US / OFAC-sanctioned users are geofenced and must not attempt workarounds. + +See [SKILL.md](SKILL.md) for the full agent protocol and safety notices. diff --git a/skills/otto-mispricing-assistant/plugin.yaml b/skills/otto-mispricing-assistant/plugin.yaml new file mode 100644 index 000000000..c25662f14 --- /dev/null +++ b/skills/otto-mispricing-assistant/plugin.yaml @@ -0,0 +1,39 @@ +schema_version: 1 +name: otto-mispricing-assistant +version: "0.1.3" +description: "Otto Mispricing Assistant — Scan near-resolution Polymarket markets where implied probability diverges from Otto's news + KOL + funding signals, then trade a single market only after explicit user confirmation. Dry-run by default." +author: + name: "Otto AI" + github: "useOttoAI" +license: MIT +category: trading-strategy +tags: + - prediction-markets + - polymarket + - polygon + - onchainos + - trading-strategy + - mispricing + - news-signals + +# Strategy plugin metadata — declares the trading plugin this strategy calls, +# the risk tier, and the venues it operates on. Per FOR-DEVELOPERS.md +# "Submitting Strategy Plugins" section. +dependent_plugin: + - name: polymarket-plugin + version: "^0.5.0" + +risk_level: advanced + +supported_venues: + - polymarket + +components: + skill: + dir: . + +api_calls: + - "signals.useotto.xyz" + - "gamma-api.polymarket.com" + +type: community-developer diff --git a/skills/otto-mispricing-assistant/scripts/bot.py b/skills/otto-mispricing-assistant/scripts/bot.py new file mode 100644 index 000000000..bcb8af737 --- /dev/null +++ b/skills/otto-mispricing-assistant/scripts/bot.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +""" +Otto Mispricing Assistant v0.1 — Optional scanner (notify-only). + +The reactive flow documented in SKILL.md is the primary mode. This bot.py is for +advanced users who want a background watcher that periodically scans Polymarket +for mispricings and logs candidates. **This bot NEVER places orders.** It is +intentionally notify-only — to trade, the user paste a market ID back into the +reactive Skill flow where the Step 7 confirmation protocol enforces safety. + +Run: + python3 bot.py --interval 900 --notify-only # scan every 15 min, log only + +See config.py for every tunable. +""" +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +import time +import urllib.request +import urllib.error +from datetime import datetime, timezone +from pathlib import Path + +try: + import config # type: ignore[import-not-found] +except Exception: + sys.path.insert(0, str(Path(__file__).parent)) + import config # type: ignore[import-not-found] + + +def log(record: dict) -> None: + record["ts"] = datetime.now(timezone.utc).isoformat() + line = json.dumps(record) + print(line, flush=True) + if config.LOG_SCANS_TO_FILE: + with open(config.LOG_FILE, "a") as f: + f.write(line + "\n") + + +def _http_json(url: str) -> dict | None: + for attempt in range(config.SIGNAL_FEED_RETRIES + 1): + try: + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=config.SIGNAL_FEED_TIMEOUT) as resp: + return json.loads(resp.read()) + except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e: + log({"event": "http_error", "url": url, "attempt": attempt, "err": str(e)}) + time.sleep(3) + return None + + +def fetch_otto_signals() -> dict: + return { + "news": _http_json(f"{config.SIGNAL_FEED_BASE}/v1/news-flash?since_minutes={config.WINDOW_MIN_NEWS}&severity_min=3"), + "kol": _http_json(f"{config.SIGNAL_FEED_BASE}/v1/kol-sentiment"), + "funding": _http_json(f"{config.SIGNAL_FEED_BASE}/v1/funding-extremes?limit=5"), + } + + +def fetch_polymarket_markets(category: str) -> list[dict]: + try: + out = subprocess.check_output( + ["polymarket-plugin", "list-markets", "--limit", "40", "--category", category], + text=True, timeout=20, + ) + data = json.loads(out) + return data.get("markets", []) if isinstance(data, dict) else data + except Exception as e: + log({"event": "polymarket_list_failed", "category": category, "err": str(e)}) + return [] + + +def polymarket_ready() -> bool: + try: + out = subprocess.check_output(["polymarket-plugin", "quickstart"], text=True, timeout=15) + data = json.loads(out) + return data.get("status") in ("ready", "proxy_ready", "eoa_ready") + except Exception as e: + log({"event": "polymarket_quickstart_failed", "err": str(e)}) + return False + + +def estimate_otto_probability(market: dict, signals: dict) -> tuple[float, float, list[str]]: + """ + Very conservative v0.1 estimator — combines matched news-flash direction_bias + and KOL sentiment magnitude. Returns (prob, confidence, matched_signal_tags). + + This is intentionally simple. v0.2 will incorporate funding extremes and a + learned calibration pass. + """ + question = (market.get("question") or "").lower() + tags = [] + conf = 0.0 + delta = 0.0 + + news = (signals.get("news") or {}).get("flashes", []) + for flash in news: + assets = [a.lower() for a in flash.get("affected_assets", [])] + for asset in assets: + if asset and asset in question: + severity = flash.get("severity", 3) / 5.0 + if flash.get("direction_bias") == "risk-on": + delta += 0.05 * severity + elif flash.get("direction_bias") == "risk-off": + delta -= 0.05 * severity + conf = max(conf, severity) + tags.append(f"news:{flash.get('id','?')}") + + kol = signals.get("kol") or {} + kol_coin = (kol.get("coin") or "").lower() + if kol_coin and kol_coin in question and kol.get("kol_count", 0) >= 30: + if kol.get("direction") == "long": + delta += 0.04 * kol.get("confidence", 0) + elif kol.get("direction") == "short": + delta -= 0.04 * kol.get("confidence", 0) + conf = max(conf, kol.get("confidence", 0)) + tags.append(f"kol:{kol_coin}") + + try: + implied = float(market.get("yes_price", 0.5)) + except (TypeError, ValueError): + implied = 0.5 + otto_estimate = max(0.02, min(0.98, implied + delta)) + return otto_estimate, conf, tags + + +def score_candidate(market: dict, signals: dict) -> dict | None: + try: + implied = float(market.get("yes_price", 0)) + liquidity = float(market.get("liquidity_usd", 0)) + volume = float(market.get("volume_usd", 0)) + except (TypeError, ValueError): + return None + if liquidity < config.MIN_LIQUIDITY_USD or volume < config.MIN_VOLUME_USD: + return None + + otto_estimate, signal_conf, tags = estimate_otto_probability(market, signals) + edge = otto_estimate - implied + abs_edge = abs(edge) + if abs_edge < config.MIN_EDGE_PCT or signal_conf < 0.2: + return None + liq_score = min(liquidity / 10_000, 1.0) + score = abs_edge * signal_conf * liq_score + + return { + "market_id": market.get("market_id") or market.get("id"), + "question": market.get("question"), + "end_date": market.get("end_date"), + "implied_prob": implied, + "otto_estimate": otto_estimate, + "edge_pct": edge, + "signal_conf": signal_conf, + "liquidity_usd": liquidity, + "volume_usd": volume, + "mispricing_score": score, + "side": "yes" if edge > 0 else "no", + "matched_signals": tags, + } + + +def scan_once() -> list[dict]: + if not polymarket_ready(): + log({"event": "polymarket_not_ready"}) + return [] + signals = fetch_otto_signals() + if not any(signals.values()): + log({"event": "no_signals_available"}) + return [] + candidates: list[dict] = [] + for category in config.DEFAULT_CATEGORIES: + for market in fetch_polymarket_markets(category): + scored = score_candidate(market, signals) + if scored: + scored["category"] = category + candidates.append(scored) + candidates.sort(key=lambda c: c["mispricing_score"], reverse=True) + return candidates[: config.TOP_N_CANDIDATES] + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--interval", type=int, default=900, help="seconds between scans") + parser.add_argument("--notify-only", action="store_true", default=True, + help="log candidates only; this bot NEVER trades") + parser.add_argument("--once", action="store_true") + args = parser.parse_args() + + log({"event": "scanner_start", "interval": args.interval, "notify_only": True, + "dry_run": config.DRY_RUN, "paused": config.PAUSED}) + + while True: + if config.PAUSED: + log({"event": "paused"}) + else: + candidates = scan_once() + if not candidates: + log({"event": "no_candidates_this_cycle"}) + else: + log({"event": "candidates", "count": len(candidates), "top": candidates}) + + if args.once: + break + time.sleep(args.interval) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + log({"event": "scanner_stop", "reason": "keyboard_interrupt"}) diff --git a/skills/otto-mispricing-assistant/scripts/config.py b/skills/otto-mispricing-assistant/scripts/config.py new file mode 100644 index 000000000..8229bfc5b --- /dev/null +++ b/skills/otto-mispricing-assistant/scripts/config.py @@ -0,0 +1,52 @@ +""" +Otto Mispricing Assistant v0.1 — Hot-reloadable configuration. + +Every parameter below is tunable without changing bot.py or the SKILL protocol. +The AI agent reads these values when ranking candidates and when sizing a trade. + +⚠️ Disclaimer: values are reference defaults sized for a generalist user on +Polymarket. They are NOT investment advice and may not fit your risk tolerance +or jurisdiction. Adjust before going live. +""" + +# ── Run mode ───────────────────────────────────────────────────────────────── +DRY_RUN = True # True = no live orders, even with --confirm +PAUSED = False # True = refuse even dry-run scans + +# ── Trade sizing ───────────────────────────────────────────────────────────── +DEFAULT_TRADE_SIZE_USD = 10 # per-trade default in USDC.e +MIN_TRADE_SIZE_USD = 2 +MAX_TRADE_SIZE_USD = 50 # hard cap per single trade +MAX_SESSION_BUDGET_USD = 200 # cumulative cap per session + +# ── Edge + filter thresholds ───────────────────────────────────────────────── +# Edge gate stays tight (mispricing is the whole point); only loosened the +# signal-confidence floor in score_candidate to accommodate news-flash flashes +# whose severity-derived confidence sits at 0.6-0.8. +MIN_EDGE_PCT = 0.05 # min |otto_estimate - implied_prob| to surface +RESOLUTION_WINDOW_DAYS = 14 # skip markets resolving later than this +MIN_LIQUIDITY_USD = 5_000 # on-book liquidity floor +MIN_VOLUME_USD = 1_000 # 24h volume floor +TOP_N_CANDIDATES = 5 # max candidates presented to user + +# ── Signal windows ─────────────────────────────────────────────────────────── +WINDOW_MIN_NEWS = 360 # 6h lookback for news-flash relevance +WINDOW_HOURS_KOL = 24 # rolling KOL sentiment window +PRICE_STALENESS_SEC = 60 # re-quote if older than this at Step 7 + +# ── Signal feed ────────────────────────────────────────────────────────────── +# Calibrated against live signals.useotto.xyz cadence 2026-04-30: +# - News-flash producer: top_ten_analysis pipeline runs every 6h (top-of-window). +# - KOL pipeline: hourly. Funding pipeline: not yet shipped. +# - 75 min covers the typical refresh latency without firing on stale data. +SIGNAL_FEED_BASE = "https://signals.useotto.xyz" +SIGNAL_FEED_TIMEOUT = 4 +SIGNAL_FEED_RETRIES = 1 +MAX_SIGNAL_AGE_SEC = 4500 # 75 min — covers full hourly KOL + 6h news cycles + +# ── Polymarket categories to scan by default ───────────────────────────────── +DEFAULT_CATEGORIES = ["crypto", "macro", "elections"] + +# ── Logging ────────────────────────────────────────────────────────────────── +LOG_SCANS_TO_FILE = True +LOG_FILE = "otto_mispricing_scans.jsonl"