# polybot — a Polymarket paper-trading bot (no real money)

`polybot` watches **real, live Polymarket prices** and trades them with **fake money**. It never
places a real order, never asks for a private key, and never needs a Polymarket account. The point
is to find out — cheaply and honestly — whether any trading idea actually makes money before you
risk a cent.

The simulator supports legacy CTF (`v1`) assets and older payloads without a version field.
It skips explicit `v2` and unknown protocol versions: their position identifiers and trading
constraints require a separate integration.

> **Read this first: what "goal is profitability" can and can't mean.**
> Nobody can promise a prediction-market bot is profitable, and this README won't. Most retail
> Polymarket bots lose money, because the easy edges are taken by faster, better-funded bots.
> What this project gives you is a **fair test bench**: it models order-book depth, fees and
> slippage, tracks every result, and has a deliberately hard-to-pass **go-live gate**. If the gate
> says "NEGATIVE", the bot just saved you real money. That *is* the profitable outcome of paper
> trading, and it is a good one.

**Verification, 10 Nov 2026:** 129 offline tests pass. The `run --once` entry point was also
checked against live public APIs using an isolated paper account: 400 events scanned, five
harvest trades, and $999.61 equity after entry costs and marking. No trade had resolved;
this checks operation, not profitability. See the [latest profitability audit](docs/profitability-followup-2026-11-10.md)
for the fixes, historical sample, and remaining limitations. Start with `python -m polybot check`
to verify your own connection.

---

## Table of contents

1. [Quick start (5 minutes)](#1-quick-start-5-minutes)
2. [Do I need a Polymarket account?](#2-do-i-need-a-polymarket-account)
3. [Creating a Polymarket account (optional, for later)](#3-creating-a-polymarket-account-optional-for-later)
4. [Setting up the bot, step by step](#4-setting-up-the-bot-step-by-step)
5. [Running it: every command](#5-running-it-every-command)
6. [The two strategies, honestly explained](#6-the-two-strategies-honestly-explained)
7. [Configuration reference](#7-configuration-reference)
8. [Reading the report and the go-live gate](#8-reading-the-report-and-the-go-live-gate)
9. [Running it 24/7](#9-running-it-247)
10. [Troubleshooting](#10-troubleshooting)
11. [Going live (not built in, on purpose)](#11-going-live-not-built-in-on-purpose)
12. [How the simulation works, and where it's too optimistic](#12-how-the-simulation-works-and-where-its-too-optimistic)
13. [Project layout](#13-project-layout)
14. [Sources](#14-sources)

---

## 1. Quick start (5 minutes)

You need Python 3.11 or newer. Then:

```bash
cd polymarket-bot
python3 -m venv .venv
source .venv/bin/activate            # Windows: see section 4
pip install -r requirements-dev.txt
python -m pytest -q                  # offline regression suite
python -m polybot check              # can I reach Polymarket?
python -m polybot backtest           # does the harvest idea survive history? (takes a few minutes)
python -m polybot scan               # what would the bot trade right now? (trades nothing)
python -m polybot run                # start paper trading (Ctrl+C to stop)
python -m polybot report             # see how you're doing
python -m polybot web                # local dashboard at http://127.0.0.1:8787/
```

Suggested order: `check` → `backtest` → `scan` → `run` for **at least 2–4 weeks** → `report`.

---

## 2. Do I need a Polymarket account?

**No — not for anything in this project.** The bot only reads Polymarket's *public* market-data
endpoints, which need no login and no API key. Your paper account is just a SQLite file on your
computer (`data/paper.db`).

You only need a real account if, after a long successful paper run, you decide to trade real money
(section 11). You can skip section 3 until then. It's included because you asked, and because
reading how the real platform works helps you judge the simulation.

---

## 3. Creating a Polymarket account (optional, for later)

### 3.1 Before you sign up: check that you're allowed to

Polymarket restricts some regions for regulatory reasons. Per its documentation (checked 9 Nov 2026
— **lists change, so re-check**):

- **Fully blocked** (no trading at all): Iran, Syria, Cuba, North Korea, and the Ukrainian regions of
  Crimea, Donetsk and Luhansk.
- **Close-only** (you may close existing positions but not open new ones): Argentina, Australia,
  Belarus, Belgium, Brazil, Burundi, parts of Canada (British Columbia, Ontario, Alberta, Quebec),
  Central African Republic, DR Congo, Ethiopia, France, Germany, Iraq, Italy, Lebanon, Libya,
  Myanmar, New Zealand, Nicaragua, Poland, Russia, Singapore, Slovakia, Somalia, South Sudan,
  Sudan, Taiwan, Thailand, United Kingdom, **United States**, Venezuela, Yemen, Zimbabwe.
  (Ireland, Japan, Malta for sports, the Netherlands and South Korea are close-only in the website
  but not the API.)
- Anywhere not listed is not restricted by that list. That is not legal advice: **you** are
  responsible for what is legal where you live, and for reading Polymarket's Terms of Use.

How to check yourself: open `https://polymarket.com/api/geoblock` in your browser (no login). It
returns your detected `country`, `region` and a `blocked` true/false.

**Do not use a VPN or any trick to get around a restriction.** It can violate Polymarket's terms,
and funds in an account opened that way can end up frozen.

### 3.2 Sign up

Go to **polymarket.com** (type the address yourself; don't click ads or links from messages —
fake Polymarket sites exist) and click **Sign Up**. Polymarket's help center describes three ways in:

| Method | What you do | Notes |
|---|---|---|
| **Google** | Choose Google, connect your Google account, finish the prompts. | Easiest. You'll log in with Google afterwards. |
| **Email** | Enter your email, then type the **6-digit code** sent to it. | You log in the same way each time: email + a fresh code. |
| **Crypto wallet** | Connect MetaMask, Rabby, Phantom or similar, then **sign two messages**: one to connect, one to enable trading. | Choose this only if you already understand wallets and seed phrases. |

**Security rules that actually matter:**

- **Never share the 6-digit login code with anyone**, including "support". Polymarket says it cannot
  recover funds lost through a shared code.
- If you use a wallet, the **seed phrase** is the whole account. Write it on paper, never
  photograph it, never type it into a website or into this bot.
- Turn on two-step protection on the email or Google account you used.

The sign-up page doesn't describe ID verification (KYC). Depending on how you fund the account,
what you withdraw, and your region, identity checks may still appear. Read what the app asks for
and follow Polymarket's official help center rather than third-party blogs, many of which are
affiliate marketing.

### 3.3 Funding (only when you're ready for real money)

Polymarket has its own **"How to Deposit"** article in its help center, and the deposit screen in
the app tells you exactly which asset and network to send. **Follow the app, not a blog or this
README:** sending the wrong asset or using the wrong network can lose the money permanently, and
the supported options change over time. If you ever deposit, start with a test amount you'd be fine
losing entirely.

---

## 4. Setting up the bot, step by step

### 4.1 Install Python 3.11 or newer

Check what you have:

```bash
python3 --version        # macOS / Linux
py --version             # Windows
```

If it prints 3.11 or higher you're set. Otherwise:

- **Windows:** download the installer from **python.org/downloads**. On the first screen tick
  **"Add python.exe to PATH"**, then *Install Now*.
- **macOS:** download from python.org, or `brew install python` if you use Homebrew.
- **Linux (Debian/Ubuntu):** `sudo apt update && sudo apt install python3 python3-venv python3-pip`.
  If your distribution's Python is older than 3.11, use `pyenv` or a newer distribution release.

(The 3.11 minimum is because the config loader uses the built-in `tomllib` module.)

### 4.2 Get the project

Unzip `polymarket-bot.zip` somewhere, then open a terminal *inside* the `polymarket-bot` folder
(Windows: open the folder in File Explorer, click the address bar, type `cmd` or `powershell`, press
Enter).

### 4.3 Create a virtual environment and install

A virtual environment keeps this project's packages separate from the rest of your computer.

**macOS / Linux**
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
```

**Windows (PowerShell)**
```powershell
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements-dev.txt
```
If PowerShell says *"running scripts is disabled on this system"*, run
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once, then try the activate line again.
(Or use Command Prompt: `.venv\Scripts\activate.bat`.)

**Windows (Command Prompt)**
```bat
py -m venv .venv
.venv\Scripts\activate.bat
pip install -r requirements-dev.txt
```

You'll know it worked when your prompt starts with `(.venv)`. Run the activate line again every
time you open a new terminal. On Windows use `python` in place of `python3` for the commands below.

### 4.4 Run the tests

```bash
python -m pytest -q
```
Expected: all tests pass (129 at the audit above). These tests use fake data and need no internet. They check the maths (fees,
order-book fills, arbitrage profit, settlement accounting, risk limits).

### 4.5 Check the connection

```bash
python -m polybot check
```
You want two `[OK]` lines (the trading server and the market-data server). If you see `[FAIL]`, go
to [troubleshooting](#10-troubleshooting).

### 4.6 (Optional) edit the settings

`config.toml` in the project folder holds every setting with its default. You can run without
touching it. If you edit it, a misspelt setting name makes the bot **refuse to start with a clear
error** rather than silently ignore your change. See [section 7](#7-configuration-reference).

---

## 5. Running it: every command

All commands are `python -m polybot <command>`. Add `-v` after `polybot` for debug logging, or
`--config path/to/file.toml` to use another config.

### `check`
Tests connectivity to Polymarket's public servers. Trades nothing.

### `backtest`
Downloads recently *resolved* markets and their price history, then asks: *"when the favourite was
priced at X a few days before the end, how often did it actually win?"* The table shows win rate vs
cost per price bucket, with a 95% confidence bound, and splits the configured price band into an
older and newer half so you can see whether the result is stable. It takes a few minutes (hundreds
of requests, throttled on purpose). **Run this first**: it can tell you in minutes what paper
trading would take weeks to reveal.

### `scan [--limit N]`
One pass over the current market. Prints what each strategy *would* trade right now and trades
nothing. "No signals" is a normal and common result.

### `run [--once] [--cycles N]`
Starts paper trading. Every `poll_seconds` (default 60) it:

1. checks whether any market you hold has resolved and pays out the winners,
2. marks your open positions to the market and saves an equity point,
3. stops opening trades if a circuit breaker has tripped (daily loss or max drawdown),
4. refreshes the list of events (every 5 minutes),
5. asks each strategy for ideas, **refreshes market status, fee schedules, and order books for each idea**, simulates the fill,
   rechecks eligibility and risk limits, and only then "buys". It marks new positions and checks
   loss limits between trades. Missing held-position books block new entries.

Daily halts persist through the UTC day, including restarts. Drawdown halts persist until the
paper account is reset after review. Trading and reset commands share a process lock per database;
stop the bot before resetting. Reports remain available while trading.

`--once` runs a single cycle and exits; `--cycles 5` stops after five. Stop a normal run with
**Ctrl+C**: your account is saved after every trade. Log lines look like:

```
universe refreshed: 312 events, 1874 markets
PAPER TRADE [harvest] Will X happen? -> Yes @ 0.962 (bid 0.955), ends in 3.1d, 24h vol $48,210
skipped [arbitrage] neg_risk_no_basket -> stale: edge vanished on re-quote (...)
cycle done | equity $1,000.12 | cash $990.04 | open positions 1 | signals 2 | executed 1
```

### `web [--host 127.0.0.1] [--port 8787]`
Opens a local dashboard in your browser: equity, the go-live gate, positions, the signal
funnel, scan, backtest, and start/stop for paper trading. There is no login. It listens on
localhost only unless you pass another `--host`. Do not expose it to a network you do not trust:
anyone who can open the page can reset the paper account. It trades fake money only.

### `report [--csv DIR]`
Prints the performance report (explained in [section 8](#8-reading-the-report-and-the-go-live-gate)).
`--csv exports/` also writes `trades.csv`, `settlements.csv` and `equity.csv` for Excel / Google
Sheets. Safe to run while the bot is running.

### `reset [--yes]`
Deletes the paper account and starts over with fresh fake cash. **Do this whenever you change
strategy settings**, otherwise old and new results are mixed and the report means nothing.

---

## 6. The two strategies, honestly explained

### 6.1 Multi-outcome basket arbitrage (`arbitrage`)

In a "neg-risk" event (for example *"Who will win X?"* with a separate Yes/No market per candidate),
**at most one outcome can happen**. So buying **one NO share of every outcome** pays out at least
`n − 1` dollars (`n` if nobody wins). If the whole basket costs less than that, **after fees**, the
profit is locked in whoever wins. The bot sizes the basket by walking the real order books,
subtracts the taker fee for each leg, and only signals if the net profit and annualised return clear
your thresholds.

Honest expectations:

- Real bots hunt for exactly this, in milliseconds. This bot polls once a minute, so **most signals
  will already be gone** when it re-checks. The report's *signal funnel* counts how many ideas went
  `stale`, which measures this directly.
- Where a market charges fees, thin edges are erased. The bot reads each market's published fee
  schedule, and falls back to a 7% rate if it's missing (the highest published standard rate
  checked on 10 Nov 2026; re-check if Polymarket changes its schedule).
- It isn't literally riskless: money is locked until the event resolves, and a market that resolves
  50/50 or is cancelled can break the payoff. Resolution disputes can also take a long time.
- An implausibly large edge (default: over 25%) is treated as bad data and skipped.

### 6.2 Near-resolution favourite harvesting (`harvest`)

Buy the heavy favourite (default ask price 93–98.5¢) in a liquid binary market that ends within two
weeks, hold to resolution, collect $1. A win pays a few cents; a loss costs ~95¢. So it only makes
money if favourites at 95¢ really win **more than ~95% of the time plus the fee**. Many people
believe they do ("picking up pennies"); others find the occasional loss wipes out ~20 wins. **This is
a hypothesis to test, not a proven edge.** That is exactly what `backtest` and the report's
*calibration table* measure.

Filters: minimum volume and liquidity, minimum order-book depth, maximum bid/ask spread, ends no
sooner than 2 hours (avoids last-second resolution traps), keyword exclusions for gimmick markets
("up or down", "tweet", "will say", "mention"), and it never buys the same market twice.

### 6.3 Adding your own strategy

Create a class in `polybot/strategies/` with a `scan(ctx)` method that returns `Signal` objects, add
it to `build_strategies()`, and the engine handles pricing, risk checks, execution and reporting.
Look at `harvest.py` (about 80 lines) as the template. Rule of thumb: add an idea only after you can
say *who is on the other side of the trade and why they're wrong*.

---

## 7. Configuration reference

Everything lives in `config.toml`. The ones you're most likely to change:

| Setting | Default | Meaning |
|---|---|---|
| `broker.starting_cash` | 1000 | Fake dollars to start. Pick roughly what you'd really use. |
| `broker.slippage_buffer` | 0.002 | Extra price paid on every fill, a cushion for being slower than reality. Raise it to be more pessimistic. |
| `broker.fallback_fee_rate` | 0.07 | Taker-fee rate used when a market doesn't publish its schedule. |
| `harvest.stake_usd` | 10 | Dollars per harvest trade. |
| `harvest.min_price` / `max_price` | 0.93 / 0.985 | Ask-price band for favourites. **Don't tune these after looking at results** (overfitting). |
| `harvest.max_days_to_end` | 14 | Shorter = capital turns over faster and results arrive sooner. |
| `arbitrage.max_basket_usd` | 150 | Largest basket per event. |
| `arbitrage.min_net_profit_usd` | 0.50 | Ignore baskets that lock in less than this. |
| `risk.max_daily_loss_usd` | 50 | Stop opening trades for the rest of the UTC day. |
| `risk.max_drawdown_pct` | 0.20 | Stop opening trades after falling 20% from peak equity; needs you to reset/review. |
| `risk.max_total_exposure_pct` | 0.80 | Maximum share of equity in open positions. |
| `risk.min_cash_reserve_usd` | 100 | Cash that's never committed. |
| `general.poll_seconds` | 60 | Pause between cycles. Going much lower mostly hits rate limits. |
| `general.max_days_ahead` | 60 | Ignore events ending later than this. |
| `backtest.days_back` / `max_markets` | 60 / 300 | How much history the backtest examines. |

Set `enabled = false` under `[arbitrage]` or `[harvest]` to run one strategy alone. That's the best
way to judge each one separately.

---

## 8. Reading the report and the go-live gate

`python -m polybot report` has these sections:

- **Equity**: cash + open positions marked at the order-book midpoint, vs your starting cash, plus
  maximum drawdown. Unrealised gains aren't real until a market resolves.
- **Realised**: only resolved markets: trade count, win rate, average win and loss, profit factor,
  and a per-strategy split. **This is the number that matters.**
- **Signal funnel**: what happened to each idea: `executed`, `stale` (edge gone on re-check),
  `rejected_risk` (a risk limit said no), `error`.
- **Open positions**: with days to resolution; flags positions 14+ days past their end date
  (disputed or delayed resolution).
- **Harvest calibration**: win rate vs cost by entry-price bucket, with a 95% lower bound and
  `EV/share` (win rate − cost; positive = favourites win more often than they cost).
- **GO-LIVE GATE**, one of:

| Verdict | Meaning |
|---|---|
| `NOT ENOUGH DATA` | Fewer than 100 resolved harvest trades. Keep running. Nothing is learned yet. |
| `NEGATIVE` | Hit-rate doesn't beat cost, or net P&L ≤ 0. Don't go live. Change the idea, not the thresholds. |
| `PROMISING BUT UNPROVEN` | Beats cost, but the 95% lower bound doesn't. Could be luck. Keep running. |
| `SUPPORTED` | Beats cost at the 95% lower bound, also under the unresolved-position loss check, with no repeated event in the sample. Necessary, **not sufficient**. See section 11. |

The report shows unresolved harvest cost at risk. A settled sample that passes on its own is
downgraded if counting outstanding harvest positions as losses removes support, or if multiple
settled trades share an event. Different events can still be correlated.

How long until there's data? Harvest trades resolve within days, but 100 of them across independent
markets typically takes **several weeks**. Be patient: with a 95%-win-rate strategy, 20 trades
tell you almost nothing, and one loss can erase weeks of small wins.

---

## 9. Running it 24/7

A paper bot only learns while it runs. Prices are read from your own machine, so it must be on.

- **Easiest (Linux/macOS):** `tmux new -s polybot`, activate the venv, run `python -m polybot run`,
  detach with `Ctrl+B` then `D`. Reattach with `tmux attach -t polybot`.
- **Background without tmux:** `nohup python -m polybot run > /dev/null 2>&1 &`. The bot writes its
  own log to `logs/polybot.log`. Stop it with `pkill -f "polybot run"`.
- **Windows:** use Task Scheduler → *Create Task* → trigger *At log on* → action: start
  `.venv\Scripts\python.exe` with arguments `-m polybot run` and "Start in" set to the project
  folder. Or just leave a terminal window open.
- **Cheap always-on:** a small cloud VM or a spare Raspberry Pi works well. Remember Polymarket's
  region rules apply to real trading, not to paper reading, but check the VM's region anyway.

**Only one bot can trade against a database at a time.** CLI and dashboard trading commands reject
a second instance, and reset refuses an account in use. Restart processes from older versions once
to activate this protection. Your laptop sleeping pauses the bot; it resumes when the machine wakes.

Back up `data/paper.db` occasionally. It's your whole history.

---

## 10. Troubleshooting

| Symptom | Likely cause and fix |
|---|---|
| `check` shows `[FAIL] ... ProxyError` or timeouts | Your network, firewall, work/school proxy, or ISP is blocking Polymarket's API hosts (`clob.polymarket.com`, `gamma-api.polymarket.com`). Try another network. Don't bypass regional blocks. (If every request fails, the bot waits and retries with backoff; your paper account is never harmed by an outage.) |
| `check` shows `HTTP 403` | The service refused your network (often a geo/ISP block). Same advice. |
| `check` shows `HTTP 429` | Rate limited. Wait a minute; don't lower `poll_seconds`. |
| `check` is `OK` but `scan` errors with `HTTP 400/422` | The API changed a parameter name. The client already tries alternates, so open an issue with the full message (re-run with `-v`). Docs: `docs.polymarket.com`. |
| `ModuleNotFoundError: requests` | The venv isn't active or you skipped `pip install -r requirements-dev.txt`. |
| `ModuleNotFoundError: tomllib` or syntax errors | Python is older than 3.11. Install a newer one (section 4.1). |
| `Config problem: Unknown setting 'x'` | A typo in `config.toml`. The message lists the valid names. |
| `scan` says "No signals" | Normal. Opportunities are rare and filters are strict on purpose. |
| `report` says "nothing has resolved yet" | Normal at first; markets take days to resolve. |
| Log says `TRADING HALTED` | The daily-loss or max-drawdown breaker tripped. Read the reason, look at `report`, then `reset` if you want to start over. |
| `database is locked` | Two copies are running. Stop one. |
| Many `stale` signals | That *is* the finding: the edge disappears before you could capture it. |
| A position stays open long after its end date | The market's resolution is disputed or delayed on Polymarket. The bot waits until it's fully resolved (prices exactly 0/1, or 50/50). |

Logs: `logs/polybot.log`. For more detail: `python -m polybot -v run`.

---

## 11. Going live (not built in, on purpose)

This repository contains **no live-trading code**: no key handling, no order signing. That's a
safety feature. Moving to real money should be a deliberate, separate step, taken only if **all** of
these hold:

- [ ] `backtest` shows positive EV in both the older and newer halves, with hundreds of observations.
- [ ] `report` says **SUPPORTED**, from at least 100 (ideally several hundred) resolved trades over
      several weeks that included at least one losing streak.
- [ ] You've compared paper fills to reality with a handful of **tiny** manual orders (a few dollars)
      in the Polymarket app: did you really get the price the simulator assumed?
- [ ] Trading is legal for you where you live, and you've read Polymarket's Terms of Use and
      checked `https://polymarket.com/api/geoblock` (section 3.1).
- [ ] You can afford to lose **all** of the money you fund. Start with an amount that would not hurt.

If you then build a live version, use Polymarket's **official** client library and docs
(`docs.polymarket.com`; the package and API versions have changed over time, so don't rely on old
tutorials). Keep keys in environment variables or a secrets manager, never in the repo, check the
geoblock endpoint before sending orders, keep `risk` limits at least as tight as in paper mode, and
use a separate fresh account/wallet funded with only the trading amount.

Also remember: profits may be taxable where you live. Export `trades.csv` and `settlements.csv` for
your own records.

---

## 12. How the simulation works, and where it's too optimistic

**Modelled:**
- Real order books: a buy walks the ask levels, so a bigger order gets a worse average price.
- **Taker fees**: `shares × rate × (p × (1 − p)) ^ exponent`, using each market's published fee
  schedule; zero for fee-free markets; a pessimistic fallback when unknown.
- A configurable slippage buffer on every fill, minimum order size, all-or-nothing fills.
- Fresh order books are fetched *right before* each simulated trade ("re-quote"), so ideas that
  vanished in the meantime are rejected, not magically filled.
- Resolution: positions require a `resolved` UMA status and exact final payouts (1/0, or 50/50).
- Risk limits per market, per event, in total, cash reserve, daily loss and drawdown breakers.
- Basket trades are all-legs-or-nothing in the account; real exchanges can fill only some legs.

**Not modelled (so paper results are an upper bound):**
- Network latency and being beaten to a price by other bots.
- Your own market impact on prices *after* your trade, and partial fills of real orders.
- Resting limit orders and queue position (the bot only does immediate "taker" buys).
- Maker rebates, gas or withdrawal costs, currency conversion.
- Disputed or reversed resolutions (rare, but they exist).
- Behavioural reality: it's easy to follow a bot on fake money and hard on real money.

---

## 13. Project layout

```
polymarket-bot/
├── README.md             this file
├── config.toml           all settings (documented, safe defaults)
├── requirements.txt      runtime dependency (requests)
├── requirements-dev.txt  + pytest
├── polybot/
│   ├── cli.py            commands: check, scan, run, report, backtest, web, reset
│   ├── web.py            local dashboard server
│   ├── dashboard.html    the dashboard page (Tailwind, no login)
│   ├── engine.py         the trading loop (refresh, settle, mark, scan, execute)
│   ├── client.py         read-only Polymarket API client (retries, throttling, pagination)
│   ├── models.py         parsing of API data (markets, events, order books)
│   ├── broker.py         order-book-walking fill simulator
│   ├── fees.py           taker-fee model
│   ├── risk.py           position limits and circuit breakers
│   ├── storage.py        SQLite account, positions, trades, settlements, equity
│   ├── stats.py          confidence intervals, buckets, go-live verdict
│   ├── backtest.py       calibration backtest on resolved markets
│   ├── reporting.py      report text and CSV export
│   ├── config.py         settings with typo detection
│   └── strategies/       base.py, arbitrage.py, harvest.py
├── tests/                offline regression tests (no internet needed)
├── data/                 your paper account (created on first run)
└── logs/                 polybot.log (created on first run)
```

---

## 14. Sources

Facts about Polymarket in this README come from Polymarket's own documentation and help center, read
on 9 Nov 2026. Rules, fees and endpoints change, so re-check before relying on any of them:

- Sign-up methods: [Polymarket Help Center: How to sign up](https://help.polymarket.com/en/articles/13369877-how-to-sign-up)
- Blocked and close-only regions, geoblock endpoint: [Polymarket docs: Geoblock](https://docs.polymarket.com/api-reference/geoblock)
- Fee formula and schedules: [Polymarket docs: Fees](https://docs.polymarket.com/trading/fees)
- Market data and order books: [Polymarket docs](https://docs.polymarket.com) (Gamma API for events/markets, CLOB API for order books and price history)

*This project is educational software, not financial, legal or tax advice. Prediction-market trading
can lose money.*
