Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
335
examples/neural-trader/specialized/sports-betting.js
Normal file
335
examples/neural-trader/specialized/sports-betting.js
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* Sports Betting with Neural Trader
|
||||
*
|
||||
* Demonstrates using @neural-trader/sports-betting for:
|
||||
* - Arbitrage detection across sportsbooks
|
||||
* - Kelly Criterion position sizing
|
||||
* - Expected Value (EV) calculations
|
||||
* - Odds comparison and analysis
|
||||
* - Bankroll management
|
||||
*/
|
||||
|
||||
// Sports betting configuration
|
||||
const bettingConfig = {
|
||||
// Bankroll settings
|
||||
initialBankroll: 10000,
|
||||
maxBetPercent: 0.05, // 5% max per bet (conservative Kelly)
|
||||
minEdge: 0.02, // 2% minimum edge to bet
|
||||
fractionKelly: 0.25, // Quarter Kelly for safety
|
||||
|
||||
// Sportsbooks
|
||||
sportsbooks: ['DraftKings', 'FanDuel', 'BetMGM', 'Caesars', 'PointsBet'],
|
||||
|
||||
// Sports to analyze
|
||||
sports: ['NFL', 'NBA', 'MLB', 'NHL', 'Soccer', 'UFC']
|
||||
};
|
||||
|
||||
// Sample odds data (American format)
|
||||
const sampleOdds = {
|
||||
'NFL_Week17_Chiefs_Raiders': {
|
||||
event: 'Kansas City Chiefs vs Las Vegas Raiders',
|
||||
sport: 'NFL',
|
||||
date: '2024-12-29',
|
||||
time: '16:25 ET',
|
||||
odds: {
|
||||
'DraftKings': { moneyline: { home: -280, away: +230 }, spread: { home: -6.5, homeOdds: -110, away: +6.5, awayOdds: -110 }, total: { over: 44.5, overOdds: -110, under: 44.5, underOdds: -110 } },
|
||||
'FanDuel': { moneyline: { home: -285, away: +235 }, spread: { home: -6.5, homeOdds: -112, away: +6.5, awayOdds: -108 }, total: { over: 44.5, overOdds: -108, under: 44.5, underOdds: -112 } },
|
||||
'BetMGM': { moneyline: { home: -275, away: +225 }, spread: { home: -6.5, homeOdds: -108, away: +6.5, awayOdds: -112 }, total: { over: 45.0, overOdds: -110, under: 45.0, underOdds: -110 } },
|
||||
'Caesars': { moneyline: { home: -290, away: +240 }, spread: { home: -7.0, homeOdds: -110, away: +7.0, awayOdds: -110 }, total: { over: 44.5, overOdds: -105, under: 44.5, underOdds: -115 } },
|
||||
'PointsBet': { moneyline: { home: -270, away: +220 }, spread: { home: -6.5, homeOdds: -115, away: +6.5, awayOdds: -105 }, total: { over: 44.5, overOdds: -112, under: 44.5, underOdds: -108 } }
|
||||
},
|
||||
trueProbability: { home: 0.72, away: 0.28 } // Model estimate
|
||||
},
|
||||
'NBA_Lakers_Warriors': {
|
||||
event: 'Los Angeles Lakers vs Golden State Warriors',
|
||||
sport: 'NBA',
|
||||
date: '2024-12-30',
|
||||
time: '19:30 ET',
|
||||
odds: {
|
||||
'DraftKings': { moneyline: { home: +145, away: -170 }, spread: { home: +4.5, homeOdds: -110, away: -4.5, awayOdds: -110 }, total: { over: 225.5, overOdds: -110, under: 225.5, underOdds: -110 } },
|
||||
'FanDuel': { moneyline: { home: +150, away: -175 }, spread: { home: +4.5, homeOdds: -108, away: -4.5, awayOdds: -112 }, total: { over: 226.0, overOdds: -110, under: 226.0, underOdds: -110 } },
|
||||
'BetMGM': { moneyline: { home: +140, away: -165 }, spread: { home: +4.0, homeOdds: -110, away: -4.0, awayOdds: -110 }, total: { over: 225.5, overOdds: -108, under: 225.5, underOdds: -112 } },
|
||||
'Caesars': { moneyline: { home: +155, away: -180 }, spread: { home: +5.0, homeOdds: -110, away: -5.0, awayOdds: -110 }, total: { over: 225.0, overOdds: -115, under: 225.0, underOdds: -105 } },
|
||||
'PointsBet': { moneyline: { home: +160, away: -185 }, spread: { home: +5.0, homeOdds: -105, away: -5.0, awayOdds: -115 }, total: { over: 226.5, overOdds: -110, under: 226.5, underOdds: -110 } }
|
||||
},
|
||||
trueProbability: { home: 0.42, away: 0.58 }
|
||||
}
|
||||
};
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(70));
|
||||
console.log('Sports Betting Analysis - Neural Trader');
|
||||
console.log('='.repeat(70));
|
||||
console.log();
|
||||
|
||||
// 1. Display configuration
|
||||
console.log('1. Betting Configuration:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Initial Bankroll: $${bettingConfig.initialBankroll.toLocaleString()}`);
|
||||
console.log(` Max Bet Size: ${bettingConfig.maxBetPercent * 100}% ($${bettingConfig.initialBankroll * bettingConfig.maxBetPercent})`);
|
||||
console.log(` Kelly Fraction: ${bettingConfig.fractionKelly * 100}%`);
|
||||
console.log(` Minimum Edge: ${bettingConfig.minEdge * 100}%`);
|
||||
console.log(` Sportsbooks: ${bettingConfig.sportsbooks.join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// 2. Analyze each event
|
||||
for (const [eventId, eventData] of Object.entries(sampleOdds)) {
|
||||
console.log(`2. Event Analysis: ${eventData.event}`);
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Sport: ${eventData.sport} | Date: ${eventData.date} ${eventData.time}`);
|
||||
console.log();
|
||||
|
||||
// Display odds comparison
|
||||
console.log(' Moneyline Odds Comparison:');
|
||||
console.log(' Sportsbook | Home | Away | Home Prob | Away Prob | Vig');
|
||||
console.log(' ' + '-'.repeat(60));
|
||||
|
||||
for (const [book, odds] of Object.entries(eventData.odds)) {
|
||||
const homeProb = americanToImpliedProb(odds.moneyline.home);
|
||||
const awayProb = americanToImpliedProb(odds.moneyline.away);
|
||||
const vig = (homeProb + awayProb - 1) * 100;
|
||||
|
||||
console.log(` ${book.padEnd(13)} | ${formatOdds(odds.moneyline.home).padStart(9)} | ${formatOdds(odds.moneyline.away).padStart(9)} | ${(homeProb * 100).toFixed(1).padStart(8)}% | ${(awayProb * 100).toFixed(1).padStart(8)}% | ${vig.toFixed(1)}%`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Find best odds
|
||||
const bestHomeOdds = findBestOdds(eventData.odds, 'moneyline', 'home');
|
||||
const bestAwayOdds = findBestOdds(eventData.odds, 'moneyline', 'away');
|
||||
|
||||
console.log(` Best Home Odds: ${formatOdds(bestHomeOdds.odds)} at ${bestHomeOdds.book}`);
|
||||
console.log(` Best Away Odds: ${formatOdds(bestAwayOdds.odds)} at ${bestAwayOdds.book}`);
|
||||
console.log();
|
||||
|
||||
// Check for arbitrage
|
||||
console.log(' Arbitrage Analysis:');
|
||||
const arbResult = checkArbitrage(eventData.odds);
|
||||
|
||||
if (arbResult.hasArbitrage) {
|
||||
console.log(` 🎯 ARBITRAGE OPPORTUNITY FOUND!`);
|
||||
console.log(` Guaranteed profit: ${(arbResult.profit * 100).toFixed(2)}%`);
|
||||
console.log(` Bet ${arbResult.homeBook} Home: $${arbResult.homeBet.toFixed(2)}`);
|
||||
console.log(` Bet ${arbResult.awayBook} Away: $${arbResult.awayBet.toFixed(2)}`);
|
||||
} else {
|
||||
console.log(` No pure arbitrage available (combined implied: ${(arbResult.combinedImplied * 100).toFixed(1)}%)`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// EV calculations
|
||||
console.log(' Expected Value Analysis (using model probabilities):');
|
||||
console.log(` Model: Home ${(eventData.trueProbability.home * 100).toFixed(0)}% | Away ${(eventData.trueProbability.away * 100).toFixed(0)}%`);
|
||||
console.log();
|
||||
console.log(' Bet | Book | Odds | EV | Kelly | Recommended');
|
||||
console.log(' ' + '-'.repeat(65));
|
||||
|
||||
const evAnalysis = calculateEVForAllBets(eventData);
|
||||
evAnalysis.forEach(bet => {
|
||||
const evStr = bet.ev >= 0 ? `+${(bet.ev * 100).toFixed(2)}%` : `${(bet.ev * 100).toFixed(2)}%`;
|
||||
const kellyStr = bet.kelly > 0 ? `${(bet.kelly * 100).toFixed(2)}%` : '-';
|
||||
const recBet = bet.recommendedBet > 0 ? `$${bet.recommendedBet.toFixed(0)}` : 'PASS';
|
||||
|
||||
console.log(` ${bet.type.padEnd(16)} | ${bet.book.padEnd(13)} | ${formatOdds(bet.odds).padStart(9)} | ${evStr.padStart(8)} | ${kellyStr.padStart(7)} | ${recBet.padStart(11)}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// Top recommended bets
|
||||
const topBets = evAnalysis.filter(b => b.recommendedBet > 0).sort((a, b) => b.ev - a.ev);
|
||||
if (topBets.length > 0) {
|
||||
console.log(` 📊 Top Recommended Bet:`);
|
||||
const best = topBets[0];
|
||||
console.log(` ${best.type} at ${best.book}`);
|
||||
console.log(` Odds: ${formatOdds(best.odds)} | EV: +${(best.ev * 100).toFixed(2)}% | Bet Size: $${best.recommendedBet.toFixed(0)}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// 3. Bankroll simulation
|
||||
console.log('3. Bankroll Growth Simulation:');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
const simulation = simulateBankrollGrowth(1000, 0.03, 0.55, bettingConfig);
|
||||
console.log(` Starting Bankroll: $${bettingConfig.initialBankroll.toLocaleString()}`);
|
||||
console.log(` Bets Placed: ${simulation.totalBets}`);
|
||||
console.log(` Win Rate: ${(simulation.winRate * 100).toFixed(1)}%`);
|
||||
console.log(` Final Bankroll: $${simulation.finalBankroll.toLocaleString()}`);
|
||||
console.log(` ROI: ${((simulation.finalBankroll / bettingConfig.initialBankroll - 1) * 100).toFixed(1)}%`);
|
||||
console.log(` Max Drawdown: ${(simulation.maxDrawdown * 100).toFixed(1)}%`);
|
||||
console.log();
|
||||
|
||||
// 4. Syndicate management (advanced)
|
||||
console.log('4. Syndicate Management:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(' Account Diversification Strategy:');
|
||||
console.log(' - Spread bets across multiple sportsbooks');
|
||||
console.log(' - Maximum 20% of action per book');
|
||||
console.log(' - Rotate accounts to avoid limits');
|
||||
console.log(' - Track CLV (Closing Line Value) per book');
|
||||
console.log();
|
||||
|
||||
console.log('='.repeat(70));
|
||||
console.log('Sports betting analysis completed!');
|
||||
console.log('='.repeat(70));
|
||||
}
|
||||
|
||||
// Convert American odds to implied probability
|
||||
function americanToImpliedProb(odds) {
|
||||
if (odds > 0) {
|
||||
return 100 / (odds + 100);
|
||||
} else {
|
||||
return Math.abs(odds) / (Math.abs(odds) + 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert implied probability to American odds
|
||||
function probToAmerican(prob) {
|
||||
if (prob >= 0.5) {
|
||||
return Math.round(-100 * prob / (1 - prob));
|
||||
} else {
|
||||
return Math.round(100 * (1 - prob) / prob);
|
||||
}
|
||||
}
|
||||
|
||||
// Format American odds
|
||||
function formatOdds(odds) {
|
||||
return odds > 0 ? `+${odds}` : `${odds}`;
|
||||
}
|
||||
|
||||
// Find best odds across sportsbooks
|
||||
function findBestOdds(odds, market, side) {
|
||||
let best = { odds: -Infinity, book: '' };
|
||||
|
||||
for (const [book, bookOdds] of Object.entries(odds)) {
|
||||
const odd = bookOdds[market][side];
|
||||
if (odd > best.odds) {
|
||||
best = { odds: odd, book };
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
// Check for arbitrage opportunity
|
||||
function checkArbitrage(odds) {
|
||||
const bestHome = findBestOdds(odds, 'moneyline', 'home');
|
||||
const bestAway = findBestOdds(odds, 'moneyline', 'away');
|
||||
|
||||
const homeProb = americanToImpliedProb(bestHome.odds);
|
||||
const awayProb = americanToImpliedProb(bestAway.odds);
|
||||
const combinedImplied = homeProb + awayProb;
|
||||
|
||||
if (combinedImplied < 1) {
|
||||
// Arbitrage exists!
|
||||
const profit = 1 / combinedImplied - 1;
|
||||
const totalStake = 1000;
|
||||
const homeBet = totalStake * (homeProb / combinedImplied);
|
||||
const awayBet = totalStake * (awayProb / combinedImplied);
|
||||
|
||||
return {
|
||||
hasArbitrage: true,
|
||||
profit,
|
||||
combinedImplied,
|
||||
homeBook: bestHome.book,
|
||||
awayBook: bestAway.book,
|
||||
homeBet,
|
||||
awayBet
|
||||
};
|
||||
}
|
||||
|
||||
return { hasArbitrage: false, combinedImplied };
|
||||
}
|
||||
|
||||
// Calculate EV for all betting options
|
||||
function calculateEVForAllBets(eventData) {
|
||||
const results = [];
|
||||
const bankroll = bettingConfig.initialBankroll;
|
||||
|
||||
for (const [book, odds] of Object.entries(eventData.odds)) {
|
||||
// Home moneyline
|
||||
const homeOdds = odds.moneyline.home;
|
||||
const homeEV = calculateEV(eventData.trueProbability.home, homeOdds);
|
||||
const homeKelly = calculateKelly(eventData.trueProbability.home, homeOdds);
|
||||
const homeRec = homeEV >= bettingConfig.minEdge
|
||||
? Math.min(homeKelly * bettingConfig.fractionKelly, bettingConfig.maxBetPercent) * bankroll
|
||||
: 0;
|
||||
|
||||
results.push({
|
||||
type: 'Home Moneyline',
|
||||
book,
|
||||
odds: homeOdds,
|
||||
ev: homeEV,
|
||||
kelly: homeKelly,
|
||||
recommendedBet: homeRec
|
||||
});
|
||||
|
||||
// Away moneyline
|
||||
const awayOdds = odds.moneyline.away;
|
||||
const awayEV = calculateEV(eventData.trueProbability.away, awayOdds);
|
||||
const awayKelly = calculateKelly(eventData.trueProbability.away, awayOdds);
|
||||
const awayRec = awayEV >= bettingConfig.minEdge
|
||||
? Math.min(awayKelly * bettingConfig.fractionKelly, bettingConfig.maxBetPercent) * bankroll
|
||||
: 0;
|
||||
|
||||
results.push({
|
||||
type: 'Away Moneyline',
|
||||
book,
|
||||
odds: awayOdds,
|
||||
ev: awayEV,
|
||||
kelly: awayKelly,
|
||||
recommendedBet: awayRec
|
||||
});
|
||||
}
|
||||
|
||||
return results.sort((a, b) => b.ev - a.ev);
|
||||
}
|
||||
|
||||
// Calculate Expected Value
|
||||
function calculateEV(trueProb, americanOdds) {
|
||||
const impliedProb = americanToImpliedProb(americanOdds);
|
||||
const decimalOdds = americanOdds > 0 ? (americanOdds / 100) + 1 : (100 / Math.abs(americanOdds)) + 1;
|
||||
|
||||
return (trueProb * decimalOdds) - 1;
|
||||
}
|
||||
|
||||
// Calculate Kelly Criterion
|
||||
function calculateKelly(trueProb, americanOdds) {
|
||||
const decimalOdds = americanOdds > 0 ? (americanOdds / 100) + 1 : (100 / Math.abs(americanOdds)) + 1;
|
||||
const b = decimalOdds - 1;
|
||||
const p = trueProb;
|
||||
const q = 1 - p;
|
||||
|
||||
const kelly = (b * p - q) / b;
|
||||
return Math.max(0, kelly);
|
||||
}
|
||||
|
||||
// Simulate bankroll growth
|
||||
function simulateBankrollGrowth(numBets, avgEdge, winRate, config) {
|
||||
let bankroll = config.initialBankroll;
|
||||
let peak = bankroll;
|
||||
let maxDrawdown = 0;
|
||||
let wins = 0;
|
||||
|
||||
for (let i = 0; i < numBets; i++) {
|
||||
const betSize = bankroll * config.maxBetPercent * config.fractionKelly;
|
||||
const isWin = Math.random() < winRate;
|
||||
|
||||
if (isWin) {
|
||||
bankroll += betSize * (1 + avgEdge);
|
||||
wins++;
|
||||
} else {
|
||||
bankroll -= betSize;
|
||||
}
|
||||
|
||||
peak = Math.max(peak, bankroll);
|
||||
maxDrawdown = Math.max(maxDrawdown, (peak - bankroll) / peak);
|
||||
}
|
||||
|
||||
return {
|
||||
totalBets: numBets,
|
||||
winRate: wins / numBets,
|
||||
finalBankroll: Math.round(bankroll),
|
||||
maxDrawdown
|
||||
};
|
||||
}
|
||||
|
||||
// Run the example
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user