# Edge Trader Bot — Build Plan

> **How to use this file:** Drop it into a folder with a fresh Next.js + Netlify project,
> open it in Claude Code, and say *"Read this file and build everything."*
> Configure your env vars. Deploy. Fund your wallet. Trade.

> **Prerequisites:** Create a Polymarket account via [this link](https://polymarket.com?via=alphastack-eymx),
> fund a Polygon wallet with USDC, and have a Netlify site deployed.

---

# Part 1: The Big Picture

## What This Bot Does

An autonomous trading bot that detects mispriced Polymarket markets
using Truth Machine's free AI edge detection API, places FOK (Fill-or-Kill)
orders on Polymarket's CLOB, manages positions with dynamic targets,
and exits via stop-loss, target-hit, AI reversal, or time decay.

```
 EDGE TRADER BOT
 ════════════════════════════════════════════════════════════
 Every N minutes (configurable):

 ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
 │  REVIEW  │─►│  SELL     │─►│  SCAN    │─►│  BUY     │
 │ existing │  │ targets,  │  │ for new  │  │ top edge │
 │ positions│  │ stops,    │  │ edges    │  │ markets  │
 │          │  │ reversals │  │          │  │          │
 └──────────┘  └──────────┘  └──────────┘  └──────────┘
       │              │              │              │
       ▼              ▼              ▼              ▼
  Update AI      FOK sell at    Filter by      FOK buy at
  probabilities  market price   edge, conf,    market price
  + targets                     price range
 ════════════════════════════════════════════════════════════
```

## System Architecture

```
┌───────────────────────────────────────────────────────────────┐
│              NETLIFY SERVERLESS FUNCTIONS                      │
│                                                               │
│  ┌───────────────┐  ┌──────────────────┐  ┌──────────────┐  │
│  │ bot-cycle.ts  │  │ bot-cycle-       │  │ bot-status.ts│  │
│  │ (dispatcher)  │  │ background.ts    │  │ (config/API) │  │
│  │               │  │ (15-min worker)  │  │              │  │
│  │ POST: create  │  │                  │  │ GET: status  │  │
│  │ job, invoke   │──│ Run full cycle:  │  │ POST: update │  │
│  │ background fn │  │ review→sell→     │  │ config       │  │
│  │               │  │ scan→buy         │  │              │  │
│  │ GET: poll     │  │                  │  │              │  │
│  │ job status    │  │ Stream progress  │  │ Health check │  │
│  └───────┬───────┘  │ to Netlify Blobs │  └──────┬───────┘  │
│          │          └────────┬─────────┘         │          │
│          │                   │                    │          │
│  ┌───────┴───────────────────┴────────────────────┴───────┐  │
│  │                 NETLIFY BLOBS (state)                    │  │
│  │  ├── bot/state        → positions, config, stats        │  │
│  │  ├── bot/api-creds    → derived CLOB API keys           │  │
│  │  ├── bot/cycle-lock   → prevents concurrent cycles      │  │
│  │  ├── bot/cycle-history→ last 200 cycle results          │  │
│  │  └── bot-jobs/{id}    → background job progress         │  │
│  └─────────────────────────────────────────────────────────┘  │
│          │                   │                    │          │
│  ┌───────┴───────────────────┴────────────────────┴───────┐  │
│  │              SHARED LIBRARIES (src/lib/bot/)             │  │
│  │  ├── types.ts    → BotConfig, BotPosition, CycleResult  │  │
│  │  ├── store.ts    → getBotState(), saveBotState(), lock   │  │
│  │  ├── client.ts   → getClobClient(), deriveAndStoreCreds  │  │
│  │  ├── market.ts   → fetchTradingInfo() from Gamma API    │  │
│  │  └── cycle.ts    → runCycle() — THE CORE TRADING LOGIC  │  │
│  └─────────────────────────────────────────────────────────┘  │
└──────────────────────────┬──────────────────────────────────┘
                           │
           ┌───────────────┼───────────────┐
           ▼               ▼               ▼
    ┌────────────┐  ┌────────────┐  ┌────────────┐
    │ Truth      │  │ Polymarket │  │ Polymarket  │
    │ Machine    │  │ Gamma API  │  │ CLOB API    │
    │            │  │            │  │             │
    │ GET /api/  │  │ GET /events│  │ Orders,     │
    │ best-bets  │  │ → token    │  │ order books,│
    │            │  │   IDs,     │  │ balances    │
    │ FREE       │  │   metadata │  │             │
    │ No auth    │  │            │  │ Requires    │
    │ 15m cache  │  │ FREE       │  │ HMAC auth   │
    └────────────┘  └────────────┘  └─────────────┘
```

## Data Flow: From Edge Detection to Trade

```
Step 1: EDGE DETECTION (Truth Machine does this for free)
══════════════════════════════════════════════════════════

  Truth Machine AI Pipeline:
  ┌─────────┐   ┌──────────┐   ┌──────────────────────────┐
  │ 21 Data │──►│ Gemini   │──►│ Edge = AI_prob - Market%  │
  │ Sources │   │ AI       │   │                           │
  │ (news,  │   │ Analysis │   │ Example:                  │
  │ finance,│   │          │   │ AI says 85% YES           │
  │ sports) │   │          │   │ Market says 72¢ YES       │
  └─────────┘   └──────────┘   │ Edge = 13% (buy YES)     │
                               └─────────┬────────────────┘
                                         │
                          GET /api/best-bets?limit=50
                                         │
                                         ▼
Step 2: CANDIDATE FILTERING
══════════════════════════════════════════════════════════

  Bot Criteria:
  ┌─────────────────────────────────────────────────────┐
  │  Edge > minEdge (default 6%)?     ──► YES ✓         │
  │  Not already holding this market?  ──► YES ✓         │
  │  Price between 5¢ and 90¢?        ──► YES ✓         │
  │  Analysis < 48 hours old?          ──► YES ✓         │
  │  Not on FOK cooldown?             ──► YES ✓         │
  │  Balance >= bet size?              ──► YES ✓         │
  │  Under max positions?             ──► YES ✓         │
  │                                                     │
  │  ──► PASS: Place FOK order                          │
  └─────────────────────────────────────────────────────┘
                                         │
                                         ▼
Step 3: POSITION SIZING
══════════════════════════════════════════════════════════

  Two modes (configurable):
  ┌─────────────────────────────────────────────────────┐
  │  PERCENT MODE (default):                            │
  │    betSize = balance * betSizePct (e.g. 5%)         │
  │    Floor: $5  |  Ceiling: $500                      │
  │                                                     │
  │  FIXED MODE:                                        │
  │    betSize = fixed amount (e.g. $15)                │
  └─────────────────────────────────────────────────────┘
                                         │
                                         ▼
Step 4: ORDER EXECUTION (FOK = Fill or Kill)
══════════════════════════════════════════════════════════

  ┌──────────┐   ┌───────────┐   ┌──────────────────┐
  │ Resolve  │──►│ Get order │──►│ Place FOK order  │
  │ slug to  │   │ book,     │   │ on CLOB          │
  │ token ID │   │ tick size │   │                  │
  │ (Gamma)  │   │ (CLOB)    │   │ Max price: +10%  │
  │          │   │           │   │ above market     │
  └──────────┘   └───────────┘   └──────────────────┘
                                         │
                                         ▼
Step 5: TARGET PRICING
══════════════════════════════════════════════════════════

  Edge-Based Targets:
  ┌─────────────────────────────────────────────────────┐
  │  target = fillPrice + edge * edgeCaptureRatio       │
  │                                                     │
  │  Example:                                           │
  │  Filled at 72¢, edge = 13%, captureRatio = 60%      │
  │  target = 0.72 + 0.13 * 0.6 = 79.8¢               │
  │                                                     │
  │  Bot sells when fair price reaches 79.8¢            │
  └─────────────────────────────────────────────────────┘
```

## What It Costs

| Service | Cost | Notes |
|---------|------|-------|
| Truth Machine API | **Free** | No auth, 15-min cache |
| Polymarket trading | Gas fees only | ~$0.001 per order on Polygon |
| USDC capital | Your trading capital | Start with $50-100 |
| Netlify hosting | **Free tier** | Generous function invocations |

## What You Need

| Requirement | Where | Notes |
|-------------|-------|-------|
| Netlify account | [netlify.com](https://netlify.com) | For deployment + Blobs storage |
| Polygon wallet private key | MetaMask export or generate | Hex format, starts with 0x |
| Polymarket proxy address | polymarket.com → Settings → Advanced | Your "Funder" address |
| USDC on Polygon | Bridge or buy via exchange | Your trading capital |
| Polymarket account | [polymarket.com](https://polymarket.com?via=alphastack-eymx) | Must have traded at least once |

---

# Part 2: Project Setup

## Create the Project

```bash
npx create-next-app@latest edge-trader --typescript --tailwind --app
cd edge-trader
npm install @polymarket/clob-client @ethersproject/wallet @netlify/blobs @netlify/functions
```

## Project Structure

```
edge-trader/
├── .env                                ← secrets (NEVER commit)
├── netlify.toml                        ← Netlify config
├── netlify/functions/
│   ├── bot-cycle.ts                    ← Cycle dispatcher (v2 handler)
│   ├── bot-cycle-background.ts         ← Background worker (v1 handler, 15min)
│   ├── bot-status.ts                   ← GET/POST status & config
│   └── bot-sell.ts                     ← Force-sell endpoint
├── src/lib/bot/
│   ├── types.ts                        ← All type definitions
│   ├── store.ts                        ← Netlify Blobs state persistence
│   ├── client.ts                       ← Polymarket CLOB client
│   ├── market.ts                       ← Gamma API market info fetcher
│   └── cycle.ts                        ← CORE: the trading cycle logic
└── src/app/bot/
    ├── page.tsx                         ← Page wrapper
    └── BotClient.tsx                    ← React dashboard (optional)
```

## Environment Variables

Set these in your Netlify site's environment settings:

```env
# Required — Polygon wallet
POLYMARKET_PRIVATE_KEY=0xabc123...      # Your wallet private key (hex)
POLYMARKET_FUNDER=0xdef456...           # Your Polymarket proxy/funder address
POLYMARKET_SIG_TYPE=1                    # 0=EOA, 1=POLY_PROXY (default), 2=GNOSIS_SAFE

# Required — Netlify (auto-set if using Netlify CLI, manual for Netlify UI)
NETLIFY_API_TOKEN=nfp_xxx...            # Personal access token from Netlify UI
NETLIFY_SITE_ID=abc-123-def             # Your site ID

# Required — Admin auth (choose any secret string)
ADMIN_SECRET=your-secret-here           # Protects all API endpoints
```

## netlify.toml

```toml
[build]
  command = "npm run build"
  publish = ".next"

[functions]
  directory = "netlify/functions"

[[redirects]]
  from = "/api/bot/*"
  to = "/.netlify/functions/:splat"
  status = 200
```

---

# Part 3: Type Definitions

## src/lib/bot/types.ts

Build this file first — everything else depends on these types.

```
┌─────────────────────────────────────────────────────────────┐
│                     TYPE HIERARCHY                           │
│                                                              │
│  BotState                                                    │
│  ├── positions: BotPosition[]   ← every trade ever taken     │
│  ├── config: BotConfig          ← all trading parameters     │
│  ├── totalCycles: number        ← lifetime cycle count       │
│  ├── totalTrades: number        ← lifetime trade count       │
│  ├── totalPnl: number           ← lifetime realized P&L      │
│  ├── portfolioSnapshots[]       ← for sparkline charts       │
│  └── fokCooldowns: Record       ← slug→timestamp of failure  │
│                                                              │
│  BotPosition                                                 │
│  ├── Core: id, slug, tokenId, side, shares, costBasis       │
│  ├── Pricing: entryPrice, targetPrice, originalTargetPrice   │
│  ├── AI: aiProbability, edge, lastFairPrice                  │
│  ├── Status: "open" | "sold" | "expired"                     │
│  └── Exit: soldAt, soldPrice, pnl, sellReason               │
│                                                              │
│  CycleResult                                                 │
│  ├── sold: []        ← positions closed this cycle           │
│  ├── bought: []      ← positions opened this cycle           │
│  ├── reviewed: []    ← positions checked with AI update      │
│  ├── skipped: []     ← reasons we didn't buy                 │
│  ├── errors: []      ← anything that failed                  │
│  ├── balance: number ← USDC after cycle                      │
│  └── openPositions   ← count of open positions               │
│                                                              │
│  ProgressEvent                                               │
│  ├── phase: init|balance|fetch_ai|review|scan|buy|sell       │
│  ├── message: string                                         │
│  └── data?: Record<string, unknown>                          │
└─────────────────────────────────────────────────────────────┘
```

Key types to implement:

- **`BotPosition`** — Tracks every position from entry to exit. Must include `entryPrice`, `targetPrice`, `originalTargetPrice` (floor for target ratcheting), `aiProbability`, `edge`, `lastFairPrice`, `negRisk`, `status`, `sellReason`.

- **`BotConfig`** — All configurable parameters: `minEdge`, `betSize`, `betSizePct`, `betSizeMode` ("fixed"|"percent"), `edgeCaptureRatio`, `maxPositions`, `maxBetsPerCycle`, `scanIntervalMin`, `stopLossPct`, `maxHoldDays`, `maxDrawdownPct`, `buyEnabled`, `sellEnabled`.

- **`BotState`** — The root state object persisted in Netlify Blobs. Includes `positions[]`, `config`, counters, and `fokCooldowns` (slug→ISO timestamp map for tracking failed FOK orders).

- **`CycleResult`** — What each cycle returns: `sold[]`, `bought[]`, `reviewed[]`, `skipped[]`, `errors[]`, `balance`, `openPositions`, `timestamp`.

- **`ProgressEvent`** — Streamed to frontend during cycle execution. Has a `phase` field matching the pipeline stage.

- **`DEFAULT_CONFIG`** — Sensible defaults: 6% min edge, 5% bet sizing, 8 max positions, 2 bets/cycle, 5-min interval, 30% stop-loss, 30-day max hold.

- **`STRATEGY_PRESETS`** — Conservative, Balanced, Aggressive presets that override trading params but never toggle buyEnabled/sellEnabled.

---

# Part 4: State Persistence

## src/lib/bot/store.ts

Uses Netlify Blobs for all state. No database needed.

```
┌─────────────────────────────────────────────────────────────┐
│                   BLOB STORE LAYOUT                         │
│                                                              │
│  Store: "bot"                                                │
│  ├── key: "state"          → JSON(BotState)                  │
│  ├── key: "api-creds"      → JSON(StoredCreds)               │
│  ├── key: "cycle-lock"     → JSON({ acquiredAt })            │
│  └── key: "cycle-history"  → JSON(CycleResult[])             │
│                                                              │
│  Store: "bot-jobs"                                           │
│  └── key: "{jobId}"        → JSON({ status, progress })     │
│                                                              │
│  Store: "bot-cache"                                          │
│  ├── key: "health"         → cached health check             │
│  └── key: "on-chain-pos"   → cached on-chain positions       │
└─────────────────────────────────────────────────────────────┘
```

Key functions:

- **`getBotState()`** — Load state from blob, merge with `DEFAULT_CONFIG` for forward-compatibility. Run migrations for new fields (e.g., `fokCooldowns`, `originalTargetPrice`).

- **`saveBotState(state)`** — Write full state to blob.

- **`acquireCycleLock()`** — Read-then-write lock with 14-minute TTL. Returns `false` if lock is held. Prevents concurrent cycles.

- **`releaseCycleLock()`** / **`forceReleaseLock()`** — Delete the lock blob.

- **`appendCycleHistory(result)`** — Prepend to cycle history, cap at 200 entries.

---

# Part 5: CLOB Client

## src/lib/bot/client.ts

Manages the `@polymarket/clob-client` connection and API credential lifecycle.

```
┌─────────────────────────────────────────────────────────────┐
│              CLIENT INITIALIZATION FLOW                      │
│                                                              │
│  1. Read private key from env → create ethers Wallet        │
│  2. Check blob store for cached API credentials              │
│  3. If none: call createOrDeriveApiKey() → store in blob    │
│  4. Create ClobClient with wallet + creds + funder + sigType│
│                                                              │
│  IMPORTANT: @polymarket/clob-client is ESM-only.            │
│  Use dynamic import() everywhere to avoid CJS crashes.      │
│                                                              │
│  const { ClobClient, Chain } = await import(                │
│    "@polymarket/clob-client"                                │
│  );                                                          │
└─────────────────────────────────────────────────────────────┘
```

Key functions:

- **`getClobClient()`** — Returns a ready-to-trade ClobClient. Auto-derives credentials on first use.
- **`deriveAndStoreCreds()`** — Derives API keys and persists to blob.
- **`getStoredCreds()`** — Read cached credentials.
- **`getWalletAddress()`** / **`getFunderAddress()`** — Safe public info getters.

---

# Part 6: Market Info Fetcher

## src/lib/bot/market.ts

Resolves market slugs to token IDs needed for CLOB orders.

```
┌─────────────────────────────────────────────────────────────┐
│              SLUG → TOKEN ID RESOLUTION                      │
│                                                              │
│  Input: "us-cuba-invasion--will-the-us-invade-cuba"          │
│         └── event slug ──┘  └── market slug ──────┘          │
│                                                              │
│  1. Split on "--" → eventSlug + marketSlug                   │
│  2. GET gamma-api.polymarket.com/events?slug={eventSlug}     │
│  3. Find sub-market matching marketSlug                      │
│     (if not found: return null — NEVER fallback to wrong     │
│      market to avoid trading the wrong tokens)               │
│  4. Parse clobTokenIds → YES token ID + NO token ID          │
│  5. Return TradingMarketInfo { tokenIds, negRisk, active }   │
└─────────────────────────────────────────────────────────────┘
```

---

# Part 7: The Trading Cycle (CORE)

## src/lib/bot/cycle.ts

This is the brain of the bot. Called by the background function every cycle.

```
┌─────────────────────────────────────────────────────────────┐
│                    CYCLE PIPELINE                            │
│                                                              │
│  Phase 1: INIT                                               │
│  ├── Acquire cycle lock (fail if already running)            │
│  ├── Load bot state + config                                 │
│  ├── Check buyEnabled || sellEnabled (need at least one)     │
│  └── Initialize CLOB client                                  │
│                                                              │
│  Phase 2: BALANCE                                            │
│  ├── Fetch USDC balance from CLOB                            │
│  ├── If auth fails: re-derive credentials, retry             │
│  └── Check drawdown circuit breaker                          │
│                                                              │
│  Phase 3: FETCH AI DATA                                      │
│  ├── GET /api/best-bets?limit=50&min_edge=0                 │
│  ├── For each held position: also fetch individual analysis  │
│  │   (individual blobs are more current than the index)      │
│  └── Build aiBySlug lookup map                               │
│                                                              │
│  Phase 4: REVIEW POSITIONS (if any are open)                 │
│  ├── For each open position:                                 │
│  │   ├── Get order book → compute fair price                 │
│  │   ├── Update lastFairPrice + lastReviewedAt               │
│  │   ├── Check: market resolved (fair >= 95¢)? → SELL       │
│  │   ├── Check: AI edge reversed (<=0)? → SELL              │
│  │   ├── Check: edge shrunk significantly? → lower target    │
│  │   ├── Check: edge grew significantly? → raise target      │
│  │   ├── Check: stop-loss triggered? → SELL                 │
│  │   ├── Check: max hold days exceeded? → SELL              │
│  │   └── Check: target hit (fair >= target)? → SELL         │
│  └── All sell actions gated by sellEnabled flag              │
│                                                              │
│  Phase 5: SCAN + BUY (if buyEnabled)                         │
│  ├── Filter candidates by edge, price range, staleness       │
│  ├── Exclude: already-held slugs, FOK cooldown slugs         │
│  ├── Sort by edge descending                                 │
│  ├── For top N candidates:                                   │
│  │   ├── Resolve slug → token ID (Gamma API)                │
│  │   ├── Get order book → tick size                          │
│  │   ├── Place FOK BUY (max price = market + 10%)           │
│  │   ├── If FOK fails: record 30-min cooldown for slug      │
│  │   ├── Compute edge at fill price (may differ from est.)  │
│  │   ├── Set target = fillPrice + edge * captureRatio        │
│  │   └── Create BotPosition, push to state                   │
│  └── Subtract cost locally for multi-buy balance tracking    │
│                                                              │
│  Phase 6: SAVE + COMPLETE                                    │
│  ├── Prune closed positions older than 7 days                │
│  ├── Record portfolio snapshot                               │
│  ├── Increment totalCycles                                   │
│  ├── Save state to blob                                      │
│  ├── Append to cycle history                                 │
│  └── Release cycle lock                                      │
└─────────────────────────────────────────────────────────────┘
```

### Critical Implementation Details

**Edge Computation (must be consistent everywhere):**
```
For YES positions:
  edge = aiProbability - currentYesPrice

For NO positions:
  edge = (1 - aiProbability) - currentNoPrice
```

**Target Pricing:**
```
target = fillPrice + edgeAtEntry * edgeCaptureRatio
target = clamp(target, fillPrice + 0.01, 0.95)

If slippage ate the edge (edge < 2% at fill):
  target = fillPrice + 0.01  (exit at breakeven)
```

**Target Ratcheting (during review):**
```
When AI raises probability → raise target
When AI lowers probability → lower target
  BUT: never below midpoint of (entry, originalTarget)
```

**FOK Cooldown:**
```
When a FOK order fails (illiquid market):
  state.fokCooldowns[slug] = new Date().toISOString()

On next cycle's buy phase:
  Clean up cooldowns older than 30 minutes
  Filter out cooled-down slugs from candidates
```

**Sell Price Floors (prevent dumping on thin books):**
```
Target hit:    minPrice = targetPrice * 90%
Stop-loss:     minPrice = fairPrice * 80%
AI reversal:   minPrice = fairPrice * 70%
Time decay:    minPrice = fairPrice * 70%
Market resolved: minPrice = fairPrice * 90%
```

**Circuit Breaker:**
```
If totalPnl < -(startingCapital * maxDrawdownPct):
  Disable buyEnabled (but keep sellEnabled for orderly exits)
  Log + return error
```

---

# Part 8: Netlify Functions (API Layer)

## netlify/functions/bot-cycle.ts (Dispatcher)

```
┌─────────────────────────────────────────────────────────────┐
│              CYCLE DISPATCH PATTERN                          │
│                                                              │
│  POST /api/bot/cycle                                        │
│  ├── Verify admin secret (x-admin-secret header)            │
│  ├── Generate jobId = "cycle_{timestamp}_{random}"           │
│  ├── Store job as "dispatched" in bot-jobs blob              │
│  ├── POST to /.netlify/functions/bot-cycle-background        │
│  │   with jobId in body                                      │
│  └── Return { jobId } immediately to client                  │
│                                                              │
│  GET /api/bot/cycle?jobId=xxx                                │
│  ├── Verify admin secret                                     │
│  └── Return job status + progress events from blob           │
│                                                              │
│  WHY THIS PATTERN:                                           │
│  Netlify v2 functions timeout after 10 seconds.              │
│  Background functions (v1, "-background" suffix) run 15 min. │
│  Dispatcher bridges the gap: instant response + long worker. │
└─────────────────────────────────────────────────────────────┘
```

## netlify/functions/bot-cycle-background.ts (Worker)

```
┌─────────────────────────────────────────────────────────────┐
│              BACKGROUND FUNCTION                             │
│                                                              │
│  v1 handler format (HandlerEvent, not Request)               │
│  Filename must end in "-background" for Netlify to recognize │
│                                                              │
│  1. Parse jobId from body                                    │
│  2. Verify job exists in bot-jobs blob with "dispatched"     │
│  3. Mark job as "running"                                    │
│  4. Call runCycle(onProgress) from cycle.ts                   │
│  5. onProgress callback: append events + update blob         │
│     (frontend polls this for live progress)                  │
│  6. On success: mark job "complete" with result              │
│  7. On error: mark job "error" with message                  │
│  8. Always return 202                                        │
└─────────────────────────────────────────────────────────────┘
```

## netlify/functions/bot-status.ts (Status & Config)

```
┌─────────────────────────────────────────────────────────────┐
│              STATUS ENDPOINT                                 │
│                                                              │
│  GET /api/bot/status                                        │
│  ├── Run health checks (cached 5 min):                      │
│  │   ├── Env vars present?                                  │
│  │   ├── API creds stored?                                  │
│  │   ├── CLOB API reachable?                                │
│  │   ├── Best-bets API reachable?                           │
│  │   ├── USDC balance?                                      │
│  │   └── POL (gas) balance? (multi-RPC fallback)            │
│  ├── Load full state                                        │
│  ├── Compute stats (P&L, win rate, unrealized)              │
│  └── Return config + positions + health + stats              │
│                                                              │
│  POST /api/bot/status                                       │
│  ├── body.positionId? → edit position (target, expire, del) │
│  ├── body.action="force_release_lock"? → release lock       │
│  └── Otherwise → update config with validation              │
└─────────────────────────────────────────────────────────────┘
```

## netlify/functions/bot-sell.ts (Force-Sell)

```
┌─────────────────────────────────────────────────────────────┐
│              FORCE-SELL ENDPOINT                              │
│                                                              │
│  POST /api/bot/sell { positionId }                          │
│  ├── Find open position by ID                                │
│  ├── Get order book for price floor                          │
│  ├── Accept up to 20% slippage from best bid                 │
│  ├── Place FOK SELL order                                    │
│  ├── Update position: status="sold", pnl, soldPrice          │
│  └── Save state                                              │
└─────────────────────────────────────────────────────────────┘
```

---

# Part 9: Frontend (Optional Dashboard)

The frontend is a React client component (`"use client"`) that:

1. **Authenticates** via admin secret stored in sessionStorage
2. **Polls** bot status every 30 seconds
3. **Runs cycles** via POST → poll job progress → show live log
4. **Manages positions** — edit targets, force-sell, write-off, delete
5. **Configures strategy** — Buy/Sell toggles, sizing, edge thresholds, stops, presets

The UI is optional — the bot works entirely via API calls. You could run it with just `curl`:

```bash
# Run a cycle
curl -X POST https://your-site.netlify.app/api/bot/cycle \
  -H "x-admin-secret: your-secret" | jq .jobId

# Poll for result
curl "https://your-site.netlify.app/api/bot/cycle?jobId=cycle_xxx" \
  -H "x-admin-secret: your-secret" | jq .

# Check status
curl https://your-site.netlify.app/api/bot/status \
  -H "x-admin-secret: your-secret" | jq .
```

---

# Part 10: Safety & Risk Management

```
┌─────────────────────────────────────────────────────────────┐
│              SAFETY SYSTEMS                                  │
│                                                              │
│  ENTRY PROTECTION:                                           │
│  ✓ Min edge threshold (default 6%)                           │
│  ✓ Price range filter (5¢ - 90¢ only)                       │
│  ✓ Stale analysis filter (>48h = skip)                       │
│  ✓ FOK orders only (never resting limit orders)              │
│  ✓ Max buy price = market + 10% (slippage cap)               │
│  ✓ FOK cooldown (30 min after failure — illiquid market)     │
│  ✓ Post-fill edge validation (warn if slippage ate edge)     │
│                                                              │
│  EXIT PROTECTION:                                            │
│  ✓ Edge-based target pricing with capture ratio              │
│  ✓ Stop-loss at configurable % (default 30%)                 │
│  ✓ Time decay auto-sell after max hold days                  │
│  ✓ AI reversal instant sell (edge flipped negative)          │
│  ✓ Market resolution detection (fair ≥ 95¢)                 │
│  ✓ Sell price floors (never dump on thin order books)        │
│  ✓ Target ratcheting floor (never below midpoint)            │
│                                                              │
│  PORTFOLIO PROTECTION:                                       │
│  ✓ Max positions limit                                       │
│  ✓ Max bets per cycle limit                                  │
│  ✓ Drawdown circuit breaker (auto-disables buying)           │
│  ✓ Cycle lock (prevents concurrent execution)               │
│  ✓ Independent Buy/Sell toggles (sell-only wind-down mode)   │
│                                                              │
│  OPERATIONAL SAFETY:                                         │
│  ✓ Admin secret on all endpoints (constant-time comparison)  │
│  ✓ API credentials derived + stored (never exposed)          │
│  ✓ Private key never leaves server environment               │
│  ✓ Background function with 15-min timeout                   │
│  ✓ Lock auto-expires after 14 min (prevents deadlocks)       │
│  ✓ Force-sell and force-release-lock as emergency controls   │
│                                                              │
│  NEVER:                                                      │
│  ✗ Resting limit orders (heartbeat management is complex)    │
│  ✗ Commit private keys or secrets to git                     │
│  ✗ Set and forget (monitor daily)                            │
│  ✗ Ignore FOK failures (could indicate liquidity problems)   │
│  ✗ Trust the AI blindly (it's probabilistic, not certain)    │
└─────────────────────────────────────────────────────────────┘
```

---

# Part 11: Truth Machine API Reference

The bot relies on one primary endpoint. Free, no auth needed.

## GET /api/best-bets

```
https://truthmachine.live/api/best-bets?limit=50&min_edge=0

Response:
{
  "markets": [
    {
      "slug": "us-cuba-invasion--will-the-us-invade-cuba-in-2026",
      "question": "Will the U.S. invade Cuba in 2026?",
      "side": "NO",
      "ai_probability": 0.04,
      "market_price": 0.26,
      "edge": 0.21,
      "confidence": "high",
      "volume": 150000,
      "category": "politics",
      "analyzed_at": "2026-04-02T12:00:00Z",
      "url": "/market/us-cuba-invasion--will-the-us-invade-cuba-in-2026"
    }
  ],
  "count": 50,
  "generated_at": "2026-04-02T12:15:00Z"
}

Fields:
  slug           Compound slug: "event--market" format
  question       Human-readable market question
  side           "YES" or "NO" — which side the AI says to bet
  ai_probability AI's estimated P(YES), always between 0 and 1
  market_price   Current price of the recommended side (side's token price)
  edge           = |ai_fair_value - market_price| for the recommended side
  confidence     "high", "medium", or "low"
  analyzed_at    ISO timestamp of when AI last analyzed this market
```

## GET /.netlify/functions/get-analysis?slug={slug}

Individual analysis for a specific market (more current than the index):

```
{
  "slug": "...",
  "analysis": { "yesPercent": 4, "noPercent": 96, "confidence": "high", ... },
  "yesPrice": 0.26,
  "noPrice": 0.74,
  ...
}
```

---

# Part 12: Deployment Checklist

```
┌─────────────────────────────────────────────────────────────┐
│  DEPLOYMENT STEPS                                           │
│                                                              │
│  1. Create Netlify site, connect to git repo                │
│  2. Set ALL environment variables in Netlify UI              │
│  3. Deploy (git push)                                        │
│  4. Visit /bot → enter admin secret                          │
│  5. System tab: verify health = "healthy"                    │
│     ├── All env vars: OK                                     │
│     ├── API creds: OK (auto-derived on first cycle)          │
│     ├── CLOB API: reachable                                  │
│     ├── Best-Bets: reachable                                 │
│     ├── USDC balance: > $0                                   │
│     └── POL balance: > 0 (for gas)                           │
│  6. Strategy tab: configure Buy ON, Sell ON                  │
│  7. Trading tab: Run Cycle                                   │
│  8. Watch the live log for the first cycle                    │
│  9. If it buys something: you're live!                       │
│  10. Enable Auto for continuous trading                       │
│                                                              │
│  FIRST TRADE NOTE:                                           │
│  NegRisk markets require a manual trade on polymarket.com    │
│  first to set the exchange allowance. The bot will log       │
│  "NegRisk allowance not set" for these — just trade once     │
│  on the web UI for that market type.                         │
└─────────────────────────────────────────────────────────────┘
```

---

# Part 13: Build Order

When implementing, build in this exact order:

```
Step 1:  types.ts          ← Define all interfaces first
Step 2:  store.ts          ← State persistence (depends on types)
Step 3:  client.ts         ← CLOB client (depends on types, store)
Step 4:  market.ts         ← Market info (depends on types)
Step 5:  cycle.ts          ← Core logic (depends on ALL above)
Step 6:  bot-status.ts     ← Status API (depends on store, client)
Step 7:  bot-sell.ts       ← Force-sell (depends on store, client)
Step 8:  bot-cycle.ts      ← Dispatcher (depends on store)
Step 9:  bot-cycle-bg.ts   ← Background worker (depends on cycle)
Step 10: BotClient.tsx     ← Dashboard UI (depends on all APIs)
```

Each step should be buildable and testable independently.
The bot is fully functional after Step 9 (API-only, no UI).
Step 10 adds the management dashboard.
