
Mm
- 17 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
mm is a Claude Code skill that runs automated two-sided market making on Polymarket and Kalshi with inventory skew, volatility-adjusted spreads, and risk controls.
About
This skill runs automated two-sided market making on prediction markets (Polymarket and Kalshi) from the clodds bot. A developer starts a market maker on a market and token, and it places post-only maker quotes with inventory skew, volatility-adjusted spreads, and auto-halt risk controls. It exposes lifecycle, monitoring, and config commands plus a TypeScript strategy API.
- Automated two-sided quoting on Polymarket and Kalshi
- Inventory skew, volatility-adjusted spreads, and risk controls
- Auto-halt when realized P&L exceeds a max-loss threshold
Mm by the numbers
- 17 all-time installs (skills.sh)
- Ranked #731 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
mm capabilities & compatibility
Requires POLY_API_KEY or KALSHI_API_KEY; trading capital and fees apply
- Capabilities
- market making · two sided quoting · risk controls
- Use cases
- trading
- Runs
- Runs locally
- Pricing
- Bring your own API key
What mm says it does
Automated two-sided quoting on prediction markets with inventory skew, volatility-adjusted spreads, and risk controls.
npx skills add https://github.com/alsk1992/cloddsbot --skill mmAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 17 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Run automated inventory-managed two-sided quoting on Polymarket or Kalshi with risk controls.
Who is it for?
Automated liquidity provision on prediction markets with inventory and loss controls
When should I use this skill?
You want to run or tune a market-making bot on Polymarket or Kalshi
What you get
A market maker places and requotes managed two-sided orders and halts on excess loss.
By the numbers
- 20 configurable start flags
- 6-step how-it-works cycle
Files
Market Making Skill
Automated two-sided quoting on prediction markets with inventory skew, volatility-adjusted spreads, and risk controls.
Supported Platforms
- Polymarket (post-only maker orders, zero taker fees)
- Kalshi
---
Chat Commands
Lifecycle
/mm start <platform> <marketId> <tokenId> [flags] Start market making
/mm stop <id> Stop and cancel all orders
/mm list List active market makersMonitoring
/mm status Overview of all active MMs
/mm status <id> Detailed state for one MMConfiguration
/mm config <id> View current config as JSON
/mm config <id> --spread 3 Update config (takes effect next requote)---
Start Flags
| Flag | Default | Description |
|---|---|---|
--spread N | 2 | Base half-spread in cents |
--min-spread N | 1 | Minimum spread floor (cents) |
--max-spread N | 10 | Maximum spread cap (cents) |
--size N | 50 | Order size per side (shares) |
--max-inventory N | 500 | Max inventory before aggressive skew |
--skew N | 0.5 | Inventory skew factor (0-1) |
--vol-mult N | 10 | Volatility multiplier for spread widening |
--alpha N | 0.3 | EMA alpha for fair value smoothing (0-1) |
--fv-method M | weighted_mid | Fair value method: mid_price, weighted_mid, vwap, ema |
--interval N | 5000 | Requote interval in ms |
--threshold N | 1 | Min price change (cents) to trigger requote |
--max-pos N | 1000 | Max position value in USD |
--max-loss N | 100 | Max loss before auto-halt (USD) |
--max-orders N | 1 | Orders per side (levels) |
--level-spacing N | (=spread) | Cents between price levels |
--level-decay N | 0.5 | Size decay per level (0-1, e.g. 0.5 = each level half of previous) |
--neg-risk true | false | Enable negative risk mode (Polymarket crypto) |
--name "Name" | auto | Display name for the outcome |
---
Examples
# Start with defaults
/mm start polymarket 0xabc123 98765
# Custom spread and sizing
/mm start polymarket 0xabc123 98765 --spread 3 --size 100 --max-inventory 1000
# Tight spread for liquid market
/mm start polymarket 0xabc123 98765 --spread 1 --min-spread 1 --max-spread 5 --interval 2000
# 3-level quoting: L1=50 shares, L2=25, L3=12 — spaced 2c apart
/mm start polymarket 0xabc123 98765 --max-orders 3 --level-spacing 2 --level-decay 0.5 --size 50
# Check all running MMs
/mm status
# Widen spread on the fly
/mm config polymarket_98765678 --spread 4
# Shut down
/mm stop polymarket_98765678---
How It Works
1. Fair value computed from orderbook (weighted mid, VWAP, or EMA) 2. Spread adjusted by recent volatility (wider in volatile markets) 3. Skew shifts quotes away from overweight side to manage inventory 4. Quotes placed as post-only maker orders (bid and ask) 5. Requote cycle: cancel all, recalculate, place new orders 6. Auto-halt if realized P&L exceeds max loss threshold
---
API Usage
import { createMMStrategy, type MMConfig } from '../trading/market-making';
const config: MMConfig = {
id: 'btc-yes',
platform: 'polymarket',
marketId: '0x...',
tokenId: '12345',
outcomeName: 'BTC > 100k',
baseSpreadCents: 2,
minSpreadCents: 1,
maxSpreadCents: 10,
orderSize: 50,
maxInventory: 500,
skewFactor: 0.5,
volatilityMultiplier: 10,
fairValueAlpha: 0.3,
fairValueMethod: 'weighted_mid',
requoteIntervalMs: 5000,
requoteThresholdCents: 1,
maxPositionValueUsd: 1000,
maxLossUsd: 100,
maxOrdersPerSide: 1,
};
const strategy = createMMStrategy(config, { execution, feeds });
botManager.registerStrategy(strategy);
await botManager.startBot(strategy.config.id);/**
* Market Making CLI Skill
*
* Commands:
* /mm start <platform> <marketId> <tokenId> [--spread 2] [--size 50] [--max-inventory 500]
* /mm stop <id>
* /mm status [id]
* /mm config <id> --spread 3
* /mm list
*/
import { logger } from '../../../utils/logger';
import type { MMConfig, MMState } from '../../../trading/market-making/types';
import type { Strategy } from '../../../trading/bots/index';
// Track active MM instances
const activeMMs = new Map<string, { strategy: Strategy; config: MMConfig }>();
function parseFlags(args: string[]): Record<string, string> {
const flags: Record<string, string> = {};
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--') && i + 1 < args.length) {
flags[args[i].slice(2)] = args[i + 1];
i++;
}
}
return flags;
}
async function handleStart(args: string): Promise<string> {
const parts = args.split(/\s+/);
if (parts.length < 3) {
return 'Usage: /mm start <platform> <marketId> <tokenId> [--spread 2] [--size 50] [--max-inventory 500]\n\nExample: /mm start polymarket 0x1234... 12345 --spread 2 --size 50';
}
const platform = parts[0] as 'polymarket' | 'kalshi';
if (platform !== 'polymarket' && platform !== 'kalshi') {
return 'Platform must be "polymarket" or "kalshi"';
}
const marketId = parts[1];
const tokenId = parts[2];
const flags = parseFlags(parts.slice(3));
const id = `${platform}_${tokenId.slice(0, 8)}`;
if (activeMMs.has(id)) {
return `MM "${id}" is already running. Stop it first with: /mm stop ${id}`;
}
const mmConfig: MMConfig = {
id,
platform,
marketId,
tokenId,
outcomeName: flags['name'] || `${platform}:${tokenId.slice(0, 12)}`,
negRisk: flags['neg-risk'] === 'true',
baseSpreadCents: parseFloat(flags['spread']) || 2,
minSpreadCents: parseFloat(flags['min-spread']) || 1,
maxSpreadCents: parseFloat(flags['max-spread']) || 10,
orderSize: parseFloat(flags['size']) || 50,
maxInventory: parseFloat(flags['max-inventory']) || 500,
skewFactor: parseFloat(flags['skew']) || 0.5,
volatilityMultiplier: parseFloat(flags['vol-mult']) || 10,
fairValueAlpha: parseFloat(flags['alpha']) || 0.3,
fairValueMethod: (flags['fv-method'] as MMConfig['fairValueMethod']) || 'weighted_mid',
requoteIntervalMs: parseFloat(flags['interval']) || 5000,
requoteThresholdCents: parseFloat(flags['threshold']) || 1,
maxPositionValueUsd: parseFloat(flags['max-pos']) || 1000,
maxLossUsd: parseFloat(flags['max-loss']) || 100,
maxOrdersPerSide: parseFloat(flags['max-orders']) || 1,
levelSpacingCents: flags['level-spacing'] ? (parseFloat(flags['level-spacing']) || undefined) : undefined,
levelSizeDecay: flags['level-decay'] ? (parseFloat(flags['level-decay']) || undefined) : undefined,
};
try {
const { createMMStrategy } = await import('../../../trading/market-making/strategy');
const { createFeedManager } = await import('../../../feeds/index');
const { createExecutionService } = await import('../../../execution/index');
const feeds = await createFeedManager({} as any);
const execution = createExecutionService({});
const strategy = createMMStrategy(mmConfig, { execution, feeds });
activeMMs.set(id, { strategy, config: mmConfig });
if (strategy.init) {
await strategy.init({} as any);
}
const lines = [
`**Market Maker Started: ${id}**`,
'',
`| Parameter | Value |`,
`|-----------|-------|`,
`| Platform | ${mmConfig.platform} |`,
`| Market | ${mmConfig.marketId.slice(0, 20)}... |`,
`| Token | ${mmConfig.tokenId.slice(0, 20)}... |`,
`| Spread | ${mmConfig.baseSpreadCents}c (${mmConfig.minSpreadCents}-${mmConfig.maxSpreadCents}c) |`,
`| Size | ${mmConfig.orderSize} shares/side |`,
`| Max Inventory | ${mmConfig.maxInventory} shares |`,
`| Skew Factor | ${mmConfig.skewFactor} |`,
`| Levels/Side | ${mmConfig.maxOrdersPerSide} (spacing: ${mmConfig.levelSpacingCents ?? mmConfig.baseSpreadCents}c, decay: ${mmConfig.levelSizeDecay ?? 0.5}) |`,
`| FV Method | ${mmConfig.fairValueMethod} |`,
`| Requote | every ${mmConfig.requoteIntervalMs}ms |`,
`| Max Loss | $${mmConfig.maxLossUsd} |`,
'',
'Use `/mm status ' + id + '` to check state, `/mm stop ' + id + '` to halt.',
];
return lines.join('\n');
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
logger.error({ error: msg }, 'Failed to start MM');
return `Failed to start market maker: ${msg}`;
}
}
async function handleStop(args: string): Promise<string> {
const id = args.trim();
if (!id) return 'Usage: /mm stop <id>\n\nUse `/mm list` to see active MMs.';
const mm = activeMMs.get(id);
if (!mm) return `No active MM with id "${id}". Use \`/mm list\` to see active MMs.`;
if (mm.strategy.cleanup) {
await mm.strategy.cleanup();
}
activeMMs.delete(id);
return `Market maker "${id}" stopped and all orders cancelled.`;
}
async function handleStatus(args: string): Promise<string> {
const id = args.trim();
if (!id) {
if (activeMMs.size === 0) return 'No active market makers.';
const lines = ['**Active Market Makers**\n'];
for (const [mmId, mm] of activeMMs) {
const { getMMState } = await import('../../../trading/market-making/strategy');
const state = getMMState(mm.strategy);
const status = state?.haltReason ? `HALTED: ${state.haltReason}` : state?.isQuoting ? 'QUOTING' : 'IDLE';
lines.push(`- **${mmId}**: ${status} | inv=${state?.inventory ?? 0} | pnl=$${(state?.realizedPnL ?? 0).toFixed(2)} | fills=${state?.fillCount ?? 0}`);
}
return lines.join('\n');
}
const mm = activeMMs.get(id);
if (!mm) return `No active MM with id "${id}".`;
const { getMMState } = await import('../../../trading/market-making/strategy');
const state = getMMState(mm.strategy);
if (!state) return `Could not read state for "${id}".`;
const lines = [
`**MM Status: ${id}**`,
'',
`| Field | Value |`,
`|-------|-------|`,
`| Status | ${state.haltReason ? `HALTED: ${state.haltReason}` : state.isQuoting ? 'QUOTING' : 'IDLE'} |`,
`| Fair Value | ${state.fairValue.toFixed(4)} |`,
`| EMA FV | ${state.emaFairValue.toFixed(4)} |`,
`| Inventory | ${state.inventory} shares |`,
`| Realized PnL | $${state.realizedPnL.toFixed(2)} |`,
`| Fill Count | ${state.fillCount} |`,
`| Active Bids | ${state.activeBids.length} |`,
`| Active Asks | ${state.activeAsks.length} |`,
`| Price History | ${state.priceHistory.length} samples |`,
];
return lines.join('\n');
}
async function handleConfig(args: string): Promise<string> {
const parts = args.split(/\s+/);
const id = parts[0];
if (!id) return 'Usage: /mm config <id> --spread 3 --size 100 ...';
const mm = activeMMs.get(id);
if (!mm) return `No active MM with id "${id}".`;
const flags = parseFlags(parts.slice(1));
if (Object.keys(flags).length === 0) {
const c = mm.config;
return [
`**Config: ${id}**`,
'',
`\`\`\`json`,
JSON.stringify(c, null, 2),
`\`\`\``,
].join('\n');
}
const safeNum = (v: string) => { const n = Number(v); return isNaN(n) ? undefined : n; };
if (flags['spread']) mm.config.baseSpreadCents = safeNum(flags['spread']) ?? mm.config.baseSpreadCents;
if (flags['min-spread']) mm.config.minSpreadCents = safeNum(flags['min-spread']) ?? mm.config.minSpreadCents;
if (flags['max-spread']) mm.config.maxSpreadCents = safeNum(flags['max-spread']) ?? mm.config.maxSpreadCents;
if (flags['size']) mm.config.orderSize = safeNum(flags['size']) ?? mm.config.orderSize;
if (flags['max-inventory']) mm.config.maxInventory = safeNum(flags['max-inventory']) ?? mm.config.maxInventory;
if (flags['skew']) mm.config.skewFactor = safeNum(flags['skew']) ?? mm.config.skewFactor;
if (flags['alpha']) mm.config.fairValueAlpha = safeNum(flags['alpha']) ?? mm.config.fairValueAlpha;
if (flags['interval']) mm.config.requoteIntervalMs = safeNum(flags['interval']) ?? mm.config.requoteIntervalMs;
if (flags['max-loss']) mm.config.maxLossUsd = safeNum(flags['max-loss']) ?? mm.config.maxLossUsd;
if (flags['max-orders']) mm.config.maxOrdersPerSide = safeNum(flags['max-orders']) ?? mm.config.maxOrdersPerSide;
if (flags['level-spacing']) mm.config.levelSpacingCents = safeNum(flags['level-spacing']) ?? mm.config.levelSpacingCents;
if (flags['level-decay']) mm.config.levelSizeDecay = safeNum(flags['level-decay']) ?? mm.config.levelSizeDecay;
return `Config updated for "${id}". Changes take effect on next requote.`;
}
function handleList(): string {
if (activeMMs.size === 0) return 'No active market makers. Start one with `/mm start`.';
const lines = ['**Active Market Makers**\n'];
lines.push(`| ID | Platform | Spread | Size | Status |`);
lines.push(`|----|----------|--------|------|--------|`);
for (const [id, mm] of activeMMs) {
const c = mm.config;
lines.push(`| ${id} | ${c.platform} | ${c.baseSpreadCents}c | ${c.orderSize} | active |`);
}
return lines.join('\n');
}
function helpText(): string {
return [
'**Market Making Commands**',
'',
'```',
'/mm start <platform> <marketId> <tokenId> [flags] Start market making',
'/mm stop <id> Stop market maker',
'/mm status [id] View status',
'/mm config <id> [--spread N] [--size N] ... View/update config',
'/mm list List active MMs',
'```',
'',
'**Start Flags:**',
'```',
'--spread N Base half-spread in cents (default: 2)',
'--size N Order size per side (default: 50)',
'--max-inventory N Max inventory before aggressive skew (default: 500)',
'--skew N Skew factor 0-1 (default: 0.5)',
'--fv-method M Fair value: mid_price|weighted_mid|vwap|ema (default: weighted_mid)',
'--interval N Requote interval ms (default: 5000)',
'--max-loss N Max loss before halt USD (default: 100)',
'--max-orders N Orders per side / levels (default: 1)',
'--level-spacing N Cents between price levels (default: same as spread)',
'--level-decay N Size decay per level 0-1 (default: 0.5)',
'--neg-risk true Enable negative risk mode',
'--name "Name" Display name for the outcome',
'```',
'',
'**Example:**',
'```',
'/mm start polymarket 0xabc123 98765 --spread 3 --size 100 --max-inventory 1000',
'```',
].join('\n');
}
async function execute(args: string): Promise<string> {
const trimmed = args.trim();
if (!trimmed) return helpText();
const spaceIdx = trimmed.indexOf(' ');
const command = spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx);
const rest = spaceIdx === -1 ? '' : trimmed.slice(spaceIdx + 1).trim();
try {
switch (command) {
case 'start':
return await handleStart(rest);
case 'stop':
return await handleStop(rest);
case 'status':
return await handleStatus(rest);
case 'config':
return await handleConfig(rest);
case 'list':
return handleList();
case 'help':
default:
return helpText();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error({ error: message, args }, 'MM command failed');
return `Error: ${message}`;
}
}
export default {
name: 'mm',
description: 'Market making - two-sided quoting with inventory management',
commands: ['/mm', '/market-making'],
handle: execute,
};
Related skills
FAQ
Which platforms are supported?
Polymarket (post-only maker orders, zero taker fees) and Kalshi.
How is risk controlled?
Inventory skew, volatility-adjusted spreads, and auto-halt when realized P&L exceeds the max-loss threshold.