
Arbitrage
- 23 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
Arbitrage is a Claude Code skill that detects and monitors price-difference arbitrage opportunities across prediction-market platforms like Polymarket, Kalshi, and Betfair.
About
Arbitrage is a skill that detects and monitors arbitrage opportunities across prediction-market platforms. It compares prices across Polymarket, Kalshi, Manifold, Metaculus, PredictIt, Drift, Betfair, and Smarkets, finds cross-platform, internal, and inverse-market arbitrage, and auto-matches equivalent markets by question similarity. A developer uses it to surface guaranteed-spread bets and monitor them continuously.
- Detects cross-platform arbitrage across Polymarket, Kalshi, Manifold, Betfair, and more
- Runs continuous monitoring or one-time scans with configurable spread and liquidity thresholds
- Auto-matches equivalent markets by question similarity and links them manually
Arbitrage by the numbers
- 23 all-time installs (skills.sh)
- Ranked #704 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
arbitrage capabilities & compatibility
Requires at least one platform API key (POLY_API_KEY or KALSHI_API_KEY).
- Capabilities
- arbitrage detection · market matching · price monitoring
- Use cases
- trading
- Pricing
- Bring your own API key
What arbitrage says it does
Automated detection and monitoring of arbitrage opportunities across prediction market platforms.
Strategy: Buy both YES and NO
npx skills add https://github.com/alsk1992/cloddsbot --skill arbitrageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 23 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Detect and monitor cross-platform arbitrage opportunities across prediction-market platforms.
Who is it for?
spotting guaranteed-spread bets across multiple prediction-market platforms
Skip if: single-platform trading or markets with no equivalent listing elsewhere
When should I use this skill?
you want to find or monitor arbitrage between prediction-market platforms
What you get
A live feed of cross-platform, internal, and inverse-market arbitrage opportunities with spread and liquidity filters.
By the numbers
- 8 supported platforms
- 3 arbitrage types detected
Files
Arbitrage Service - Complete API Reference
Automated detection and monitoring of arbitrage opportunities across prediction market platforms.
Supported Platforms
- Polymarket
- Kalshi
- Manifold
- Metaculus
- PredictIt
- Drift
- Betfair
- Smarkets
---
Chat Commands
Monitoring Control
/arb start # Start arbitrage monitoring
/arb stop # Stop monitoring
/arb status # Check monitoring status
/arb config --interval 60 # Set check interval (seconds)Manual Scanning
/arb check # Run one-time scan
/arb check "election" # Scan with keyword
/arb check --platforms poly,kalshi # Specific platformsMarket Comparison
/arb compare <market-a> <market-b> # Compare two specific markets
/arb compare poly:12345 kalshi:TRUMP # By platform:idView Opportunities
/arb opportunities # List current opportunities
/arb opportunities --min-spread 2 # Min 2% spread
/arb opportunities --format table # Table format
/arb opportunities --format detailed # Detailed viewMarket Linking
/arb link <market-a> <market-b> # Manually link markets
/arb unlink <market-a> <market-b> # Remove link
/arb links # View all links
/arb auto-match # Auto-detect matchesStatistics
/arb stats # Arbitrage statistics
/arb stats --period 7d # Last 7 days
/arb history # Historical opportunities---
TypeScript API Reference
Create Arbitrage Service
import { createArbitrageService } from 'clodds/arbitrage';
const arbService = createArbitrageService({
platforms: ['polymarket', 'kalshi', 'manifold', 'betfair'],
checkIntervalMs: 30000, // Check every 30 seconds
minSpread: 0.5, // 0.5% minimum spread
minLiquidity: 100, // $100 minimum
// Platform credentials
polymarket: { apiKey, apiSecret, passphrase },
kalshi: { apiKey },
});Start/Stop Monitoring
// Start continuous monitoring
await arbService.start();
// Event handlers
arbService.on('arbitrage', (opp) => {
console.log(`⚖️ Arbitrage found!`);
console.log(` ${opp.marketA.platform}: ${opp.marketA.price}`);
console.log(` ${opp.marketB.platform}: ${opp.marketB.price}`);
console.log(` Spread: ${opp.spread.toFixed(2)}%`);
});
arbService.on('arbitrageExpired', (opp) => {
console.log(`Arbitrage expired: ${opp.id}`);
});
// Check status
const isRunning = arbService.isRunning();
// Stop monitoring
await arbService.stop();One-Time Check
// Run a single scan
const opportunities = await arbService.checkArbitrage({
query: 'trump',
platforms: ['polymarket', 'kalshi'],
minSpread: 1,
});
for (const opp of opportunities) {
console.log(`${opp.question}`);
console.log(` Buy on ${opp.buyPlatform} @ ${opp.buyPrice}`);
console.log(` Sell on ${opp.sellPlatform} @ ${opp.sellPrice}`);
console.log(` Spread: ${opp.spread.toFixed(2)}%`);
}Compare Specific Markets
// Compare two specific markets
const comparison = await arbService.compareMarkets(
{ platform: 'polymarket', id: 'market-123' },
{ platform: 'kalshi', id: 'TRUMP-WIN' }
);
if (comparison.hasArbitrage) {
console.log(`Arbitrage exists!`);
console.log(` Buy ${comparison.buySide} on ${comparison.buyPlatform}`);
console.log(` Sell ${comparison.sellSide} on ${comparison.sellPlatform}`);
console.log(` Spread: ${comparison.spread.toFixed(2)}%`);
} else {
console.log(`No arbitrage. Price difference: ${comparison.priceDiff.toFixed(2)}%`);
}Market Linking
// Add a manual match
await arbService.addMatch(
{ platform: 'polymarket', id: 'market-123', question: 'Will Trump win?' },
{ platform: 'kalshi', id: 'TRUMP-WIN', question: 'Trump wins 2024' }
);
// Remove a match
await arbService.removeMatch('polymarket:market-123', 'kalshi:TRUMP-WIN');
// Auto-detect matches using question similarity
const autoMatches = await arbService.autoMatchMarkets({
minSimilarity: 0.85,
});
console.log(`Found ${autoMatches.length} auto-matches`);Get Opportunities
// Get current opportunities
const opportunities = await arbService.getOpportunities({
minSpread: 1,
sortBy: 'spread', // 'spread' | 'liquidity' | 'confidence'
});
// Format for display
const formatted = await arbService.formatOpportunities(opportunities);
console.log(formatted);Statistics
// Get arbitrage statistics
const stats = await arbService.getStats({
period: '30d',
});
console.log(`Total opportunities: ${stats.totalOpportunities}`);
console.log(`Avg spread: ${stats.avgSpread.toFixed(2)}%`);
console.log(`Max spread seen: ${stats.maxSpread.toFixed(2)}%`);
console.log(`By platform pair:`);
for (const [pair, count] of Object.entries(stats.byPlatformPair)) {
console.log(` ${pair}: ${count}`);
}---
Arbitrage Types Detected
1. Cross-Platform Price Difference
Market: "Trump wins 2024"
Polymarket YES: 52¢
Kalshi YES: 55¢
Strategy: Buy Polymarket YES, Sell Kalshi YES
Spread: 3¢ (5.8%)2. Internal Arbitrage (Rebalancing)
Market: "Will X happen?"
YES: 45¢
NO: 52¢
Total: 97¢
Strategy: Buy both YES and NO
Guaranteed profit: 3¢ per $13. Inverse Markets
Market A: "Trump wins" = 55¢
Market B: "Trump loses" = 48¢
Total: 103¢ (should be 100¢)
Strategy: Sell both, pocket 3¢---
Configuration
arbService.configure({
// Scanning
checkIntervalMs: 30000,
batchSize: 50,
// Filtering
minSpread: 0.5,
minLiquidity: 100,
minConfidence: 0.7,
// Matching
autoMatchEnabled: true,
minMatchSimilarity: 0.85,
// Alerts
alertOnNewArb: true,
alertThreshold: 2, // Alert on 2%+ spreads
});---
Best Practices
1. Verify matches manually - Auto-matching can have false positives 2. Check liquidity - Ensure you can actually execute 3. Account for fees - Platform fees reduce spreads 4. Move fast - Arbitrage disappears quickly 5. Use limit orders - Avoid slippage 6. Track all outcomes - Build performance data
/**
* Arbitrage CLI Skill
*
* Commands:
* /arb start - Start arbitrage monitoring
* /arb stop - Stop monitoring
* /arb status - Check monitoring status
* /arb check [query] - Run one-time scan
* /arb compare <market-a> <market-b> - Compare two markets
* /arb opportunities - List current opportunities
* /arb link <market-a> <market-b> - Manually link markets
* /arb unlink <match-id> - Remove link
* /arb links - View all links
* /arb auto-match <query> - Auto-detect matches
* /arb stats - Arbitrage statistics
*/
import {
createArbitrageService,
type ArbitrageService,
type PriceProvider,
} from '../../../arbitrage/index';
import { logger } from '../../../utils/logger';
import type { Platform } from '../../../types';
import { formatHelp } from '../../help.js';
import { wrapSkillError } from '../../errors.js';
let arbService: ArbitrageService | null = null;
function getService(): ArbitrageService {
if (!arbService) {
const providers = new Map() as Map<any, PriceProvider>;
// Providers are registered dynamically by the arbitrage service
// based on available feed configurations
arbService = createArbitrageService(providers);
logger.info({ providerCount: providers.size }, 'Arbitrage service initialized');
}
return arbService;
}
async function handleStart(): Promise<string> {
const service = getService();
service.start();
return 'Arbitrage monitoring started.';
}
async function handleStop(): Promise<string> {
const service = getService();
service.stop();
return 'Arbitrage monitoring stopped.';
}
async function handleStatus(): Promise<string> {
const service = getService();
const stats = service.getStats();
return `**Arbitrage Status**\n\n` +
`Matched market pairs: ${stats.matchCount}\n` +
`Active opportunities: ${stats.activeOpportunities}\n` +
`Average spread: ${stats.avgSpread.toFixed(2)}%\n` +
`Platforms: ${stats.platforms.join(', ')}`;
}
async function handleCheck(query: string): Promise<string> {
const service = getService();
try {
const opps = await service.checkArbitrage();
if (opps.length === 0) {
return 'No new arbitrage opportunities found.';
}
let output = `**Found ${opps.length} Arbitrage Opportunities**\n\n`;
for (const opp of opps.slice(0, 10)) {
output += `**${opp.spreadPct.toFixed(1)}% spread**\n`;
output += ` Buy on ${opp.buyPlatform}: $${opp.buyPrice.toFixed(3)}\n`;
output += ` Sell on ${opp.sellPlatform}: $${opp.sellPrice.toFixed(3)}\n`;
output += ` Profit per $100: $${opp.profitPer100.toFixed(2)}\n\n`;
}
return output;
} catch (error) {
return `Error checking arbitrage: ${error instanceof Error ? error.message : String(error)}`;
}
}
async function handleCompare(marketA: string, marketB: string): Promise<string> {
const service = getService();
// Parse platform:id format
const VALID_PLATFORMS: Platform[] = ['polymarket', 'kalshi', 'manifold', 'metaculus', 'drift', 'predictit', 'predictfun', 'betfair', 'smarkets', 'opinion', 'virtuals', 'hedgehog', 'hyperliquid', 'binance', 'bybit', 'mexc'];
const parseMarket = (m: string): { platform: Platform; id: string } | null => {
const parts = m.split(':');
if (parts.length === 2) {
if (!VALID_PLATFORMS.includes(parts[0] as Platform)) return null;
return { platform: parts[0] as Platform, id: parts[1] };
}
return { platform: 'polymarket', id: m };
};
const a = parseMarket(marketA);
const b = parseMarket(marketB);
if (!a) return `Unknown platform in "${marketA}". Use format: platform:id (e.g., kalshi:MARKET_ID)`;
if (!b) return `Unknown platform in "${marketB}". Use format: platform:id (e.g., polymarket:MARKET_ID)`;
try {
const result = await service.compareMarkets(a.platform, a.id, b.platform, b.id);
if (!result) {
return 'No arbitrage found between these markets.';
}
return `**Market Comparison**\n\n` +
`Spread: ${result.spreadPct.toFixed(2)}%\n` +
`Buy on ${result.buyPlatform}: $${result.buyPrice.toFixed(3)}\n` +
`Sell on ${result.sellPlatform}: $${result.sellPrice.toFixed(3)}\n` +
`Profit per $100: $${result.profitPer100.toFixed(2)}\n` +
`Confidence: ${(result.confidence * 100).toFixed(0)}%`;
} catch (error) {
return `Error comparing markets: ${error instanceof Error ? error.message : String(error)}`;
}
}
async function handleOpportunities(): Promise<string> {
const service = getService();
return service.formatOpportunities();
}
async function handleLinks(): Promise<string> {
const service = getService();
const matches = service.getMatches();
if (matches.length === 0) {
return 'No linked market pairs.';
}
let output = `**Linked Markets** (${matches.length})\n\n`;
for (const match of matches) {
output += `ID: \`${match.id}\`\n`;
output += ` Similarity: ${(match.similarity * 100).toFixed(0)}%\n`;
output += ` Matched by: ${match.matchedBy}\n`;
for (const m of match.markets) {
output += ` - ${m.platform}: ${m.question}\n`;
}
output += '\n';
}
return output;
}
async function handleLink(marketA: string, marketB: string): Promise<string> {
const service = getService();
const parseMarket = (m: string) => {
const parts = m.split(':');
if (parts.length === 2) {
return { platform: parts[0] as Platform, marketId: parts[1], question: parts[1] };
}
return { platform: 'polymarket' as Platform, marketId: m, question: m };
};
const a = parseMarket(marketA);
const b = parseMarket(marketB);
const match = service.addMatch({
markets: [a, b],
similarity: 1.0,
matchedBy: 'manual',
});
return `Markets linked. Match ID: \`${match.id}\``;
}
async function handleUnlink(matchId: string): Promise<string> {
const service = getService();
const success = service.removeMatch(matchId);
return success
? `Match \`${matchId}\` removed.`
: `Match \`${matchId}\` not found.`;
}
async function handleAutoMatch(query: string): Promise<string> {
const service = getService();
try {
const matches = await service.autoMatchMarkets(query);
if (matches.length === 0) {
return 'No matching markets found across platforms.';
}
let output = `**Auto-Matched ${matches.length} Market Pairs**\n\n`;
for (const match of matches) {
output += `Similarity: ${(match.similarity * 100).toFixed(0)}%\n`;
for (const m of match.markets) {
output += ` - ${m.platform}: ${m.question}\n`;
}
output += '\n';
}
return output;
} catch (error) {
return `Error auto-matching: ${error instanceof Error ? error.message : String(error)}`;
}
}
async function handleStats(): Promise<string> {
const service = getService();
const stats = service.getStats();
return `**Arbitrage Statistics**\n\n` +
`Linked market pairs: ${stats.matchCount}\n` +
`Active opportunities: ${stats.activeOpportunities}\n` +
`Average spread: ${stats.avgSpread.toFixed(2)}%\n` +
`Monitored platforms: ${stats.platforms.join(', ')}`;
}
export async function execute(args: string): Promise<string> {
const parts = args.trim().split(/\s+/);
const cmd = parts[0]?.toLowerCase() || 'help';
const rest = parts.slice(1);
try {
switch (cmd) {
case 'start':
return handleStart();
case 'stop':
return handleStop();
case 'status':
return handleStatus();
case 'check':
case 'scan':
return handleCheck(rest.join(' '));
case 'compare':
if (rest.length < 2) return 'Usage: /arb compare <market-a> <market-b>';
return handleCompare(rest[0], rest[1]);
case 'opportunities':
case 'opps':
return handleOpportunities();
case 'link':
if (rest.length < 2) return 'Usage: /arb link <market-a> <market-b>';
return handleLink(rest[0], rest[1]);
case 'unlink':
if (!rest[0]) return 'Usage: /arb unlink <match-id>';
return handleUnlink(rest[0]);
case 'links':
case 'matches':
return handleLinks();
case 'auto-match':
case 'automatch':
if (!rest[0]) return 'Usage: /arb auto-match <query>';
return handleAutoMatch(rest.join(' '));
case 'stats':
return handleStats();
case 'help':
default:
return formatHelp({
name: 'Arbitrage',
description: 'Automated cross-platform arbitrage detection and monitoring',
sections: [
{
title: 'Monitoring',
commands: [
{ cmd: '/arb start', description: 'Start monitoring' },
{ cmd: '/arb stop', description: 'Stop monitoring' },
{ cmd: '/arb status', description: 'Check status' },
],
},
{
title: 'Scanning',
commands: [
{ cmd: '/arb check [query]', description: 'One-time scan' },
{ cmd: '/arb compare <market-a> <market-b>', description: 'Compare two markets' },
{ cmd: '/arb opportunities', description: 'List opportunities' },
],
},
{
title: 'Market Linking',
commands: [
{ cmd: '/arb link <market-a> <market-b>', description: 'Link markets manually' },
{ cmd: '/arb unlink <match-id>', description: 'Remove link' },
{ cmd: '/arb links', description: 'View all links' },
{ cmd: '/arb auto-match <query>', description: 'Auto-detect matches' },
],
},
{
title: 'Statistics',
commands: [
{ cmd: '/arb stats', description: 'View statistics' },
],
},
],
examples: [
'/arb check "trump election"',
'/arb compare poly:12345 kalshi:TRUMP',
'/arb link poly:abc123 kalshi:XYZ',
],
seeAlso: [
{ cmd: '/poly', description: 'Polymarket trading' },
{ cmd: '/bf', description: 'Betfair trading' },
{ cmd: '/feeds', description: 'Market data feeds' },
{ cmd: '/signals', description: 'Trading signals' },
],
notes: [
'Shortcuts: scan = check, opps = opportunities, matches = links, automatch = auto-match',
],
});
}
} catch (error) {
return wrapSkillError('Arbitrage', cmd || 'command', error);
}
}
export default {
name: 'arbitrage',
description: 'Automated cross-platform arbitrage detection and monitoring',
commands: ['/arbitrage', '/arb'],
handle: execute,
};
Related skills
FAQ
Which platforms does it cover?
Polymarket, Kalshi, Manifold, Metaculus, PredictIt, Drift, Betfair, and Smarkets.
What arbitrage types does it detect?
Cross-platform price differences, internal (YES/NO rebalancing) arbitrage, and inverse-market arbitrage.