- name
- openclaw-polymarket-trading-bot
- description
- AI-powered prediction and automated trading bot for Polymarket's 5-minute Bitcoin Up/Down markets
- triggers
- ["set up OpenClaw Polymarket trading bot","configure Polymarket AI trading bot with CLOB","run Polymarket prediction bot for BTC markets","deploy OpenClaw bot with whale flow analysis","integrate OpenAI with Polymarket trading strategy","build custom Polymarket trading indicators","debug Polymarket CLOB order execution","analyze whale activity on Polymarket BTC markets"]
# OpenClaw Polymarket Trading Bot
> Skill by [ara.so](https://ara.so) — Hermes Skills collection.
OpenClaw is a production-ready TypeScript bot that predicts whether Polymarket's 5-minute BTC Up/Down markets will move up (YES) or down (NO) and executes real orders via the Polymarket CLOB API. It combines momentum indicators, volatility analysis, whale flow tracking, and optional LLM scoring to generate trading signals.
## What It Does
The bot runs a continuous loop that:
1. Identifies the active 5-minute BTC Up/Down market on Polymarket
2. Collects real-time price data and whale flow (large trades ≥ $200)
3. Computes indicators (momentum, volatility, whale bias)
4. Generates predictions using AI-enhanced scoring
5. Places market orders when edge threshold is exceeded
6. Optionally closes positions after a timed interval
## Installation
```bash
git clone https://github.com/Golrypavium/openclaw-polymarket-trading-bot.git
cd openclaw-polymarket-trading-bot
npm install
cp .env.example .env
```
Edit `.env` with your Polymarket CLOB credentials:
```bash
# Required CLOB credentials
PRIVATE_KEY=your_wallet_private_key_hex
CLOB_API_KEY=your_clob_api_key
CLOB_SECRET=your_clob_secret
CLOB_PASS_PHRASE=your_clob_passphrase
# Optional AI enhancement
OPENAI_API_KEY=your_openai_key
```
## Key Commands
```bash
# Run bot in development mode
npm run dev
# Build for production
npm run build
# Run production build
npm start
# Start Signal Lab dashboard
npm run serve
```
## Configuration
### Environment Variables
| Variable | Purpose | Default |
|----------|---------|---------|
| `PRIVATE_KEY` | Wallet private key (64 hex chars) | Required |
| `CLOB_API_KEY` | Polymarket CLOB API key | Required |
| `CLOB_SECRET` | CLOB secret | Required |
| `CLOB_PASS_PHRASE` | CLOB passphrase | Required |
| `LOOP_SECONDS` | Cycle interval | `15` |
| `MAX_POSITION_USD` | Order size in USD | `100` |
| `EDGE_THRESHOLD` | Minimum edge to trade | `0.03` |
| `CLOSE_AFTER_SECONDS` | Auto-close timer (0=hold) | `0` |
| `OPENAI_API_KEY` | Optional LLM enhancement | Empty |
| `OPENAI_MODEL` | Model for predictions | `gpt-4o-mini` |
### Strategy Parameters
The edge threshold determines when to trade:
- `pUp5m > 0.5 + EDGE_THRESHOLD` → Open YES position
- `pUp5m < 0.5 - EDGE_THRESHOLD` → Open NO position
- Otherwise → Hold
Example with `EDGE_THRESHOLD=0.03`:
- Trade YES if pUp5m > 0.53
- Trade NO if pUp5m < 0.47
## Core Architecture
### Data Flow
```typescript
// core/entry.ts - Main bot loop
import { opts } from './settings';
import { getMarketSnapshot } from './adapters/eventSource';
import { computeIndicators } from './strategy/indicators';
import { generateForecast } from './strategy/forecast';
import { decideAction } from './strategy/simulator';
import { submitBuy } from './adapters/orderBridge';
async function runCycle() {
// 1. Get current market and price data
const snapshot = await getMarketSnapshot();
// 2. Build indicators
const indicators = computeIndicators(snapshot.ticks, snapshot.whale);
// 3. Generate forecast
const forecast = await generateForecast(indicators);
// 4. Decide action
const action = decideAction(forecast, snapshot.currentPrice);
// 5. Execute if needed
if (action.type === 'OPEN_YES') {
await submitBuy('YES', action.size, snapshot.market);
}
}
```
### Market Selection
```typescript
// core/adapters/eventSource.ts
import { opts } from '../settings';
export async function getCurrentMarket(): Promise<MarketInfo> {
const gammaUrl = `${opts.polymarketRestBase}/markets`;
const response = await fetch(gammaUrl);
const markets = await response.json();
// Find active 5-min BTC market by time bucket
const now = Date.now();
const btcMarket = markets.find(m =>
m.question.includes('BTC') &&
m.question.includes('5 minutes') &&
m.endDate > now &&
m.startDate <= now
);
return {
conditionId: btcMarket.conditionId,
question: btcMarket.question,
yesTokenId: btcMarket.tokens.YES,
noTokenId: btcMarket.tokens.NO
};
}
```
### Indicator Calculation
```typescript
// core/strategy/indicators.ts
import { PricePoint, WhaleActivity, IndicatorSet } from '../schema/defs';
export function computeIndicators(
ticks: PricePoint[],
whale: WhaleActivity
): IndicatorSet {
// Momentum: recent price changes
const shortReturn = computeReturn(ticks, 2); // ~30s ago
const medReturn = computeReturn(ticks, 8); // ~2min ago
// Volatility: recent price range
const recentPrices = ticks.slice(-8).map(t => t.price);
const volatility = Math.max(...recentPrices) - Math.min(...recentPrices);
// Whale bias: net YES vs NO from large trades
const whaleBias = (whale.yesVolume - whale.noVolume) /
(whale.yesVolume + whale.noVolume + 0.01);
return {
shortReturn,
medReturn,
volatility,
whaleBias,
whaleIntensity: whale.yesVolume + whale.noVolume
};
}
function computeReturn(ticks: PricePoint[], lookback: number): number {
if (ticks.length < lookback + 1) return 0;
const current = ticks[ticks.length - 1].price;
const past = ticks[ticks.length - lookback - 1].price;
return (current - past) / (past + 0.01);
}
```
### Forecast Generation
```typescript
// core/strategy/forecast.ts
import { IndicatorSet, Forecast } from '../schema/defs';
import { scoreLLM } from '../ai/remoteScorer';
import { opts } from '../settings';
export async function generateForecast(
indicators: IndicatorSet
): Promise<Forecast> {
// Base probability from momentum and whale bias
let pUp5m = 0.5;
// Momentum component (40% weight)
const momentumSignal = (indicators.shortReturn * 0.6 +
indicators.medReturn * 0.4);
pUp5m += momentumSignal * 0.4;
// Whale bias component (30% weight)
pUp5m += indicators.whaleBias * 0.3;
// Volatility dampening
const volFactor = Math.min(indicators.volatility * 2, 0.1);
pUp5m = 0.5 + (pUp5m - 0.5) * (1 - volFactor);
// Optional LLM enhancement (30% weight)
let llmBias = 0;
if (opts.openaiApiKey) {
llmBias = await scoreLLM(indicators);
pUp5m += llmBias * 0.3;
}
// Clamp to [0.2, 0.8]
pUp5m = Math.max(0.2, Math.min(0.8, pUp5m));
const confidence = Math.abs(pUp5m - 0.5) * 2; // [0, 1]
return { pUp5m, confidence, llmBias };
}
```
### LLM Integration
```typescript
// core/ai/remoteScorer.ts
import { IndicatorSet } from '../schema/defs';
import { opts } from '../settings';
export async function scoreLLM(indicators: IndicatorSet): Promise<number> {
const prompt = `Predict Bitcoin 5-minute direction.
Indicators:
- Short momentum: ${indicators.shortReturn.toFixed(4)}
- Medium momentum: ${indicators.medReturn.toFixed(4)}
- Volatility: ${indicators.volatility.toFixed(4)}
- Whale bias: ${indicators.whaleBias.toFixed(3)}
Respond with a number from -1 (strong DOWN) to +1 (strong UP).`;
const response = await fetch(`${opts.openaiBaseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${opts.openaiApiKey}`
},
body: JSON.stringify({
model: opts.openaiModel,
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
max_tokens: 10
})
});
const data = await response.json();
const text = data.choices[0].message.content.trim();
const bias = parseFloat(text);
return isNaN(bias) ? 0 : Math.max(-1, Math.min(1, bias));
}
```
### Order Execution
```typescript
// core/adapters/orderBridge.ts
import { ClobClient } from '@polymarket/clob-client';
import { opts } from '../settings';
const clobClient = new ClobClient(
opts.clobApiUrl,
opts.clobChainId,
opts.privateKey,
{
apiKey: opts.clobApiKey,
secret: opts.clobSecret,
passphrase: opts.clobPassPhrase
}
);
export async function submitBuy(
side: 'YES' | 'NO',
usdSize: number,
market: MarketInfo
): Promise<string> {
const tokenId = side === 'YES' ? market.yesTokenId : market.noTokenId;
// Get current best price
const book = await clobClient.getOrderBook(tokenId);
const price = side === 'YES'
? parseFloat(book.asks[0]?.price || '0.5')
: parseFloat(book.bids[0]?.price || '0.5');
const shares = usdSize / price;
const order = await clobClient.createMarketBuyOrder({
tokenID: tokenId,
amount: shares.toString(),
feeRateBps: '0',
nonce: Date.now()
});
const result = await clobClient.postOrder(order);
// Record position
recordPosition({
orderId: result.orderID,
side,
entryPrice: price,
shares,
timestamp: Date.now(),
marketId: market.conditionId
});
return result.orderID;
}
```
### Position Management
```typescript
// core/strategy/book.ts
import fs from 'fs';
interface Position {
orderId: string;
side: 'YES' | 'NO';
entryPrice: number;
shares: number;
timestamp: number;
marketId: string;
}
const POSITIONS_FILE = './positions.json';
export function recordPosition(pos: Position): void {
const positions = loadPositions();
positions.push(pos);
fs.writeFileSync(POSITIONS_FILE, JSON.stringify(positions, null, 2));
}
export function getExpiredPositions(closeAfterSeconds: number): Position[] {
const positions = loadPositions();
const now = Date.now();
return positions.filter(p =>
(now - p.timestamp) / 1000 > closeAfterSeconds
);
}
export function removePosition(orderId: string): void {
const positions = loadPositions();
const filtered = positions.filter(p => p.orderId !== orderId);
fs.writeFileSync(POSITIONS_FILE, JSON.stringify(filtered, null, 2));
}
function loadPositions(): Position[] {
if (!fs.existsSync(POSITIONS_FILE)) return [];
return JSON.parse(fs.readFileSync(POSITIONS_FILE, 'utf-8'));
}
```
### Timed Position Closing
```typescript
// In core/entry.ts cycle
import { getExpiredPositions, removePosition } from './strategy/book';
import { submitSell } from './adapters/orderBridge';
async function closeExpiredPositions() {
if (opts.closeAfterSeconds === 0) return;
const expired = getExpiredPositions(opts.closeAfterSeconds);
for (const pos of expired) {
try {
await submitSell(pos.side, pos.shares, pos.marketId);
removePosition(pos.orderId);
console.log(`Closed position ${pos.orderId} after timeout`);
} catch (err) {
console.error(`Failed to close ${pos.orderId}:`, err);
}
}
}
```
## Signal Lab Dashboard
### Starting the Dashboard
```bash
npm run serve
# Opens on http://localhost:8787
```
### Prediction API
```typescript
// core/dashboard.ts
import express from 'express';
import { getMarketSnapshot } from './adapters/eventSource';
import { computeIndicators } from './strategy/indicators';
import { generateForecast } from './strategy/forecast';
const app = express();
app.get('/api/prediction', async (req, res) => {
try {
const snapshot = await getMarketSnapshot();
const indicators = computeIndicators(snapshot.ticks, snapshot.whale);
const forecast = await generateForecast(indicators);
res.json({
market: snapshot.market.question,
currentPrice: snapshot.currentPrice,
prediction: forecast.pUp5m,
confidence: forecast.confidence,
recommendedSide: forecast.pUp5m > 0.5 ? 'YES' : 'NO',
indicators: {
momentum: indicators.shortReturn,
volatility: indicators.volatility,
whaleBias: indicators.whaleBias
}
});
} catch (error) {
Ver en GitHub