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.
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.
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-15mCreated 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.
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
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
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."
}
}The 247% overnight return was exceptional and likely not repeatable. Some observations:
- Variance: This was one session. The win rate could easily be 40% over a longer period.
- Market conditions: Overnight volatility was higher than usual. The bot benefited from strong directional moves.
- Compounding risk: Scaling position sizes amplified gains but also amplified risk. A few bad trades could have wiped out 50%+ of capital.
- 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.
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) │
└──────────────┘ └────────────────┘ └─────────────┘
- Market Monitor polls Polymarket API every 30 seconds for active 15m BTC markets
- Signal Engine computes indicators from historical resolutions and current sentiment
- Decision Core combines signals into a confidence score and decides: buy Yes, buy No, or skip
- Risk Manager validates the trade against position limits, drawdown checks, cooldown timers
- Trade Executor submits the order to Polymarket and tracks confirmation
- Logger records everything: signals, reasoning, outcome, post-trade review
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
- 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)
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.txtCreate a .env file in the root directory:
cp .env.example .envEdit .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
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
Paper trading mode (no real money):
python src/main.py --paper-modeLive trading (real money, careful):
python src/main.py --liveBacktest on historical data:
python scripts/backtest.py --start-date 2026-01-01 --end-date 2026-01-25The 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 secondsRSI 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 rsiMoving 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:]) / periodMomentum 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_scoreSignals 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, directionRisk management is the most critical component. Without strict controls, the bot could lose everything in minutes.
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_sizeIf 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 TrueAfter 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] = 0Before 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"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_idEmergency 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)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 TrueEvery 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
{
"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."
}
}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 reportSample 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
Before risking real money, test the strategy on historical data.
Run the bot without executing real trades:
python src/main.py --paper-modeIn 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 profitReplay past markets to test strategy performance:
python scripts/backtest.py \
--start-date 2026-01-01 \
--end-date 2026-01-25 \
--market-type btc-15mThe backtest script:
- Loads historical market data from database
- Simulates the bot's decision process at each point in time
- Tracks hypothetical trades and outcomes
- 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, tradesBacktesting has several limitations:
- Lookahead bias: Careful to only use information available at decision time
- Survivorship bias: Only backtesting markets that resolved might skew results
- Slippage: Simulated trades assume we get filled at displayed odds, which isn't always true
- Market impact: In low-liquidity markets, large orders move prices
- Changing market dynamics: Past patterns may not continue
- 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.
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" >> .envError: 401 Unauthorized
Fix: API key might be expired or invalid. Generate a new key from Polymarket dashboard.
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.
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 60Error: 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}")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>Error: X API rate limit exceeded
Fix: The free X API tier has strict limits. Either:
- Disable sentiment module:
ENABLE_SENTIMENT=falsein.env - Upgrade to paid X API tier
- Reduce sentiment polling frequency
Run with verbose logging:
LOG_LEVEL=DEBUG python src/main.py --liveThis will output detailed information about every decision, signal calculation, and API call.
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")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 .envWhen 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.
Rotate API keys regularly:
# Generate new key from Polymarket dashboard
# Update .env file
# Restart botEnsure 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')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.
Run the bot in a secure environment:
# Use a dedicated virtual machine
# Encrypt the disk
# Use strong passwords
# Enable firewall
# Keep OS and dependencies updatedRegularly 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"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.)
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
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
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%.
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.
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
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.
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
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.
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.
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.
Polymarket doesn't have built-in copy-trading. You'd need:
- Access to their trade history
- API to replicate their orders
- Permission (copying someone's trades without consent might violate ToS)
Better to run your own bot with your own risk parameters.
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.
The bot is designed to be modular. If API changes:
- Update
src/market/client.pywith new endpoints - Test in paper mode
- Redeploy
API changes are a risk with any external platform.
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.
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.
Contributions are welcome. Before submitting a PR:
- Test thoroughly: Run in paper mode, verify no regressions
- Document changes: Update README if adding features
- Follow style: Use Black for Python formatting
- 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.
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.
