Build a custom OpenClaw skill that tracks your bankroll across every platform you bet on. One SKILL.md file, a SQLite database, and your agent can log bets, calculate ROI, enforce risk limits, and tell you exactly where your money is.
Why Your Agent Needs a Bankroll Manager
Most autonomous betting agents can find edges. Very few can manage money. An agent that identifies +EV bets but doesn’t track its bankroll is like a trader who picks stocks but never looks at their portfolio balance. Eventually, sizing errors, platform fragmentation, and tilt compound into ruin.
The problem gets worse across multiple platforms. You might have units on DraftKings, USDC on Polymarket, and cash on Kalshi — three separate balances that your agent treats as disconnected. Without a unified view, your agent can’t answer basic questions: “How much am I up today?” or “Can I afford this 3-unit bet without exceeding my risk limits?”
The bankroll-manager skill is the financial backbone of your agent. It’s a Layer 2 (Wallet) skill in the Agent Betting Stack — sitting between identity (Layer 1) and trading (Layer 3). Every bet your agent places upstream gets logged here. Every risk limit gets enforced here. Every P&L report originates here.
The skill is entirely local. No API calls, no credentials, no network dependencies. Just SQLite and Python math on your machine.
What You’re Building
The bankroll-manager skill teaches your OpenClaw agent six operations:
- Log a bet — Record platform, sport, selection, stake, odds, and bet type
- Record a result — Mark a bet as won, lost, or pushed and calculate P&L
- Check bankroll status — Current balance, total wagered, ROI, and units won/lost
- Generate P&L report — Daily, weekly, or per-platform breakdown
- Pre-bet risk check — Verify a new bet doesn’t breach max daily loss, max single bet, or max correlated exposure
- Export history — Dump all bets to CSV for external analysis
The skill uses sqlite3 and inline python3 — no external packages, no API keys, no network calls.
Prerequisites
- OpenClaw installed and running — Any channel (WhatsApp, Telegram, Discord, WebChat)
- sqlite3 — Pre-installed on macOS and most Linux distributions
- python3 — Standard on macOS and most Linux distributions
No API keys required. This skill is entirely self-contained.
The Math Behind Bankroll Tracking
The bankroll manager tracks everything in units — a fixed fraction of your starting bankroll. This normalizes P&L across platforms where your actual denomination might differ (USD on sportsbooks, USDC on Polymarket, contract prices on Kalshi).
Unit P&L for a winning bet:
If American odds < 0: profit = stake * (100 / |odds|)
If American odds > 0: profit = stake * (odds / 100)
Unit P&L for a losing bet:
pnl = -stake
ROI = (total_profit / total_wagered) * 100
Bankroll = starting_bankroll + sum(all pnl)
For prediction market bets (Polymarket/Kalshi), contract prices are converted to equivalent American odds before P&L calculation:
Contract price to American odds:
If price >= 0.50: odds = -(price / (1 - price)) * 100
If price < 0.50: odds = ((1 - price) / price) * 100
The Complete SKILL.md
Create the skill directory and data folder:
mkdir -p ~/.openclaw/skills/bankroll-manager ~/.openclaw/data
Initialize the SQLite database:
sqlite3 ~/.openclaw/data/bankroll.db "CREATE TABLE IF NOT EXISTS bets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL,
sport TEXT,
selection TEXT NOT NULL,
bet_type TEXT DEFAULT 'h2h',
stake REAL NOT NULL,
odds INTEGER NOT NULL,
result TEXT DEFAULT 'pending',
pnl REAL DEFAULT 0,
logged_at TEXT DEFAULT (datetime('now')),
settled_at TEXT,
notes TEXT
);
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value REAL NOT NULL
);
INSERT OR IGNORE INTO config (key, value) VALUES
('starting_bankroll', 100),
('max_daily_loss', 10),
('max_single_bet', 5),
('max_correlated_exposure', 15);
CREATE VIEW IF NOT EXISTS daily_pnl AS
SELECT date(settled_at) as day,
platform,
COUNT(*) as bets,
SUM(CASE WHEN result='win' THEN 1 ELSE 0 END) as wins,
SUM(CASE WHEN result='loss' THEN 1 ELSE 0 END) as losses,
SUM(pnl) as net_pnl,
SUM(stake) as wagered
FROM bets
WHERE result != 'pending'
GROUP BY date(settled_at), platform;"
Create ~/.openclaw/skills/bankroll-manager/SKILL.md with this content:
---
name: bankroll-manager
description: "Track bankroll across sportsbooks and prediction markets. Log bets, record results, calculate ROI, generate P&L reports, and enforce risk limits. Use when asked about bankroll, P&L, bet logging, ROI, units, risk limits, or money management."
metadata:
openclaw:
emoji: "💰"
requires:
bins: ["sqlite3", "python3"]
---
# Bankroll Manager — Cross-Platform P&L Tracker
Track bankroll, log bets, calculate ROI, and enforce risk limits across all betting platforms.
# When to Use
Use this skill when the user asks about:
- Logging a new bet or recording a bet result
- Current bankroll balance or P&L
- Daily, weekly, or per-platform P&L reports
- ROI or units won/lost
- Whether a bet is within their risk limits
- "How much am I up/down?"
- "Can I afford this bet?"
- Setting or adjusting risk limits
# Database Location
All bankroll data is stored in: `~/.openclaw/data/bankroll.db`
# Operations
### 1. Log a Bet
Record a new bet. Replace values in caps:
```bash
sqlite3 ~/.openclaw/data/bankroll.db \
"INSERT INTO bets (platform, sport, selection, bet_type, stake, odds, notes)
VALUES ('PLATFORM', 'SPORT_KEY', 'SELECTION', 'BET_TYPE', STAKE, ODDS, 'NOTES');"
Parameters:
- PLATFORM — draftkings, fanduel, betmgm, pinnacle, polymarket, kalshi, etc.
- SPORT_KEY — basketball_nba, americanfootball_nfl, soccer_epl, politics, crypto, etc.
- SELECTION — e.g., “Los Angeles Lakers ML”, “Yes on Trump wins”, “Over 215.5”
- BET_TYPE — h2h, spreads, totals, outright, contract
- STAKE — Units wagered (e.g., 2.0)
- ODDS — American odds integer (e.g., -150, +200). For prediction markets, convert contract price first
- NOTES — Optional context
For prediction market contract prices, convert to American odds first:
python3 -c "
price = CONTRACT_PRICE
if price >= 0.50:
odds = int(-(price / (1 - price)) * 100)
else:
odds = int(((1 - price) / price) * 100)
print(odds)
"
After logging, confirm:
sqlite3 ~/.openclaw/data/bankroll.db \
"SELECT id, platform, selection, stake, odds, logged_at FROM bets ORDER BY id DESC LIMIT 5;"
2. Record a Result
Mark a bet as won, lost, or pushed. The P&L is calculated automatically:
python3 -c "
stake = STAKE
odds = ODDS
result = 'RESULT'
if result == 'win':
pnl = stake * (100 / abs(odds)) if odds < 0 else stake * (odds / 100)
elif result == 'push':
pnl = 0
else:
pnl = -stake
print(f'{pnl:.4f}')
" | xargs -I{} sqlite3 ~/.openclaw/data/bankroll.db \
"UPDATE bets SET result = 'RESULT', pnl = {}, settled_at = datetime('now') WHERE id = BET_ID;"
Replace BET_ID with the row ID, STAKE and ODDS with the original values from the bet, and RESULT with win, loss, or push.
Confirm the update:
sqlite3 ~/.openclaw/data/bankroll.db \
"SELECT id, selection, stake, odds, result, pnl FROM bets WHERE id = BET_ID;"
3. Check Bankroll Status
Get current bankroll balance, total P&L, and ROI:
python3 -c "
import sqlite3
conn = sqlite3.connect('$HOME/.openclaw/data/bankroll.db')
c = conn.cursor()
c.execute('SELECT value FROM config WHERE key=\"starting_bankroll\"')
starting = c.fetchone()[0]
c.execute('SELECT COALESCE(SUM(pnl), 0), COALESCE(SUM(stake), 0), COUNT(*), SUM(CASE WHEN result=\"win\" THEN 1 ELSE 0 END), SUM(CASE WHEN result=\"loss\" THEN 1 ELSE 0 END), SUM(CASE WHEN result=\"pending\" THEN 1 ELSE 0 END) FROM bets')
total_pnl, total_wagered, total_bets, wins, losses, pending = c.fetchone()
wins = wins or 0; losses = losses or 0; pending = pending or 0
current = starting + total_pnl
roi = (total_pnl / total_wagered * 100) if total_wagered > 0 else 0
## Pending exposure
c.execute('SELECT COALESCE(SUM(stake), 0) FROM bets WHERE result=\"pending\"')
open_exposure = c.fetchone()[0]
## Today's P&L
c.execute('SELECT COALESCE(SUM(pnl), 0) FROM bets WHERE date(settled_at) = date(\"now\") AND result != \"pending\"')
today_pnl = c.fetchone()[0]
print('=== Bankroll Status ===')
print(f'Starting bankroll: {starting:.1f} units')
print(f'Current bankroll: {current:.1f} units ({total_pnl:+.2f})')
print(f'ROI: {roi:+.2f}%')
print(f'Record: {wins}W - {losses}L ({total_bets} total, {pending} pending)')
print(f'Total wagered: {total_wagered:.1f} units')
print(f'Open exposure: {open_exposure:.1f} units')
print(f\"Today's P&L: {today_pnl:+.2f} units\")
conn.close()
"
4. Generate P&L Report
Produce a daily P&L report with optional platform filter:
python3 -c "
import sqlite3
conn = sqlite3.connect('$HOME/.openclaw/data/bankroll.db')
c = conn.cursor()
## Daily P&L (last 14 days)
c.execute('''SELECT day, SUM(bets), SUM(wins), SUM(losses), SUM(net_pnl), SUM(wagered)
FROM daily_pnl
GROUP BY day ORDER BY day DESC LIMIT 14''')
days = c.fetchall()
## Per-platform totals
c.execute('''SELECT platform, COUNT(*), SUM(CASE WHEN result=\"win\" THEN 1 ELSE 0 END),
SUM(pnl), SUM(stake) FROM bets WHERE result != \"pending\"
GROUP BY platform ORDER BY SUM(pnl) DESC''')
platforms = c.fetchall()
print('=== P&L Report ===')
print()
print('Daily Breakdown (last 14 days):')
print(f'{\"Date\":<12} {\"Bets\":>5} {\"W-L\":>7} {\"P&L\":>8} {\"Wagered\":>8}')
print('-' * 44)
running = 0
for day, bets, wins, losses, pnl, wagered in reversed(days):
running += pnl
print(f'{day:<12} {bets:>5} {wins}-{losses:>4} {pnl:>+8.2f} {wagered:>8.1f}')
print('-' * 44)
print(f'{\"TOTAL\":<12} {\"\":>5} {\"\":>7} {running:>+8.2f}')
print()
print('Per-Platform:')
print(f'{\"Platform\":<16} {\"Bets\":>5} {\"Wins\":>5} {\"P&L\":>8} {\"ROI\":>7}')
print('-' * 44)
for plat, cnt, wins, pnl, wagered in platforms:
wins = wins or 0
roi = (pnl / wagered * 100) if wagered > 0 else 0
print(f'{plat:<16} {cnt:>5} {wins:>5} {pnl:>+8.2f} {roi:>+6.1f}%')
conn.close()
"
5. Pre-Bet Risk Check
Before logging a new bet, verify it doesn’t breach risk limits. Run this BEFORE operation 1:
python3 -c "
import sqlite3, sys
conn = sqlite3.connect('$HOME/.openclaw/data/bankroll.db')
c = conn.cursor()
proposed_stake = PROPOSED_STAKE
proposed_sport = 'PROPOSED_SPORT'
## Load config
config = {}
for row in c.execute('SELECT key, value FROM config'):
config[row[0]] = row[1]
starting = config.get('starting_bankroll', 100)
max_daily = config.get('max_daily_loss', 10)
max_single = config.get('max_single_bet', 5)
max_correlated = config.get('max_correlated_exposure', 15)
## Current bankroll
c.execute('SELECT COALESCE(SUM(pnl), 0) FROM bets')
total_pnl = c.fetchone()[0]
current = starting + total_pnl
## Today's realized loss
c.execute('SELECT COALESCE(SUM(pnl), 0) FROM bets WHERE date(settled_at) = date(\"now\") AND result != \"pending\"')
today_pnl = c.fetchone()[0]
## Today's open exposure
c.execute('SELECT COALESCE(SUM(stake), 0) FROM bets WHERE result = \"pending\" AND date(logged_at) = date(\"now\")')
today_open = c.fetchone()[0]
## Correlated exposure (same sport, pending)
c.execute('SELECT COALESCE(SUM(stake), 0) FROM bets WHERE result = \"pending\" AND sport = ?', (proposed_sport,))
sport_exposure = c.fetchone()[0]
violations = []
if proposed_stake > max_single:
violations.append(f'SINGLE BET LIMIT: {proposed_stake:.1f} units exceeds max {max_single:.1f} units')
worst_case_daily = today_pnl - today_open - proposed_stake
if abs(worst_case_daily) > max_daily:
violations.append(f'DAILY LOSS LIMIT: worst-case daily loss ({abs(worst_case_daily):.1f}) exceeds max {max_daily:.1f} units')
if sport_exposure + proposed_stake > max_correlated:
violations.append(f'CORRELATED EXPOSURE: {proposed_sport} exposure ({sport_exposure + proposed_stake:.1f}) exceeds max {max_correlated:.1f} units')
if proposed_stake > current * 0.25:
violations.append(f'BANKROLL WARNING: {proposed_stake:.1f} units is >{25}% of current bankroll ({current:.1f})')
if violations:
print('⛔ RISK CHECK FAILED:')
for v in violations:
print(f' - {v}')
print()
print('Recommendation: Reduce stake or wait for pending bets to settle.')
else:
print('✅ RISK CHECK PASSED')
print(f' Stake: {proposed_stake:.1f} units')
print(f' Current bankroll: {current:.1f} units')
print(f' Today P&L: {today_pnl:+.2f} | Open exposure: {today_open:.1f}')
print(f' {proposed_sport} exposure after bet: {sport_exposure + proposed_stake:.1f}/{max_correlated:.1f}')
conn.close()
"
Replace PROPOSED_STAKE with the stake in units and PROPOSED_SPORT with the sport key.
6. Export History
Export all bets to CSV for external analysis:
sqlite3 -header -csv ~/.openclaw/data/bankroll.db \
"SELECT id, platform, sport, selection, bet_type, stake, odds, result, pnl, logged_at, settled_at, notes
FROM bets ORDER BY logged_at DESC;" \
> ~/.openclaw/data/bankroll_export.csv && echo "Exported to ~/.openclaw/data/bankroll_export.csv"
7. Update Risk Limits
Adjust risk limits at any time:
sqlite3 ~/.openclaw/data/bankroll.db \
"UPDATE config SET value = NEW_VALUE WHERE key = 'CONFIG_KEY';"
Valid CONFIG_KEY values: starting_bankroll, max_daily_loss, max_single_bet, max_correlated_exposure.
Show current limits:
sqlite3 ~/.openclaw/data/bankroll.db "SELECT key, value FROM config ORDER BY key;"
Output Rules
- When logging a bet, confirm with bet ID, platform, selection, stake, and odds
- When recording a result, show the P&L for that bet and updated bankroll balance
- For bankroll status, always show: starting bankroll, current balance, ROI, record, open exposure, and today’s P&L
- For P&L reports, include daily breakdown and per-platform summary
- Always run the risk check (Operation 5) before logging any new bet — if it fails, warn the user and do NOT log the bet
- Use +/- notation for P&L (e.g., +3.5 units, -2.0 units)
- For prediction market bets, show both the contract price and converted American odds
Error Handling
- If sqlite3 is not available, tell the user to install it (apt install sqlite3 or brew install sqlite3)
- If the database doesn’t exist, run the CREATE TABLE commands from the setup section
- If a bet ID doesn’t exist when updating, warn and list recent bet IDs
- If risk limits haven’t been configured, use defaults: 100 unit bankroll, 10 unit max daily loss, 5 unit max single bet, 15 unit max correlated exposure
- If the user provides odds in decimal or fractional format, convert to American first
## Configure Your Bankroll
After installing the skill, set your actual starting bankroll and risk limits:
```bash
sqlite3 ~/.openclaw/data/bankroll.db "UPDATE config SET value = 1000 WHERE key = 'starting_bankroll';"
sqlite3 ~/.openclaw/data/bankroll.db "UPDATE config SET value = 50 WHERE key = 'max_daily_loss';"
sqlite3 ~/.openclaw/data/bankroll.db "UPDATE config SET value = 30 WHERE key = 'max_single_bet';"
sqlite3 ~/.openclaw/data/bankroll.db "UPDATE config SET value = 75 WHERE key = 'max_correlated_exposure';"
A common framework for risk limits:
- Max daily loss: 5-10% of bankroll
- Max single bet: 1-5% of bankroll (Kelly sizing often recommends even less)
- Max correlated exposure: 10-15% of bankroll per sport/event cluster
These numbers are aggressive for recreational bettors and conservative for professionals. Adjust based on your edge confidence and risk tolerance.
Test It
Restart OpenClaw (or wait for hot-reload if your version supports it), then try these prompts:
| Prompt | Expected Behavior |
|---|---|
| “Log a 2 unit bet on Lakers ML -150 on DraftKings” | Runs risk check, then inserts bet record with stake=2, odds=-150 |
| “I won that Lakers bet (#12)” | Updates bet #12 with result=win, calculates pnl=+1.33 units |
| “What’s my bankroll status?” | Shows starting balance, current balance, ROI, record, open exposure |
| “Show my P&L for this week” | Daily breakdown with per-platform summary |
| “Can I put 8 units on this NFL game?” | Runs pre-bet risk check against max_single_bet and daily loss limits |
| “Log a Polymarket bet: Yes on Biden at $0.62, 5 units” | Converts $0.62 to -163 American odds, runs risk check, logs bet |
| “Export my betting history” | Dumps bets table to ~/.openclaw/data/bankroll_export.csv |
| “Set my max daily loss to 30 units” | Updates config table |
Your agent reads the SKILL.md, runs the risk check before every new bet, and executes the sqlite3/python3 pipeline.
How It Works Under the Hood
OpenClaw skills are not plugins that execute code directly. The SKILL.md is a set of instructions that the LLM (Claude, GPT, etc.) reads and follows at runtime.
When you ask “what’s my bankroll status,” this is what happens:
User message → OpenClaw Gateway
→ LLM reads system prompt + active skills
→ LLM matches "bankroll status" to bankroll-manager skill
→ LLM generates python3 command with SQLite queries
→ OpenClaw executes command via shell tool
→ Python reads database, computes balance and ROI
→ LLM formats output for user with +/- unit notation
The critical behavior is the pre-bet risk check. The SKILL.md instructs the agent to always run Operation 5 before Operation 1. This means your agent will refuse to log a bet that would breach your risk limits — even if you explicitly ask it to. This is the difference between a betting tool and a bankroll manager: the manager says no when it needs to.
Extending the Skill
Daily Snapshot Cron Job
Set up a scheduled task that takes a daily bankroll snapshot at midnight:
{
"cron": "0 0 * * *",
"prompt": "Generate my bankroll status and P&L report for today. If today's loss exceeded 80% of my max daily loss limit, warn me. Save the report to ~/.openclaw/data/snapshots/YYYY-MM-DD.txt."
}
This builds a historical record of your bankroll trajectory and catches risk limit breaches before they escalate.
Chain with Other Skills
The bankroll-manager is the financial hub that connects to every other skill in the stack:
- odds-scanner — When your agent finds a bet, odds-scanner provides the odds that get logged here
- kelly-sizer — Kelly outputs a recommended stake that the bankroll manager validates against risk limits before logging
- clv-tracker — After a bet is logged here, clv-tracker picks up the placement odds for closing line comparison
- ev-calculator — EV analysis identifies the bet, bankroll-manager decides whether the position size is safe
- arb-finder — Arb opportunities involve multiple simultaneous positions — bankroll-manager tracks the correlated exposure
Add Streak Tracking
Extend the status report to include win/loss streaks — useful for detecting tilt:
sqlite3 ~/.openclaw/data/bankroll.db \
"SELECT result, COUNT(*) as streak FROM (
SELECT result, id - ROW_NUMBER() OVER (PARTITION BY result ORDER BY id) as grp
FROM bets WHERE result IN ('win','loss') ORDER BY id DESC LIMIT 20
) GROUP BY result, grp ORDER BY MAX(id) DESC LIMIT 1;"
If your agent detects a 5+ loss streak, it can automatically tighten risk limits or suggest taking a break — a feature no sportsbook will ever give you.
Full Skill Series
This guide is part of the AgentBets OpenClaw Skills series. We’re building a complete library of betting-specific OpenClaw skills that map to the four layers of the Agent Betting Stack:
- Layer 1 — Identity: Agent reputation tracking via Moltbook
- Layer 2 — Wallet: Bankroll management (this guide), balance checking across platforms
- Layer 3 — Trading: Odds scanning, Polymarket monitoring, Kalshi tracking, arbitrage detection
- Layer 4 — Intelligence: EV calculation, Kelly sizing, CLV tracking, news sentiment
Each skill is a standalone SKILL.md you can install independently or compose into a full autonomous betting pipeline.
What’s Next
- Agent Wallet Comparison — Compare wallet infrastructure options for autonomous agents
- Sharp Betting Guide — Understanding edge, CLV, and why bankroll management matters
- Agent Betting Stack Overview — The four-layer framework these skills map to
- Kelly Sizer Skill — Optimal bet sizing that feeds into bankroll manager risk checks
- CLV Tracker Skill — Measure whether your edge is real by tracking closing line value
