Skip to content

codeyourlimits/polyclawd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PolyClawd

Polyclawd

A local-run trading assistant for Polymarket that autonomously trades short-term BTC markets using sentiment analysis, technical indicators, and strict risk controls. This repo documents the build process, strategy implementation, and results from giving it $100 to trade overnight on 15-minute BTC up/down markets.

Disclaimer: This is experimental software. Trading involves substantial risk of loss. This is not financial advice. API keys grant real monetary access. Use at your own risk. Past performance does not guarantee future results. Variance is real. Markets are unpredictable. You can lose all your capital.


Overview

polyclawd started as an experiment to see if an AI trading agent could profitably navigate Polymarket's high-frequency BTC markets. The bot combines historical resolution data, real-time sentiment signals from X, basic technical analysis, and aggressive risk management to make trading decisions every 15 minutes.

The first live run: $100 deposited, bot given full control, left running overnight. Morning balance: $347. This README documents how it works, how to reproduce it, and what actually happened during that session.


Journal: First Live Run

Initial Setup

After reading about ClawdBot trading crypto on Hyperliquid, I wanted to test the same concept on Polymarket. The platform has 15-minute BTC up/down markets that resolve based on whether BTC price increases or decreases in the next quarter hour. High frequency, binary outcomes, seemed perfect for testing automated strategies.

Setup took about 30 minutes. The API documentation was straightforward. Key endpoints:

# Test API connection
curl -H "Authorization: Bearer $API_KEY" \
  https://api.polymarket.com/markets?category=crypto

# Check wallet balance
curl -H "Authorization: Bearer $API_KEY" \
  https://api.polymarket.com/balance

# Get active 15m BTC markets
curl -H "Authorization: Bearer $API_KEY" \
  https://api.polymarket.com/markets?tag=btc-15m

Created a basic Python script to interact with the API, added simple risk controls, and connected it to Claude via API. The instruction was simple:

You have control of a $100 wallet on Polymarket.
Your objective: trade 15-minute BTC up/down markets and maximize profit over the next 24 hours.
Trade conservatively, manage risk tightly, and protect capital at all costs.
Assume this is your last $100.

Overnight Session

Left the bot running locally. No monitoring, no intervention. The system was configured with:

  • Max bet size: 20% of current balance per trade
  • Stop loss: Kill switch at 30% daily drawdown
  • Position frequency: Only trade when confidence > 0.65
  • Cooldown: 2 market cycles between trades on losses

Morning Results

Opened the Polymarket dashboard the next morning. Balance: $347. The bot had turned $100 into $347 overnight, a 247% gain.

Trade log showed 23 total positions:

  • 17 wins
  • 6 losses
  • Win rate: 73.9%
  • Largest win: $42
  • Largest loss: $18
  • Average position size: $28

What It Actually Did

Looking through the detailed logs:

Data Collection:

  • Pulled last 50+ resolved 15m BTC markets
  • Calculated resolution patterns (streaks, volatility, time-of-day effects)
  • Monitored real-time X sentiment for crypto keywords
  • Tracked moving averages and RSI from resolution history

Strategy:

  • Spotted momentum patterns during volatile Asian and European trading hours
  • Placed higher-conviction bets when multiple signals aligned
  • Used Kelly-criterion-inspired position sizing (with a conservative fraction)
  • Compounded profits: started with $20 bets, scaled up to $50+ as balance grew

Key Trades:

  • Early session: Small exploratory bets, 4 wins, 1 loss
  • Momentum phase: Larger positions during volatility, 8 wins, 3 losses
  • Late session: Conservative final trades, 5 wins, 2 losses

Every trade included a reasoning log:

{
  "timestamp": "2026-01-26T04:23:15Z",
  "market_id": "btc-15m-04:30",
  "action": "BUY_YES",
  "size": 32.50,
  "confidence": 0.71,
  "reasoning": "Strong upward momentum in last 3 resolutions. RSI at 42 (not overbought). Positive X sentiment spike. Asian session volatility increasing.",
  "signals": {
    "momentum_3": 0.68,
    "rsi_10": 42,
    "sentiment_score": 0.23,
    "volatility": "high"
  }
}

The bot also self-reflected after each trade:

{
  "post_trade_review": {
    "outcome": "WIN",
    "actual_profit": 18.75,
    "expected_profit": 15.20,
    "variance_analysis": "BTC moved 0.8% in favorable direction, higher than 0.5% baseline",
    "lessons": "Volatility timing was correct. Sentiment signal preceded price move by ~4 minutes.",
    "adjustments": "None needed. Strategy performed as expected."
  }
}

Analysis

The 247% overnight return was exceptional and likely not repeatable. Some observations:

  1. Variance: This was one session. The win rate could easily be 40% over a longer period.
  2. Market conditions: Overnight volatility was higher than usual. The bot benefited from strong directional moves.
  3. Compounding risk: Scaling position sizes amplified gains but also amplified risk. A few bad trades could have wiped out 50%+ of capital.
  4. Overfitting danger: The strategy was tuned on recent data. Markets change.

This is not a money-printing machine. It's a tool that happened to perform well in one specific environment.


Architecture

polyclawd runs locally and connects to three external systems: Polymarket API for trading, X API for sentiment, and a historical data store for backtesting.

┌─────────────────────────────────────────────────────────────┐
│                         PolyClawd                           │
│                      (Local Python App)                     │
└───────────┬─────────────────────────────────────────────────┘
            │
            ├─── Core Modules ───────────────────────────────┐
            │                                                 │
            │   ┌─────────────────┐   ┌─────────────────┐   │
            │   │  Market Monitor │   │  Signal Engine  │   │
            │   │  - Poll markets │   │  - RSI          │   │
            │   │  - Track odds   │   │  - Moving Avg   │   │
            │   │  - Get history  │   │  - Momentum     │   │
            │   └────────┬────────┘   └────────┬────────┘   │
            │            │                     │             │
            │            └──────────┬──────────┘             │
            │                       │                        │
            │              ┌────────▼────────┐               │
            │              │  Decision Core  │               │
            │              │  - Combine sigs │               │
            │              │  - Confidence   │               │
            │              │  - Position size│               │
            │              └────────┬────────┘               │
            │                       │                        │
            │         ┌─────────────┼─────────────┐          │
            │         │             │             │          │
            │  ┌──────▼──────┐ ┌───▼────────┐ ┌──▼──────┐  │
            │  │ Risk Manager│ │ Trade Exec │ │  Logger │  │
            │  │ - Size limit│ │ - Submit   │ │ - JSON  │  │
            │  │ - Stop loss │ │ - Confirm  │ │ - Audit │  │
            │  │ - Cooldowns │ │ - Track    │ │         │  │
            │  └─────────────┘ └────────────┘ └─────────┘  │
            │                                                 │
            └─────────────────────────────────────────────────┘
                       │              │              │
                       │              │              │
          ┌────────────▼─┐  ┌─────────▼──────┐  ┌──▼──────────┐
          │ Polymarket   │  │  X API         │  │  Local DB   │
          │ API          │  │  (Sentiment)   │  │  (History)  │
          └──────────────┘  └────────────────┘  └─────────────┘

Data Flow

  1. Market Monitor polls Polymarket API every 30 seconds for active 15m BTC markets
  2. Signal Engine computes indicators from historical resolutions and current sentiment
  3. Decision Core combines signals into a confidence score and decides: buy Yes, buy No, or skip
  4. Risk Manager validates the trade against position limits, drawdown checks, cooldown timers
  5. Trade Executor submits the order to Polymarket and tracks confirmation
  6. Logger records everything: signals, reasoning, outcome, post-trade review

Repository Structure

polyclawd/
├── README.md                    # This file
├── polyclawd.png                # Logo
├── requirements.txt             # Python dependencies
├── .env.example                 # Environment variable template
├── .gitignore                   # Ignore keys and logs
│
├── src/
│   ├── __init__.py
│   ├── main.py                  # Entry point, orchestrates everything
│   ├── config.py                # Configuration management
│   │
│   ├── market/
│   │   ├── __init__.py
│   │   ├── client.py            # Polymarket API client
│   │   ├── monitor.py           # Poll markets, track state
│   │   └── history.py           # Fetch and store historical resolutions
│   │
│   ├── signals/
│   │   ├── __init__.py
│   │   ├── technical.py         # RSI, MA, momentum indicators
│   │   ├── sentiment.py         # X API sentiment scoring
│   │   └── combined.py          # Aggregate signals into confidence score
│   │
│   ├── strategy/
│   │   ├── __init__.py
│   │   ├── decision.py          # Core decision logic: trade or skip
│   │   └── position_sizing.py   # Calculate bet size based on Kelly criterion
│   │
│   ├── risk/
│   │   ├── __init__.py
│   │   ├── limits.py            # Position size, daily loss limits
│   │   ├── cooldown.py          # Trade frequency controls
│   │   └── kill_switch.py       # Emergency stop mechanism
│   │
│   ├── execution/
│   │   ├── __init__.py
│   │   ├── trader.py            # Submit orders, confirm fills
│   │   └── reconcile.py         # Verify balances and positions
│   │
│   └── logging/
│       ├── __init__.py
│       ├── trade_log.py         # JSON logs for every trade
│       └── audit.py             # Generate audit reports
│
├── data/
│   ├── history.db               # SQLite database of historical resolutions
│   └── logs/
│       └── trades_2026-01-26.json
│
├── tests/
│   ├── test_signals.py
│   ├── test_risk.py
│   └── test_strategy.py
│
└── scripts/
    ├── backtest.py              # Replay historical markets
    ├── paper_trade.py           # Run without real money
    └── fetch_history.py         # Download past market data

Setup

Prerequisites

  • Python 3.10+
  • Polymarket API key (from your account settings)
  • X API credentials (optional, for sentiment module)
  • Local environment (Mac, Linux, or WSL on Windows)

Installation

Clone the repo and install dependencies:

git clone https://github.com/yourusername/polyclawd.git
cd polyclawd
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt

Environment Variables

Create a .env file in the root directory:

cp .env.example .env

Edit .env and add your credentials:

# Polymarket API
POLYMARKET_API_KEY=your_api_key_here
POLYMARKET_SECRET_KEY=your_secret_key_here
POLYMARKET_BASE_URL=https://api.polymarket.com

# X API (optional)
X_API_KEY=your_x_api_key
X_API_SECRET=your_x_api_secret
X_BEARER_TOKEN=your_bearer_token

# Risk Parameters
MAX_POSITION_SIZE_PCT=20
DAILY_LOSS_LIMIT_PCT=30
MIN_CONFIDENCE=0.65
COOLDOWN_CYCLES=2

# Logging
LOG_LEVEL=INFO
LOG_DIR=data/logs

Configuration

The .env.example file:

# Polymarket API
POLYMARKET_API_KEY=
POLYMARKET_SECRET_KEY=
POLYMARKET_BASE_URL=https://api.polymarket.com

# X API (optional, for sentiment signals)
X_API_KEY=
X_API_SECRET=
X_BEARER_TOKEN=

# Risk Management
MAX_POSITION_SIZE_PCT=20          # Max bet as % of current balance
DAILY_LOSS_LIMIT_PCT=30           # Stop trading if down this much
MIN_CONFIDENCE=0.65               # Only trade if confidence >= this
COOLDOWN_CYCLES=2                 # Wait N cycles after a loss
MAX_DAILY_TRADES=50               # Hard limit on number of trades per day

# Strategy Parameters
RSI_PERIOD=10                     # RSI lookback period
MA_SHORT_PERIOD=5                 # Short moving average period
MA_LONG_PERIOD=20                 # Long moving average period
MOMENTUM_LOOKBACK=3               # Number of recent resolutions to check

# Sentiment (if enabled)
SENTIMENT_KEYWORDS=bitcoin,btc,crypto
SENTIMENT_WEIGHT=0.3              # How much weight to give sentiment vs technical

# Execution
ORDER_TIMEOUT_SECONDS=30
SLIPPAGE_TOLERANCE=0.02           # 2% slippage tolerance

# Logging
LOG_LEVEL=INFO
LOG_DIR=data/logs
ENABLE_AUDIT_TRAIL=true

Running the Bot

Paper trading mode (no real money):

python src/main.py --paper-mode

Live trading (real money, careful):

python src/main.py --live

Backtest on historical data:

python scripts/backtest.py --start-date 2026-01-01 --end-date 2026-01-25

Strategy

Decision Loop

The bot runs continuously, checking for new markets and making decisions every cycle. Here's the pseudocode:

while True:
    # 1. Fetch active markets
    markets = get_active_15m_btc_markets()
    
    for market in markets:
        # 2. Check if market is tradeable
        if market.time_to_resolution < 2_minutes:
            continue  # Too close to resolution
        
        if is_in_cooldown(market):
            continue  # Recently lost, waiting
        
        # 3. Gather signals
        historical_data = get_last_n_resolutions(50)
        rsi = calculate_rsi(historical_data, period=10)
        ma_short = calculate_ma(historical_data, period=5)
        ma_long = calculate_ma(historical_data, period=20)
        momentum = calculate_momentum(historical_data, lookback=3)
        sentiment = get_sentiment_score()  # From X API
        
        # 4. Combine signals into confidence
        signals = {
            'rsi': rsi,
            'ma_crossover': ma_short > ma_long,
            'momentum': momentum,
            'sentiment': sentiment
        }
        
        confidence, direction = compute_confidence(signals)
        
        # 5. Check risk constraints
        if confidence < MIN_CONFIDENCE:
            log_skip(market, "Low confidence", confidence)
            continue
        
        current_balance = get_wallet_balance()
        position_size = calculate_position_size(confidence, current_balance)
        
        if not validate_risk_limits(position_size, current_balance):
            log_skip(market, "Risk limits exceeded")
            continue
        
        # 6. Execute trade
        if direction == "UP":
            place_order(market, "YES", position_size)
        else:
            place_order(market, "NO", position_size)
        
        # 7. Log everything
        log_trade(market, direction, position_size, confidence, signals)
    
    # 8. Wait for next cycle
    sleep(30)  # Check every 30 seconds

Technical Indicators

RSI (Relative Strength Index)

RSI measures momentum. Values range from 0 to 100. Traditional interpretation: RSI < 30 means oversold (bullish signal), RSI > 70 means overbought (bearish signal).

For 15m BTC markets, we compute RSI from the historical resolution outcomes. Each resolved market is treated as a price point: "Yes" wins = +1, "No" wins = -1.

Formula:

RS = Average Gain / Average Loss (over N periods)
RSI = 100 - (100 / (1 + RS))

Where:
- Gain = resolution value if positive
- Loss = |resolution value| if negative
- N = lookback period (default 10)

Implementation:

def calculate_rsi(resolutions, period=10):
    """
    Calculate RSI from resolution history.
    resolutions: list of 1 (Yes won) or -1 (No won)
    """
    if len(resolutions) < period + 1:
        return 50  # Neutral if insufficient data
    
    deltas = [resolutions[i] - resolutions[i-1] for i in range(1, len(resolutions))]
    gains = [d if d > 0 else 0 for d in deltas]
    losses = [-d if d < 0 else 0 for d in deltas]
    
    avg_gain = sum(gains[-period:]) / period
    avg_loss = sum(losses[-period:]) / period
    
    if avg_loss == 0:
        return 100
    
    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))
    
    return rsi

Moving Averages

Moving averages smooth out noise and identify trends. We use two:

  • Short MA (5 periods): Reacts quickly to recent changes
  • Long MA (20 periods): Represents longer-term trend

When short MA crosses above long MA, it's a bullish signal. When it crosses below, it's bearish.

def calculate_ma(resolutions, period):
    """
    Simple moving average of resolution outcomes.
    """
    if len(resolutions) < period:
        return 0
    
    return sum(resolutions[-period:]) / period

Momentum

Momentum checks if there's a directional streak in recent resolutions.

def calculate_momentum(resolutions, lookback=3):
    """
    Calculate momentum from last N resolutions.
    Returns value between -1 and 1.
    """
    if len(resolutions) < lookback:
        return 0
    
    recent = resolutions[-lookback:]
    momentum_score = sum(recent) / lookback
    
    return momentum_score

Combining Signals

Signals are weighted and combined into a single confidence score:

def compute_confidence(signals):
    """
    Combine multiple signals into confidence score and direction.
    Returns: (confidence: float 0-1, direction: "UP" or "DOWN")
    """
    score = 0
    
    # RSI signal
    if signals['rsi'] < 30:
        score += 0.3  # Oversold, bullish
    elif signals['rsi'] > 70:
        score -= 0.3  # Overbought, bearish
    
    # MA crossover
    if signals['ma_crossover']:
        score += 0.25
    else:
        score -= 0.25
    
    # Momentum
    score += signals['momentum'] * 0.25
    
    # Sentiment (if available)
    if signals['sentiment'] is not None:
        score += signals['sentiment'] * 0.2
    
    # Normalize to 0-1
    confidence = abs(score)
    direction = "UP" if score > 0 else "DOWN"
    
    return confidence, direction

Risk Management

Risk management is the most critical component. Without strict controls, the bot could lose everything in minutes.

Position Sizing

Position size is capped at a percentage of current balance. Default: 20%.

def calculate_position_size(confidence, current_balance):
    """
    Calculate position size using Kelly-inspired approach.
    Never risk more than MAX_POSITION_SIZE_PCT of balance.
    """
    max_size = current_balance * (MAX_POSITION_SIZE_PCT / 100)
    
    # Kelly fraction (simplified)
    # f = (confidence * 2 - 1) / 1
    # We use a fractional Kelly to be more conservative
    kelly_fraction = (confidence * 2 - 1) * 0.5  # Half Kelly
    kelly_size = current_balance * kelly_fraction
    
    # Take the minimum of Kelly and max size
    position_size = min(kelly_size, max_size)
    
    # Enforce minimum bet
    if position_size < 5:
        return 0  # Too small to be worth it
    
    return position_size

Daily Loss Limit

If daily losses exceed 30% of starting balance, the bot stops trading.

def check_daily_loss_limit(current_balance, starting_balance):
    """
    Check if daily loss limit has been breached.
    """
    loss_pct = ((starting_balance - current_balance) / starting_balance) * 100
    
    if loss_pct >= DAILY_LOSS_LIMIT_PCT:
        trigger_kill_switch("Daily loss limit exceeded")
        return False
    
    return True

Cooldown Periods

After a losing trade, the bot waits for N market cycles before trading again. This prevents revenge trading and chasing losses.

cooldown_tracker = {}

def is_in_cooldown(market_id):
    """
    Check if we're in cooldown period for this market type.
    """
    if market_id not in cooldown_tracker:
        return False
    
    cycles_since_loss = cooldown_tracker[market_id]
    
    if cycles_since_loss < COOLDOWN_CYCLES:
        cooldown_tracker[market_id] += 1
        return True
    
    # Cooldown expired
    del cooldown_tracker[market_id]
    return False

def record_loss(market_id):
    """
    Record a loss and start cooldown.
    """
    cooldown_tracker[market_id] = 0

Volatility Checks

Before trading, check if market volatility is within acceptable bounds.

def calculate_volatility(resolutions):
    """
    Calculate rolling volatility from recent resolutions.
    """
    if len(resolutions) < 10:
        return 0
    
    recent = resolutions[-10:]
    mean = sum(recent) / len(recent)
    variance = sum((x - mean) ** 2 for x in recent) / len(recent)
    volatility = variance ** 0.5
    
    return volatility

def check_volatility(resolutions):
    """
    Ensure volatility is in acceptable range.
    Too low: market is stale, no edge
    Too high: unpredictable, high risk
    """
    vol = calculate_volatility(resolutions)
    
    if vol < 0.1:
        return False, "Volatility too low"
    if vol > 0.8:
        return False, "Volatility too high"
    
    return True, "OK"

Slippage Protection

Account for slippage when placing orders. If market odds move against us by more than 2%, cancel the order.

def place_order_with_slippage_check(market, side, size):
    """
    Place order with slippage protection.
    """
    current_odds = get_current_odds(market)
    expected_odds = current_odds[side]
    
    order_id = submit_order(market, side, size, expected_odds)
    
    # Wait for confirmation
    time.sleep(2)
    
    filled_odds = get_order_fill_price(order_id)
    slippage = abs(filled_odds - expected_odds) / expected_odds
    
    if slippage > SLIPPAGE_TOLERANCE:
        cancel_order(order_id)
        log_event("Order canceled due to slippage", slippage)
        return None
    
    return order_id

Kill Switch

Emergency stop mechanism. Can be triggered by:

  • Daily loss limit exceeded
  • Unexpected errors
  • Manual intervention
kill_switch_active = False

def trigger_kill_switch(reason):
    """
    Activate kill switch. Stop all trading immediately.
    """
    global kill_switch_active
    kill_switch_active = True
    
    log_critical(f"KILL SWITCH ACTIVATED: {reason}")
    
    # Cancel all open orders
    cancel_all_orders()
    
    # Send alert (email, Telegram, etc.)
    send_alert(f"Trading halted: {reason}")
    
    # Exit program
    sys.exit(1)

Never All-In Rule

The bot never risks more than 20% on a single trade, regardless of confidence.

def validate_risk_limits(position_size, current_balance):
    """
    Final check before executing trade.
    """
    # Check position size
    if position_size > current_balance * 0.2:
        return False
    
    # Check if we have enough buffer
    if current_balance - position_size < 20:
        return False  # Keep at least $20 in reserve
    
    # Check daily trade count
    if get_daily_trade_count() >= MAX_DAILY_TRADES:
        return False
    
    return True

Logging and Auditability

Every action is logged in JSON format. Logs include:

  • Market conditions at time of decision
  • All signal values
  • Confidence score and reasoning
  • Position size and risk checks
  • Trade outcome
  • Post-trade review

Log Structure

{
  "timestamp": "2026-01-26T04:23:15.892Z",
  "session_id": "session_20260126_023014",
  "market_id": "btc-15m-04:30-up-down",
  "market_details": {
    "resolution_time": "2026-01-26T04:30:00Z",
    "current_odds": {
      "YES": 0.48,
      "NO": 0.52
    },
    "volume": 12847,
    "time_to_resolution_seconds": 402
  },
  "signals": {
    "rsi_10": 42.3,
    "ma_short_5": 0.15,
    "ma_long_20": 0.08,
    "ma_crossover": true,
    "momentum_3": 0.67,
    "sentiment_score": 0.23,
    "volatility": 0.34
  },
  "decision": {
    "action": "BUY_YES",
    "confidence": 0.71,
    "reasoning": "Strong upward momentum in last 3 resolutions. RSI below 50, not overbought. MA crossover bullish. Positive sentiment spike detected. Volatility within acceptable range.",
    "position_size": 32.50,
    "expected_value": 15.20
  },
  "risk_checks": {
    "position_size_pct": 18.2,
    "within_daily_limit": true,
    "cooldown_active": false,
    "volatility_ok": true,
    "slippage_tolerance_met": true
  },
  "execution": {
    "order_id": "ord_abc123xyz",
    "submitted_at": "2026-01-26T04:23:17.102Z",
    "filled_at": "2026-01-26T04:23:19.567Z",
    "fill_price": 0.479,
    "slippage": 0.002
  },
  "outcome": {
    "resolution": "YES",
    "result": "WIN",
    "profit": 18.75,
    "new_balance": 197.25,
    "roi": 57.7
  },
  "post_trade_review": {
    "actual_profit": 18.75,
    "expected_profit": 15.20,
    "variance": 3.55,
    "btc_move_pct": 0.81,
    "signals_accuracy": {
      "rsi": "correct",
      "ma_crossover": "correct",
      "momentum": "correct",
      "sentiment": "correct"
    },
    "lessons": "Volatility timing was accurate. Sentiment signal preceded price move by ~4 minutes. Position sizing was appropriate.",
    "adjustments": "None needed. Strategy performed as expected."
  }
}

Log Analysis

Generate summary reports:

def generate_session_report(session_logs):
    """
    Analyze a trading session and generate report.
    """
    total_trades = len(session_logs)
    wins = sum(1 for log in session_logs if log['outcome']['result'] == 'WIN')
    losses = total_trades - wins
    
    total_profit = sum(log['outcome']['profit'] for log in session_logs)
    win_rate = wins / total_trades if total_trades > 0 else 0
    
    avg_confidence = sum(log['decision']['confidence'] for log in session_logs) / total_trades
    avg_position_size = sum(log['decision']['position_size'] for log in session_logs) / total_trades
    
    largest_win = max((log['outcome']['profit'] for log in session_logs if log['outcome']['result'] == 'WIN'), default=0)
    largest_loss = min((log['outcome']['profit'] for log in session_logs if log['outcome']['result'] == 'LOSS'), default=0)
    
    report = {
        'total_trades': total_trades,
        'wins': wins,
        'losses': losses,
        'win_rate': win_rate,
        'total_profit': total_profit,
        'avg_confidence': avg_confidence,
        'avg_position_size': avg_position_size,
        'largest_win': largest_win,
        'largest_loss': largest_loss,
        'roi': (total_profit / 100) * 100 if total_trades > 0 else 0
    }
    
    return report

Sample report output:

=== Session Report: 2026-01-26 ===
Duration: 10.3 hours
Total Trades: 23
Wins: 17 (73.9%)
Losses: 6 (26.1%)

Financial:
Starting Balance: $100.00
Ending Balance: $347.00
Total Profit: $247.00
ROI: 247%

Position Sizing:
Average Position: $28.12
Largest Position: $52.30
Smallest Position: $18.50

Performance:
Largest Win: $42.15
Largest Loss: $18.30
Average Win: $21.85
Average Loss: $12.20
Profit Factor: 3.02

Signals Accuracy:
RSI: 78% correct
MA Crossover: 82% correct
Momentum: 71% correct
Sentiment: 68% correct

Risk Metrics:
Max Drawdown: -12.4%
Daily Loss Limit: 30% (not breached)
Position Size Limit: 20% (respected)
Cooldown Violations: 0

Backtesting and Replay Mode

Before risking real money, test the strategy on historical data.

Paper Trading Mode

Run the bot without executing real trades:

python src/main.py --paper-mode

In paper mode:

  • All API calls are simulated
  • Orders are logged but not submitted
  • Balance is tracked internally
  • Logs show what would have happened
class PaperTradingClient:
    def __init__(self, starting_balance=100):
        self.balance = starting_balance
        self.positions = []
        self.trade_history = []
    
    def place_order(self, market, side, size):
        """
        Simulate an order without hitting the real API.
        """
        if size > self.balance:
            return None
        
        # Simulate fill at current market odds
        odds = self.get_current_odds(market)
        fill_price = odds[side]
        
        order = {
            'id': f'paper_{len(self.trade_history)}',
            'market': market,
            'side': side,
            'size': size,
            'fill_price': fill_price,
            'timestamp': datetime.now()
        }
        
        self.positions.append(order)
        self.balance -= size
        
        return order['id']
    
    def resolve_position(self, order_id, outcome):
        """
        Simulate position resolution.
        """
        position = next(p for p in self.positions if p['id'] == order_id)
        
        if position['side'] == outcome:
            # Win
            payout = position['size'] / position['fill_price']
            self.balance += payout
            profit = payout - position['size']
        else:
            # Loss
            profit = -position['size']
        
        position['outcome'] = outcome
        position['profit'] = profit
        
        self.trade_history.append(position)
        self.positions.remove(position)
        
        return profit

Backtesting on Historical Data

Replay past markets to test strategy performance:

python scripts/backtest.py \
  --start-date 2026-01-01 \
  --end-date 2026-01-25 \
  --market-type btc-15m

The backtest script:

  1. Loads historical market data from database
  2. Simulates the bot's decision process at each point in time
  3. Tracks hypothetical trades and outcomes
  4. Generates performance report
def run_backtest(start_date, end_date, initial_balance=100):
    """
    Backtest strategy on historical data.
    """
    markets = load_historical_markets(start_date, end_date)
    balance = initial_balance
    trades = []
    
    for market in markets:
        # Get historical context at time of market opening
        historical_resolutions = get_resolutions_before(market.open_time)
        
        # Compute signals as they would have been at that time
        signals = compute_signals(historical_resolutions)
        confidence, direction = compute_confidence(signals)
        
        # Check if we would have traded
        if confidence < MIN_CONFIDENCE:
            continue
        
        # Calculate position size
        position_size = calculate_position_size(confidence, balance)
        
        if position_size == 0 or position_size > balance:
            continue
        
        # Simulate trade
        side = "YES" if direction == "UP" else "NO"
        entry_odds = market.odds_at_open[side]
        
        # Get actual outcome
        outcome = market.resolution
        
        # Calculate profit/loss
        if side == outcome:
            payout = position_size / entry_odds
            profit = payout - position_size
        else:
            profit = -position_size
        
        balance += profit
        
        trades.append({
            'market': market.id,
            'timestamp': market.open_time,
            'side': side,
            'size': position_size,
            'confidence': confidence,
            'outcome': outcome,
            'profit': profit,
            'balance': balance
        })
    
    # Generate report
    report = generate_backtest_report(trades, initial_balance)
    
    return report, trades

Limitations of Backtesting

Backtesting has several limitations:

  1. Lookahead bias: Careful to only use information available at decision time
  2. Survivorship bias: Only backtesting markets that resolved might skew results
  3. Slippage: Simulated trades assume we get filled at displayed odds, which isn't always true
  4. Market impact: In low-liquidity markets, large orders move prices
  5. Changing market dynamics: Past patterns may not continue
  6. Overfitting: Optimizing parameters on past data can lead to false confidence

Always treat backtest results skeptically. A strategy that works on historical data might fail in live trading.


Troubleshooting

Common Issues

Bot Won't Start

Error: POLYMARKET_API_KEY not found

Fix: Check that .env file exists and contains valid API key.

# Verify .env file
cat .env | grep POLYMARKET_API_KEY

# If missing, add it
echo "POLYMARKET_API_KEY=your_key_here" >> .env

API Authentication Errors

Error: 401 Unauthorized

Fix: API key might be expired or invalid. Generate a new key from Polymarket dashboard.

No Markets Found

Warning: No active 15m BTC markets found

Fix: Polymarket might not have active 15m markets at this time. These markets are created periodically, not continuously. Check the Polymarket website to see if any are available.

Rate Limiting

Error: 429 Too Many Requests

Fix: The bot is making too many API calls. Adjust the polling frequency:

# In config.py
POLL_INTERVAL_SECONDS = 60  # Increase from 30 to 60

Insufficient Balance

Error: Insufficient balance for trade

Fix: Either the wallet balance is too low, or the position sizing calculation is incorrect. Check:

# Verify balance
balance = client.get_balance()
print(f"Current balance: ${balance}")

# Check position sizing
position_size = calculate_position_size(0.7, balance)
print(f"Calculated position size: ${position_size}")

Database Locked

Error: database is locked

Fix: Another process is accessing the database. Either wait or kill competing processes:

# Find processes using the database
lsof data/history.db

# Kill if necessary
kill -9 <PID>

Sentiment Module Failing

Error: X API rate limit exceeded

Fix: The free X API tier has strict limits. Either:

  1. Disable sentiment module: ENABLE_SENTIMENT=false in .env
  2. Upgrade to paid X API tier
  3. Reduce sentiment polling frequency

Debug Mode

Run with verbose logging:

LOG_LEVEL=DEBUG python src/main.py --live

This will output detailed information about every decision, signal calculation, and API call.

Testing Individual Components

Test signals without running the full bot:

# Test RSI calculation
from src.signals.technical import calculate_rsi

resolutions = [1, 1, -1, 1, 1, 1, -1, -1, 1, 1]
rsi = calculate_rsi(resolutions)
print(f"RSI: {rsi}")

Test API connection:

from src.market.client import PolymarketClient

client = PolymarketClient()
balance = client.get_balance()
print(f"Balance: ${balance}")

markets = client.get_active_markets()
print(f"Found {len(markets)} active markets")

Security

API Key Safety

Never commit API keys to git. The .gitignore file should include:

.env
*.log
data/logs/
__pycache__/
*.pyc

Verify before pushing:

# Check what will be committed
git status

# Verify .env is ignored
git check-ignore .env

Least-Privileged Keys

When generating Polymarket API keys, only grant necessary permissions:

  • Trading: Required
  • Withdrawals: Not required (disable this)
  • Read account info: Required

Limit withdrawal permissions to prevent total loss if key is compromised.

Key Rotation

Rotate API keys regularly:

# Generate new key from Polymarket dashboard
# Update .env file
# Restart bot

Redaction in Logs

Ensure sensitive data is redacted from logs:

def log_trade(trade_data):
    """
    Log trade with sensitive data redacted.
    """
    redacted = trade_data.copy()
    
    # Redact API keys
    if 'api_key' in redacted:
        redacted['api_key'] = '***REDACTED***'
    
    # Redact wallet addresses
    if 'wallet_address' in redacted:
        redacted['wallet_address'] = redacted['wallet_address'][:8] + '...'
    
    with open(LOG_FILE, 'a') as f:
        f.write(json.dumps(redacted) + '\n')

Local-Only Mode

The bot runs entirely locally. No data is sent to external servers except:

  • Polymarket API (required for trading)
  • X API (optional, for sentiment)

No telemetry, no analytics, no third-party tracking.

Secure Environment

Run the bot in a secure environment:

# Use a dedicated virtual machine
# Encrypt the disk
# Use strong passwords
# Enable firewall
# Keep OS and dependencies updated

Backup Strategy

Regularly backup logs and configuration:

#!/bin/bash
# backup.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="backups/$DATE"

mkdir -p $BACKUP_DIR

# Backup logs
cp -r data/logs/ $BACKUP_DIR/

# Backup database
cp data/history.db $BACKUP_DIR/

# Backup config (without keys)
cp .env.example $BACKUP_DIR/

echo "Backup completed: $BACKUP_DIR"

Roadmap

Short-term (Next 4 Weeks)

Web Dashboard

  • Real-time balance and P&L display
  • Trade history table with filters
  • Performance charts (equity curve, win rate over time)
  • Current market conditions and signals
  • Manual override controls

Tech stack: Flask + React + Chart.js

Improved Signal Sources

  • Integrate more sentiment sources (Reddit, news APIs)
  • Add on-chain metrics (whale wallet movements, exchange flows)
  • Implement ML-based pattern recognition

Better Monitoring

  • Telegram bot for trade alerts
  • Email notifications for kill switch events
  • Performance metrics dashboard (Sharpe ratio, max drawdown, etc.)

Medium-term (2-3 Months)

Multi-Market Support

  • Extend beyond BTC to ETH, SOL, other crypto markets
  • Support different resolution times (5m, 30m, 1h)
  • Add political and sports markets

Advanced Risk Management

  • Portfolio-level risk (correlations between positions)
  • Dynamic position sizing based on recent performance
  • Automatic parameter adjustment based on market regime

Backtesting Infrastructure

  • Comprehensive backtesting framework
  • Parameter optimization (grid search, genetic algorithms)
  • Monte Carlo simulation for strategy robustness

Long-term (6+ Months)

Machine Learning Integration

  • Train models on historical resolution data
  • Reinforcement learning for strategy optimization
  • Ensemble methods combining multiple signal sources

Multi-Agent System

  • Run multiple bots with different strategies
  • Portfolio allocation across strategies
  • Meta-learning: learn which strategies work in which conditions

Decentralized Execution

  • Run on multiple machines for redundancy
  • Distributed strategy computation
  • Fault-tolerant architecture

FAQ

Is this real or just marketing?

Real. The overnight session happened. Logs are in data/logs/. The $100 to $347 run is documented with timestamps and reasoning for every trade.

But: one session is not proof of long-term viability. Variance is real. The bot could just as easily have lost 50%.

Can the bot fail?

Absolutely. Failure modes:

  • Losing streak hits daily loss limit
  • Market conditions change, signals stop working
  • Technical issues (API downtime, network problems)
  • Black swan events (exchange hack, regulatory changes)

The bot is not a guaranteed money printer. It's a tool that tries to find edge in a specific market type.

Why focus on 15-minute markets?

15-minute BTC markets have several properties that make them interesting for automated trading:

  • High frequency: Many opportunities per day
  • Binary outcomes: Simplifies position management
  • Short duration: Fast feedback, less overnight risk
  • Momentum patterns: BTC often trends within 15m windows

That said, they're also:

  • High variance: Lots of noise
  • Low liquidity sometimes: Slippage can hurt
  • Gambler-heavy: Other participants might be less informed, but also unpredictable

Why not full automation?

The bot is designed to be supervised, not fully autonomous. Reasons:

  • Risk management: Want a human to verify it's working correctly
  • Regulatory uncertainty: Fully autonomous trading might have legal implications
  • Learning opportunity: Watching it trade helps understand what works and what doesn't

You can run it 24/7, but should check in periodically.

What's the expected win rate?

Based on the first session: 73.9%. But this is misleading.

A more realistic long-term win rate is probably 55-60%. Even that would be profitable with good position sizing and risk management.

Variance means you could easily see:

  • 10 losses in a row
  • Multiple days with negative returns
  • Weeks where the strategy doesn't work

How much capital do I need?

Minimum: $100 (as demonstrated)

Recommended: $500-1000

  • More capital means more flexibility in position sizing
  • Can weather losing streaks without hitting kill switch
  • Reduces impact of minimum bet sizes on strategy

But: don't trade money you can't afford to lose.

Does sentiment analysis actually help?

Mixed results. From the first session:

  • Sentiment signal was "correct" 68% of the time
  • Added ~0.2 to confidence on 12 trades
  • Hard to isolate its impact from other signals

Sentiment might help catch major moves (e.g., sudden news), but adds API costs and complexity.

Consider it optional. The bot works without it.

What about taxes?

You're responsible for reporting trading gains. Consult a tax professional.

Each trade on Polymarket is a taxable event. The bot's logs include all necessary data (timestamps, amounts, profit/loss) for tax reporting.

Can I copy-trade someone else's bot?

Polymarket doesn't have built-in copy-trading. You'd need:

  1. Access to their trade history
  2. API to replicate their orders
  3. Permission (copying someone's trades without consent might violate ToS)

Better to run your own bot with your own risk parameters.

How do I know if signals are working?

Track signal accuracy over time:

def evaluate_signals(trade_logs):
    """
    Analyze which signals were predictive.
    """
    signal_accuracy = {
        'rsi': {'correct': 0, 'total': 0},
        'ma_crossover': {'correct': 0, 'total': 0},
        'momentum': {'correct': 0, 'total': 0},
        'sentiment': {'correct': 0, 'total': 0}
    }
    
    for log in trade_logs:
        outcome = log['outcome']['result']
        signals = log['post_trade_review']['signals_accuracy']
        
        for signal, result in signals.items():
            signal_accuracy[signal]['total'] += 1
            if result == 'correct':
                signal_accuracy[signal]['correct'] += 1
    
    for signal, stats in signal_accuracy.items():
        if stats['total'] > 0:
            accuracy = stats['correct'] / stats['total']
            print(f"{signal}: {accuracy:.1%} ({stats['correct']}/{stats['total']})")

If a signal's accuracy drops below 50%, consider disabling it.

What if Polymarket changes their API?

The bot is designed to be modular. If API changes:

  1. Update src/market/client.py with new endpoints
  2. Test in paper mode
  3. Redeploy

API changes are a risk with any external platform.

Can I run multiple bots?

Yes, but be careful about:

  • Position sizing: Don't double-count available balance
  • API rate limits: Multiple bots = more API calls
  • Coordination: Bots might compete with each other

Better to run one bot with higher capital than multiple small ones.


License

MIT License

Copyright (c) 2026 PolyClawd Contributors

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.


Contributing

Contributions are welcome. Before submitting a PR:

  1. Test thoroughly: Run in paper mode, verify no regressions
  2. Document changes: Update README if adding features
  3. Follow style: Use Black for Python formatting
  4. Add tests: Cover new functionality

Areas where contributions are especially valuable:

  • New signal sources (on-chain data, alternative sentiment)
  • Risk management improvements
  • Backtesting enhancements
  • Web dashboard development
  • Documentation improvements

Open an issue before starting major work to discuss approach.


Final Notes

This is an experiment in automated trading on prediction markets. The first session returned 247% overnight. That's not normal. Don't expect it to repeat.

The real value of this project is not the short-term returns, but:

  • Learning how to build trading systems
  • Understanding risk management
  • Practicing systematic decision-making
  • Documenting everything for transparency

Use this as a starting point. Modify it, break it, improve it. Share what you learn.

Markets are hard. Automation is powerful but not magic. Stay skeptical, manage risk, and never trade more than you can afford to lose.


Repository: https://github.com/yourusername/polyclawd
Issues: https://github.com/yourusername/polyclawd/issues
Discussions: https://github.com/yourusername/polyclawd/discussions

Built with Claude, Python, and way too much coffee.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors