Bet Journal
SkillFiles & storageTrack your sports bets in a local CSV journal. Calculate ROI, CLV, win rate by sport/bet-type, and identify where you're actually making money.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Bet Journal skill
What this skill tells your AI
The instructions your AI receives, as published by jiayaoqijia/cryptoskill in skills/ai-crypto/ianalloway-bet-journal/SKILL.md and read by ahel’s review.
The most important edge in sports betting isn't picking winners — it's tracking your bets to learn where your edge is real vs. imagined. This skill creates and analyzes a local CSV bet journal.
Initialize the Journal
python3 -c "
import csv, os
JOURNAL_FILE = os.path.expanduser('~/.openclaw/bet-journal.csv')
os.makedirs(os.path.dirname(JOURNAL_FILE), exist_ok=True)
HEADERS = ['date', 'sport', 'game', 'bet_type', 'pick', 'odds_american',
'stake', 'result', 'pnl', 'closing_odds', 'notes']
if not os.path.exists(JOURNAL_FILE):
with open(JOURNAL_FILE, 'w', newline='') as f:
csv.DictWriter(f, fieldnames=HEADERS).writeheader()
print(f'Journal created: {JOURNAL_FILE}')
else:
print(f'Journal already exists: {JOURNAL_FILE}')
print()
print('Fields:')
for h in HEADERS:
print(f' {h}')
print()
print('result values: W (win), L (loss), P (push), V (void)')
print('bet_type values: ML (moneyline), SPREAD, TOTAL, PARLAY, PROP, FUTURES')
"
Add a Bet
python3 -c "
import csv, os
from datetime import date
JOURNAL_FILE = os.path.expanduser('~/.openclaw/bet-journal.csv')
def add_bet(sport, game, bet_type, pick, odds_american, stake, result, closing_odds=None, notes=''):
if result == 'W':
if odds_american > 0:
pnl = stake * odds_american / 100
else:
pnl = stake * 100 / abs(odds_american)
elif result in ('L',):
pnl = -stake
else:
pnl = 0 # push or void
row = {
'date': date.today().isoformat(),
'sport': sport,
'game': game,
'bet_type': bet_type,
'pick': pick,
'odds_american': odds_american,
'stake': stake,
'result': result,
'pnl': round(pnl, 2),
'closing_odds': closing_odds or '',
'notes': notes,
}
with open(JOURNAL_FILE, 'a', newline='') as f:
csv.DictWriter(f, fieldnames=row.keys()).writerow(row)
print(f'Added: {sport} | {game} | {pick} ({odds_american:+d}) | Result: {result} | P&L: {pnl:+.2f}')
# --- EDIT THESE TO LOG YOUR BET ---
add_bet(
sport='NBA',
game='Lakers vs Warriors',
bet_type='SPREAD',
pick='Warriors -3.5',
odds_american=-110,
stake=100,
result='W', # W / L / P / V
closing_odds=-115, # Line at close (for CLV tracking)
notes='Sharp action on Warriors, public on Lakers',
)
"
View Your Dashboard
python3 -c "
import csv, os
from collections import defaultdict
JOURNAL_FILE = os.path.expanduser('~/.openclaw/bet-journal.csv')
if not os.path.exists(JOURNAL_FILE):
print('No journal found. Run the init script first.')
exit()
bets = []
with open(JOURNAL_FILE) as f:
for row in csv.DictReader(f):
row['stake'] = float(row['stake'])
row['pnl'] = float(row['pnl'])
row['odds_american'] = int(row['odds_american'])
bets.append(row)
if not bets:
print('No bets logged yet.')
exit()
wins = [b for b in bets if b['result'] == 'W']
losses = [b for b in bets if b['result'] == 'L']
pushes = [b for b in bets if b['result'] in ('P', 'V')]
total_bets = len(wins) + len(losses)
win_rate = len(wins) / total_bets if total_bets else 0
total_wagered = sum(b['stake'] for b in bets)
total_pnl = sum(b['pnl'] for b in bets)
roi = total_pnl / total_wagered if total_wagered else 0
print('=== Bet Journal Dashboard ===')
print(f'Record: {len(wins)}-{len(losses)}-{len(pushes)}')
print(f'Win Rate: {win_rate:.1%}')
print(f'Total Wagered: \${total_wagered:,.2f}')
print(f'Net P&L: {total_pnl:+,.2f}')
print(f'ROI: {roi:+.1%}')
print()
# By sport
by_sport = defaultdict(list)
for b in bets:
by_sport[b['sport']].append(b)
print('=== P&L by Sport ===')
for sport, sbets in sorted(by_sport.items()):
sw = sum(1 for b in sbets if b['result'] == 'W')
sl = sum(1 for b in sbets if b['result'] == 'L')
spnl = sum(b['pnl'] for b in sbets)
swag = sum(b['stake'] for b in sbets)
sroi = spnl / swag if swag else 0
print(f' {sport:<8} {sw}-{sl} P&L: {spnl:+.2f} ROI: {sroi:+.1%}')
# By bet type
print()
print('=== P&L by Bet Type ===')
by_type = defaultdict(list)
for b in bets:
by_type[b['bet_type']].append(b)
for bt, tbets in sorted(by_type.items()):
tw = sum(1 for b in tbets if b['result'] == 'W')
tl = sum(1 for b in tbets if b['result'] == 'L')
tpnl = sum(b['pnl'] for b in tbets)
twag = sum(b['stake'] for b in tbets)
troi = tpnl / twag if twag else 0
print(f' {bt:<10} {tw}-{tl} P&L: {tpnl:+.2f} ROI: {troi:+.1%}')
"
Closing Line Value (CLV) Calculator
CLV is the #1 predictor of long-term betting success. If you consistently beat the closing line, you have a real edge:
python3 -c "
import csv, os
JOURNAL_FILE = os.path.expanduser('~/.openclaw/bet-journal.csv')
def american_to_prob(american):
if american > 0:
return 100 / (american + 100)
else:
return abs(american) / (abs(american) + 100)
bets_with_clv = []
with open(JOURNAL_FILE) as f:
for row in csv.DictReader(f):
if row['closing_odds']:
open_prob = american_to_prob(int(row['odds_american']))
close_prob = american_to_prob(int(row['closing_odds']))
clv = close_prob - open_prob # positive = you got better than closing line
bets_with_clv.append({**row, 'clv': clv, 'open_prob': open_prob, 'close_prob': close_prob})
if not bets_with_clv:
print('No CLV data yet — add closing_odds when logging bets')
exit()
avg_clv = sum(b['clv'] for b in bets_with_clv) / len(bets_with_clv)
positive_clv = sum(1 for b in bets_with_clv if b['clv'] > 0)
print('=== Closing Line Value Report ===')
print(f'Bets with CLV data: {len(bets_with_clv)}')
print(f'Avg CLV: {avg_clv:+.2%}')
print(f'Beat closing line: {positive_clv}/{len(bets_with_clv)} ({positive_clv/len(bets_with_clv):.0%})')
print()
if avg_clv > 0.01:
print('VERDICT: ✅ Positive CLV — you are buying at better than market prices')
print(' Your edge is REAL. Keep doing what you are doing.')
elif avg_clv > -0.01:
print('VERDICT: ⚪ Neutral CLV — roughly breaking even vs. market')
print(' Improve line shopping and timing to push positive.')
else:
print('VERDICT: 🔴 Negative CLV — you are consistently getting bad lines')
print(' Shop more books, bet earlier when sharp action moves lines')
print()
print('Recent CLV (last 5 bets):')
for b in bets_with_clv[-5:]:
clv_str = f\"{b['clv']:+.2%}\"
print(f\" {b['game']:<30} {b['pick']:<20} CLV: {clv_str}\")
"
Monthly P&L Chart
python3 -c "
import csv, os
from collections import defaultdict
JOURNAL_FILE = os.path.expanduser('~/.openclaw/bet-journal.csv')
monthly = defaultdict(float)
with open(JOURNAL_FILE) as f:
for row in csv.DictReader(f):
month = row['date'][:7] # YYYY-MM
monthly[month] += float(row['pnl'])
print('=== Monthly P&L ===')
running = 0
for month in sorted(monthly):
pnl = monthly[month]
running += pnl
bar_len = int(abs(pnl) / 10)
bar = ('█' * min(bar_len, 30)) if pnl >= 0 else ('▓' * min(bar_len, 30))
sign = '+' if pnl >= 0 else ''
color_prefix = '🟢' if pnl >= 0 else '🔴'
print(f'{color_prefix} {month} {sign}{pnl:>8.2f} {bar} (running: {running:+.2f})')
"
Import from Bet Tracker Apps
Convert Action Network or Betstamp exports to journal format:
python3 -c "
import csv, os
# Converts a basic Action Network CSV export
def import_action_network(input_file, output_file):
rows_added = 0
with open(input_file) as fin, open(output_file, 'a', newline='') as fout:
reader = csv.DictReader(fin)
writer = None
for row in reader:
mapped = {
'date': row.get('Date', ''),
'sport': row.get('Sport', ''),
'game': row.get('Event', ''),
'bet_type': row.get('Type', 'ML'),
'pick': row.get('Pick', ''),
'odds_american': row.get('Odds', '0'),
'stake': row.get('Risk', '0').replace('\$', '').replace(',', ''),
'result': 'W' if row.get('Result') == 'Won' else 'L' if row.get('Result') == 'Lost' else 'P',
'pnl': row.get('Profit', '0').replace('\$', '').replace(',', ''),
'closing_odds': '',
'notes': 'imported from Action Network',
}
if writer is None:
writer = csv.DictWriter(fout, fieldnames=mapped.keys())
writer.writerow(mapped)
rows_added += 1
print(f'Imported {rows_added} bets from {input_file}')
print('Usage: import_action_network(\"action_export.csv\", \"~/.openclaw/bet-journal.csv\")')
print('Modify the field mapping above to match your export format.')
"
The Numbers That Matter
| Metric | Break-even | Good | Elite |
|---|---|---|---|
| Win Rate (at -110) | 52.4% | 54%+ | 57%+ |
| ROI | 0% | +3%+ | +8%+ |
| Average CLV | 0% | +1%+ | +2.5%+ |
| CLV Beat Rate | 50% | 55%+ | 60%+ |
Track everything. Your gut thinks you're winning everywhere. Your spreadsheet knows the truth.
Author
Created by Ian Alloway — Data Scientist specializing in sports analytics and ML.
License
MIT License
Signals
- GitHub stars
- 76
- Forks
- 16
- Last commit
- Sep 2026
ahel review
K1info
remote-installer-piped-to-shell (in TRUST.auto.yaml)
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
bet-journal- Source
- github.com/jiayaoqijia/cryptoskill