Trending-skills nothing-ever-happens-polymarket-bot

Async Python bot for Polymarket that buys "No" on all standalone non-sports yes/no markets using the "nothing ever happens" strategy.

install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/nothing-ever-happens-polymarket-bot" ~/.claude/skills/aradotso-trending-skills-nothing-ever-happens-polymarket-bot && rm -rf "$T"
manifest: skills/nothing-ever-happens-polymarket-bot/SKILL.md
source content

Nothing Ever Happens Polymarket Bot

Skill by ara.so — Daily 2026 Skills collection.

An async Python bot that scans Polymarket standalone non-sports yes/no markets and buys the "No" outcome on any market where the NO price is below a configured cap. Designed for paper trading by default with explicit opt-in to live order transmission.


What It Does

  • Scans Polymarket for standalone (non-grouped) yes/no markets
  • Filters out sports markets
  • Buys NO positions when NO price is below
    max_no_price
    threshold
  • Tracks open positions and persists recovery state to a database
  • Exposes a live dashboard (HTTP) showing portfolio and activity
  • Uses
    PaperExchangeClient
    unless all three live-mode env vars are set

Installation

git clone https://github.com/sterlingcrispin/nothing-ever-happens.git
cd nothing-ever-happens
pip install -r requirements.txt
cp config.example.json config.json
cp .env.example .env

Configuration

config.json
(non-secret runtime settings)

Edit the

strategies.nothing_happens
block:

{
  "strategies": {
    "nothing_happens": {
      "max_no_price": 0.08,
      "order_size_usdc": 1.0,
      "max_open_positions": 50,
      "scan_interval_seconds": 60,
      "min_liquidity_usdc": 100,
      "exclude_keywords": ["sport", "nfl", "nba", "mlb", "nhl", "soccer", "tennis"]
    }
  }
}

Point to a different config file:

CONFIG_PATH=/path/to/other_config.json python -m bot.main

.env
(secrets and runtime flags)

# Safety flags — ALL three required for live order transmission
BOT_MODE=paper               # set to "live" for real orders
DRY_RUN=true                 # set to "false" for real orders
LIVE_TRADING_ENABLED=false   # set to "true" for real orders

# Required only in live mode
PRIVATE_KEY=$PRIVATE_KEY
FUNDER_ADDRESS=$FUNDER_ADDRESS
DATABASE_URL=$DATABASE_URL
POLYGON_RPC_URL=$POLYGON_RPC_URL

# Optional
PORT=8080
DASHBOARD_PORT=8080

Never hardcode secrets. Always use environment variable references.


Running Locally

# Paper trading (default — safe, no real orders)
python -m bot.main

# Enable live trading (requires all three flags)
BOT_MODE=live DRY_RUN=false LIVE_TRADING_ENABLED=true python -m bot.main

The dashboard will bind to

$PORT
or
$DASHBOARD_PORT
when set.


Project Structure

bot/
  main.py               # Entry point — starts the runtime loop
  strategies/
    nothing_happens.py  # Core strategy: scan, filter, buy NO
  exchange/
    client.py           # Live exchange client (Polymarket CLOB API)
    paper_client.py     # PaperExchangeClient for safe simulation
  dashboard.py          # HTTP dashboard server
  recovery.py           # Persist and restore open positions

scripts/
  db_stats.py           # Inspect DB table counts and recent activity
  export_db.py          # Export live DB tables
  wallet_history.py     # Positions, trades, balances for configured wallet
  parse_logs.py         # Convert Heroku JSON logs to readable output

tests/                  # Unit and regression tests

Key Commands

Run Tests

python -m pytest -q

Operational Scripts

# Inspect live database
python scripts/db_stats.py

# Export database tables (uses DATABASE_URL or Heroku app)
python scripts/export_db.py

# Pull wallet history
python scripts/wallet_history.py

# Parse Heroku JSON logs into readable output
python scripts/parse_logs.py

Heroku Deployment

One-time setup

export HEROKU_APP_NAME=my-polymarket-bot

# Set runtime flags
heroku config:set BOT_MODE=live DRY_RUN=false LIVE_TRADING_ENABLED=true \
  -a "$HEROKU_APP_NAME"

# Set secrets
heroku config:set \
  PRIVATE_KEY="$PRIVATE_KEY" \
  FUNDER_ADDRESS="$FUNDER_ADDRESS" \
  POLYGON_RPC_URL="$POLYGON_RPC_URL" \
  DATABASE_URL="$DATABASE_URL" \
  -a "$HEROKU_APP_NAME"

# Deploy
git push heroku main:main

# Scale — ONLY the web dyno
heroku ps:scale web=1 worker=0 -a "$HEROKU_APP_NAME"

Do not run the

worker
dyno. It exists only to fail fast if started accidentally.

Shell helpers

./alive.sh              # Check if the app is alive
./logs.sh               # Tail logs
./live_enabled.sh       # Enable live trading
./live_disabled.sh      # Disable live trading (back to paper)
./kill.sh               # Stop the bot

All helpers use

$HEROKU_APP_NAME
or accept an app name as an argument:

./logs.sh my-polymarket-bot

Safety Model

The bot defaults to

PaperExchangeClient
(simulated orders, no real money) unless all three of these are set:

VariableRequired value
BOT_MODE
live
LIVE_TRADING_ENABLED
true
DRY_RUN
false

If any one is missing or wrong, paper mode is used. This is intentional — it makes accidental live trading very hard.

Additional live-mode requirements:

  • PRIVATE_KEY
    — wallet private key
  • FUNDER_ADDRESS
    — required for signature types
    1
    and
    2
  • DATABASE_URL
    — for recovery state persistence
  • POLYGON_RPC_URL
    — for proxy-wallet approvals and redemption

Code Examples

Check if bot will use live or paper mode

import os

def is_live_mode() -> bool:
    return (
        os.getenv("BOT_MODE") == "live"
        and os.getenv("LIVE_TRADING_ENABLED", "").lower() == "true"
        and os.getenv("DRY_RUN", "true").lower() == "false"
    )

print("Live mode:", is_live_mode())

Load strategy config

import json, os

config_path = os.getenv("CONFIG_PATH", "config.json")
with open(config_path) as f:
    config = json.load(f)

strategy_cfg = config["strategies"]["nothing_happens"]
max_no_price = strategy_cfg["max_no_price"]       # e.g. 0.08
order_size   = strategy_cfg["order_size_usdc"]    # e.g. 1.0
print(f"Will buy NO when price <= {max_no_price} USDC, size={order_size} USDC")

Run the bot programmatically

import asyncio
from bot.main import main

asyncio.run(main())

Inspect DB stats

DATABASE_URL=$DATABASE_URL python scripts/db_stats.py

Parse Heroku logs to HTML

heroku logs --num=1500 -a "$HEROKU_APP_NAME" | python scripts/parse_logs.py --html > report.html

Export DB from a Heroku app

python scripts/export_db.py --app "$HEROKU_APP_NAME"

Common Patterns

Disabling live trading quickly (kill switch)

heroku config:set LIVE_TRADING_ENABLED=false -a "$HEROKU_APP_NAME"
# or use the helper:
./live_disabled.sh

The bot will immediately fall back to paper mode on next cycle without a restart.

Adjusting the NO price cap without redeploying

Edit

config.json
, commit, and push:

# Lower the cap to only buy very cheap NOs
jq '.strategies.nothing_happens.max_no_price = 0.05' config.json > tmp.json && mv tmp.json config.json
git add config.json && git commit -m "lower max_no_price to 0.05"
git push heroku main:main

Adding more keyword exclusions

{
  "strategies": {
    "nothing_happens": {
      "exclude_keywords": ["sport", "nfl", "nba", "mlb", "nhl", "soccer", "tennis", "golf", "ufc", "esport"]
    }
  }
}

Troubleshooting

SymptomLikely CauseFix
Bot uses paper mode unexpectedlyOne of the three live flags is missing/wrongCheck all three:
BOT_MODE
,
LIVE_TRADING_ENABLED
,
DRY_RUN
DATABASE_URL
errors in live mode
Postgres not provisioned
heroku addons:create heroku-postgresql -a "$HEROKU_APP_NAME"
Orders never transmit
DRY_RUN
still
true
heroku config:set DRY_RUN=false
Dashboard not accessible
PORT
not set
heroku config:set PORT=8080
Worker dyno crashes immediatelyDon't run worker dyno
heroku ps:scale worker=0
Bot buys sports marketsKeywords not in
exclude_keywords
Add sport/league names to config
Logs unreadable (JSON)Heroku structured loggingPipe through
scripts/parse_logs.py

Disclaimer

FOR ENTERTAINMENT ONLY. PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. USE AT YOUR OWN RISK. NOT FINANCIAL ADVICE.