Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
385
vendor/ruvector/examples/neural-trader/specialized/news-trading.js
vendored
Normal file
385
vendor/ruvector/examples/neural-trader/specialized/news-trading.js
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* News-Driven Trading with Neural Trader
|
||||
*
|
||||
* Demonstrates using @neural-trader/news-trading for:
|
||||
* - Real-time news sentiment analysis
|
||||
* - Event-driven trading strategies
|
||||
* - Earnings reaction patterns
|
||||
* - Breaking news detection
|
||||
* - Social media sentiment integration
|
||||
*/
|
||||
|
||||
// News trading configuration
|
||||
const newsConfig = {
|
||||
// Sentiment thresholds
|
||||
sentiment: {
|
||||
strongBullish: 0.8,
|
||||
bullish: 0.6,
|
||||
neutral: [0.4, 0.6],
|
||||
bearish: 0.4,
|
||||
strongBearish: 0.2
|
||||
},
|
||||
|
||||
// Trading parameters
|
||||
trading: {
|
||||
maxPositionSize: 0.05,
|
||||
newsReactionWindow: 300, // 5 minutes
|
||||
stopLoss: 0.02,
|
||||
takeProfit: 0.05,
|
||||
holdingPeriodMax: 3600 // 1 hour max
|
||||
},
|
||||
|
||||
// News sources
|
||||
sources: ['Reuters', 'Bloomberg', 'CNBC', 'WSJ', 'Twitter/X', 'Reddit'],
|
||||
|
||||
// Event types
|
||||
eventTypes: ['earnings', 'fda', 'merger', 'macro', 'executive', 'legal']
|
||||
};
|
||||
|
||||
// Sample news events
|
||||
const sampleNews = [
|
||||
{
|
||||
id: 'news_001',
|
||||
timestamp: '2024-12-31T09:30:00Z',
|
||||
headline: 'Apple Reports Record Q4 Revenue, Beats Estimates by 8%',
|
||||
source: 'Bloomberg',
|
||||
symbols: ['AAPL'],
|
||||
eventType: 'earnings',
|
||||
sentiment: {
|
||||
score: 0.85,
|
||||
magnitude: 0.92,
|
||||
keywords: ['record', 'beats', 'revenue growth', 'strong demand']
|
||||
},
|
||||
priceImpact: {
|
||||
immediate: 0.035, // 3.5% immediate move
|
||||
t5min: 0.042,
|
||||
t15min: 0.038,
|
||||
t1hour: 0.045
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'news_002',
|
||||
timestamp: '2024-12-31T10:15:00Z',
|
||||
headline: 'Fed Officials Signal Pause in Rate Cuts Amid Inflation Concerns',
|
||||
source: 'Reuters',
|
||||
symbols: ['SPY', 'QQQ', 'TLT'],
|
||||
eventType: 'macro',
|
||||
sentiment: {
|
||||
score: 0.35,
|
||||
magnitude: 0.78,
|
||||
keywords: ['pause', 'inflation', 'concerns', 'hawkish']
|
||||
},
|
||||
priceImpact: {
|
||||
immediate: -0.012,
|
||||
t5min: -0.018,
|
||||
t15min: -0.015,
|
||||
t1hour: -0.008
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'news_003',
|
||||
timestamp: '2024-12-31T11:00:00Z',
|
||||
headline: 'NVIDIA Announces Next-Gen AI Chip With 3x Performance Improvement',
|
||||
source: 'CNBC',
|
||||
symbols: ['NVDA', 'AMD', 'INTC'],
|
||||
eventType: 'product',
|
||||
sentiment: {
|
||||
score: 0.88,
|
||||
magnitude: 0.95,
|
||||
keywords: ['next-gen', 'breakthrough', 'AI', 'performance']
|
||||
},
|
||||
priceImpact: {
|
||||
immediate: 0.048,
|
||||
t5min: 0.062,
|
||||
t15min: 0.055,
|
||||
t1hour: 0.071
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'news_004',
|
||||
timestamp: '2024-12-31T12:30:00Z',
|
||||
headline: 'Tesla Recalls 500,000 Vehicles Over Safety Concerns',
|
||||
source: 'WSJ',
|
||||
symbols: ['TSLA'],
|
||||
eventType: 'legal',
|
||||
sentiment: {
|
||||
score: 0.22,
|
||||
magnitude: 0.85,
|
||||
keywords: ['recall', 'safety', 'concerns', 'investigation']
|
||||
},
|
||||
priceImpact: {
|
||||
immediate: -0.028,
|
||||
t5min: -0.035,
|
||||
t15min: -0.032,
|
||||
t1hour: -0.025
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'news_005',
|
||||
timestamp: '2024-12-31T13:45:00Z',
|
||||
headline: 'Biotech Company Receives FDA Fast Track Designation for Cancer Drug',
|
||||
source: 'Reuters',
|
||||
symbols: ['MRNA'],
|
||||
eventType: 'fda',
|
||||
sentiment: {
|
||||
score: 0.82,
|
||||
magnitude: 0.88,
|
||||
keywords: ['FDA', 'fast track', 'breakthrough', 'cancer']
|
||||
},
|
||||
priceImpact: {
|
||||
immediate: 0.085,
|
||||
t5min: 0.125,
|
||||
t15min: 0.098,
|
||||
t1hour: 0.115
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Social media sentiment data
|
||||
const socialSentiment = {
|
||||
'AAPL': { twitter: 0.72, reddit: 0.68, mentions: 15420, trend: 'rising' },
|
||||
'NVDA': { twitter: 0.85, reddit: 0.82, mentions: 28350, trend: 'rising' },
|
||||
'TSLA': { twitter: 0.45, reddit: 0.38, mentions: 42100, trend: 'falling' },
|
||||
'MRNA': { twitter: 0.78, reddit: 0.75, mentions: 8920, trend: 'rising' },
|
||||
'SPY': { twitter: 0.52, reddit: 0.55, mentions: 35600, trend: 'stable' }
|
||||
};
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(70));
|
||||
console.log('News-Driven Trading - Neural Trader');
|
||||
console.log('='.repeat(70));
|
||||
console.log();
|
||||
|
||||
// 1. Display configuration
|
||||
console.log('1. Trading Configuration:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Reaction Window: ${newsConfig.trading.newsReactionWindow}s (${newsConfig.trading.newsReactionWindow / 60}min)`);
|
||||
console.log(` Max Position: ${newsConfig.trading.maxPositionSize * 100}%`);
|
||||
console.log(` Stop Loss: ${newsConfig.trading.stopLoss * 100}%`);
|
||||
console.log(` Take Profit: ${newsConfig.trading.takeProfit * 100}%`);
|
||||
console.log(` News Sources: ${newsConfig.sources.join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// 2. News feed analysis
|
||||
console.log('2. Live News Feed Analysis:');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
for (const news of sampleNews) {
|
||||
console.log();
|
||||
console.log(` 📰 ${news.headline}`);
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Time: ${news.timestamp} | Source: ${news.source} | Type: ${news.eventType}`);
|
||||
console.log(` Symbols: ${news.symbols.join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// Sentiment analysis
|
||||
const sentimentLabel = getSentimentLabel(news.sentiment.score);
|
||||
console.log(' Sentiment Analysis:');
|
||||
console.log(` Score: ${news.sentiment.score.toFixed(2)} (${sentimentLabel})`);
|
||||
console.log(` Magnitude: ${news.sentiment.magnitude.toFixed(2)}`);
|
||||
console.log(` Keywords: ${news.sentiment.keywords.join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// Price impact analysis
|
||||
console.log(' Price Impact Analysis:');
|
||||
console.log(' Window | Expected Move | Confidence | Signal');
|
||||
console.log(' ' + '-'.repeat(50));
|
||||
|
||||
const impacts = [
|
||||
{ window: 'Immediate', move: news.priceImpact.immediate, conf: 0.85 },
|
||||
{ window: 'T+5 min', move: news.priceImpact.t5min, conf: 0.78 },
|
||||
{ window: 'T+15 min', move: news.priceImpact.t15min, conf: 0.65 },
|
||||
{ window: 'T+1 hour', move: news.priceImpact.t1hour, conf: 0.52 }
|
||||
];
|
||||
|
||||
impacts.forEach(impact => {
|
||||
const moveStr = impact.move >= 0 ? `+${(impact.move * 100).toFixed(2)}%` : `${(impact.move * 100).toFixed(2)}%`;
|
||||
const signal = getSignal(impact.move, impact.conf);
|
||||
console.log(` ${impact.window.padEnd(12)} | ${moveStr.padStart(13)} | ${(impact.conf * 100).toFixed(0).padStart(9)}% | ${signal}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// Trading recommendation
|
||||
const recommendation = generateRecommendation(news);
|
||||
console.log(' 📊 Trading Recommendation:');
|
||||
console.log(` Action: ${recommendation.action.toUpperCase()}`);
|
||||
console.log(` Symbol: ${recommendation.symbol}`);
|
||||
console.log(` Size: ${(recommendation.size * 100).toFixed(1)}% of portfolio`);
|
||||
console.log(` Stop Loss: ${(recommendation.stopLoss * 100).toFixed(2)}%`);
|
||||
console.log(` Target: ${(recommendation.target * 100).toFixed(2)}%`);
|
||||
console.log(` Expected: ${(recommendation.expectedReturn * 100).toFixed(2)}%`);
|
||||
}
|
||||
|
||||
// 3. Social sentiment dashboard
|
||||
console.log();
|
||||
console.log('3. Social Media Sentiment Dashboard:');
|
||||
console.log('='.repeat(70));
|
||||
console.log(' Symbol | Twitter | Reddit | Mentions | Trend | Combined');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
Object.entries(socialSentiment).forEach(([symbol, data]) => {
|
||||
const combined = (data.twitter + data.reddit) / 2;
|
||||
const twitterStr = getSentimentEmoji(data.twitter) + ` ${(data.twitter * 100).toFixed(0)}%`;
|
||||
const redditStr = getSentimentEmoji(data.reddit) + ` ${(data.reddit * 100).toFixed(0)}%`;
|
||||
|
||||
console.log(` ${symbol.padEnd(6)} | ${twitterStr.padEnd(7)} | ${redditStr.padEnd(7)} | ${data.mentions.toLocaleString().padStart(8)} | ${data.trend.padEnd(7)} | ${getSentimentLabel(combined)}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 4. Event-type performance
|
||||
console.log('4. Historical Performance by Event Type:');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
const eventStats = calculateEventTypeStats(sampleNews);
|
||||
console.log(' Event Type | Avg Move | Win Rate | Avg Duration | Best Time');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
Object.entries(eventStats).forEach(([type, stats]) => {
|
||||
console.log(` ${type.padEnd(12)} | ${formatMove(stats.avgMove).padStart(9)} | ${(stats.winRate * 100).toFixed(0).padStart(7)}% | ${stats.avgDuration.padStart(12)} | ${stats.bestTime}`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 5. Pattern recognition
|
||||
console.log('5. Historical Pattern Recognition (RuVector):');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
const patterns = findSimilarPatterns(sampleNews[0]);
|
||||
console.log(` Finding patterns similar to: "${sampleNews[0].headline.substring(0, 50)}..."`);
|
||||
console.log();
|
||||
console.log(' Similar Events:');
|
||||
patterns.forEach((pattern, i) => {
|
||||
console.log(` ${i + 1}. ${pattern.headline.substring(0, 45)}...`);
|
||||
console.log(` Date: ${pattern.date} | Move: ${formatMove(pattern.move)} | Similarity: ${(pattern.similarity * 100).toFixed(0)}%`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 6. Real-time alerts
|
||||
console.log('6. Alert Configuration:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(' Active Alerts:');
|
||||
console.log(' - Earnings beats/misses > 5%');
|
||||
console.log(' - FDA decisions for watchlist stocks');
|
||||
console.log(' - Sentiment score change > 0.3 in 15 min');
|
||||
console.log(' - Unusual social media volume (3x average)');
|
||||
console.log(' - Breaking news with magnitude > 0.8');
|
||||
console.log();
|
||||
|
||||
// 7. Performance summary
|
||||
console.log('7. News Trading Performance Summary:');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
const perfSummary = calculatePerformance(sampleNews);
|
||||
console.log(` Total Trades: ${perfSummary.totalTrades}`);
|
||||
console.log(` Win Rate: ${(perfSummary.winRate * 100).toFixed(1)}%`);
|
||||
console.log(` Avg Winner: +${(perfSummary.avgWin * 100).toFixed(2)}%`);
|
||||
console.log(` Avg Loser: ${(perfSummary.avgLoss * 100).toFixed(2)}%`);
|
||||
console.log(` Profit Factor: ${perfSummary.profitFactor.toFixed(2)}`);
|
||||
console.log(` Sharpe (news): ${perfSummary.sharpe.toFixed(2)}`);
|
||||
console.log(` Best Event Type: ${perfSummary.bestEventType}`);
|
||||
console.log();
|
||||
|
||||
console.log('='.repeat(70));
|
||||
console.log('News-driven trading analysis completed!');
|
||||
console.log('='.repeat(70));
|
||||
}
|
||||
|
||||
// Get sentiment label from score
|
||||
function getSentimentLabel(score) {
|
||||
if (score >= newsConfig.sentiment.strongBullish) return 'STRONG BULLISH';
|
||||
if (score >= newsConfig.sentiment.bullish) return 'Bullish';
|
||||
if (score <= newsConfig.sentiment.strongBearish) return 'STRONG BEARISH';
|
||||
if (score <= newsConfig.sentiment.bearish) return 'Bearish';
|
||||
return 'Neutral';
|
||||
}
|
||||
|
||||
// Get sentiment emoji
|
||||
function getSentimentEmoji(score) {
|
||||
if (score >= 0.7) return '🟢';
|
||||
if (score >= 0.55) return '🟡';
|
||||
if (score <= 0.3) return '🔴';
|
||||
if (score <= 0.45) return '🟠';
|
||||
return '⚪';
|
||||
}
|
||||
|
||||
// Get trading signal
|
||||
function getSignal(move, confidence) {
|
||||
const absMove = Math.abs(move);
|
||||
if (absMove < 0.01 || confidence < 0.5) return 'HOLD';
|
||||
if (move > 0 && confidence > 0.7) return '🟢 LONG';
|
||||
if (move > 0) return '🟡 WEAK LONG';
|
||||
if (move < 0 && confidence > 0.7) return '🔴 SHORT';
|
||||
return '🟠 WEAK SHORT';
|
||||
}
|
||||
|
||||
// Format price move
|
||||
function formatMove(move) {
|
||||
return move >= 0 ? `+${(move * 100).toFixed(2)}%` : `${(move * 100).toFixed(2)}%`;
|
||||
}
|
||||
|
||||
// Generate trading recommendation
|
||||
function generateRecommendation(news) {
|
||||
const mainSymbol = news.symbols[0];
|
||||
const sentiment = news.sentiment.score;
|
||||
const magnitude = news.sentiment.magnitude;
|
||||
const expectedMove = news.priceImpact.t5min;
|
||||
|
||||
let action = 'HOLD';
|
||||
let size = 0;
|
||||
|
||||
if (sentiment >= newsConfig.sentiment.bullish && magnitude > 0.7) {
|
||||
action = 'BUY';
|
||||
size = Math.min(magnitude * newsConfig.trading.maxPositionSize, newsConfig.trading.maxPositionSize);
|
||||
} else if (sentiment <= newsConfig.sentiment.bearish && magnitude > 0.7) {
|
||||
action = 'SHORT';
|
||||
size = Math.min(magnitude * newsConfig.trading.maxPositionSize, newsConfig.trading.maxPositionSize);
|
||||
}
|
||||
|
||||
return {
|
||||
action,
|
||||
symbol: mainSymbol,
|
||||
size,
|
||||
stopLoss: action === 'BUY' ? -newsConfig.trading.stopLoss : newsConfig.trading.stopLoss,
|
||||
target: action === 'BUY' ? newsConfig.trading.takeProfit : -newsConfig.trading.takeProfit,
|
||||
expectedReturn: expectedMove * (action === 'SHORT' ? -1 : 1)
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate event type statistics
|
||||
function calculateEventTypeStats(news) {
|
||||
const stats = {
|
||||
earnings: { avgMove: 0.042, winRate: 0.68, avgDuration: '45 min', bestTime: 'Pre-market' },
|
||||
fda: { avgMove: 0.085, winRate: 0.72, avgDuration: '2-4 hours', bestTime: 'Any' },
|
||||
macro: { avgMove: 0.015, winRate: 0.55, avgDuration: '1-2 hours', bestTime: 'Morning' },
|
||||
merger: { avgMove: 0.12, winRate: 0.65, avgDuration: '1 day', bestTime: 'Pre-market' },
|
||||
product: { avgMove: 0.048, winRate: 0.62, avgDuration: '1 hour', bestTime: 'Market hours' },
|
||||
legal: { avgMove: -0.025, winRate: 0.45, avgDuration: '30 min', bestTime: 'Any' }
|
||||
};
|
||||
return stats;
|
||||
}
|
||||
|
||||
// Find similar historical patterns
|
||||
function findSimilarPatterns(currentNews) {
|
||||
// Simulated pattern matching (RuVector integration)
|
||||
return [
|
||||
{ headline: 'Apple Q3 2024 earnings beat by 6%, iPhone sales strong', date: '2024-08-01', move: 0.038, similarity: 0.92 },
|
||||
{ headline: 'Apple Q2 2024 revenue exceeds expectations', date: '2024-05-02', move: 0.029, similarity: 0.87 },
|
||||
{ headline: 'Apple Q4 2023 sets new revenue record', date: '2023-11-02', move: 0.045, similarity: 0.84 },
|
||||
{ headline: 'Apple Services revenue beats by 10%', date: '2024-02-01', move: 0.032, similarity: 0.79 }
|
||||
];
|
||||
}
|
||||
|
||||
// Calculate overall performance
|
||||
function calculatePerformance(news) {
|
||||
return {
|
||||
totalTrades: 127,
|
||||
winRate: 0.64,
|
||||
avgWin: 0.032,
|
||||
avgLoss: -0.018,
|
||||
profitFactor: 1.85,
|
||||
sharpe: 1.92,
|
||||
bestEventType: 'FDA Approvals'
|
||||
};
|
||||
}
|
||||
|
||||
// Run the example
|
||||
main().catch(console.error);
|
||||
317
vendor/ruvector/examples/neural-trader/specialized/prediction-markets.js
vendored
Normal file
317
vendor/ruvector/examples/neural-trader/specialized/prediction-markets.js
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Prediction Markets with Neural Trader
|
||||
*
|
||||
* Demonstrates using @neural-trader/prediction-markets for:
|
||||
* - Polymarket integration
|
||||
* - Expected value calculations
|
||||
* - Market making strategies
|
||||
* - Arbitrage across platforms
|
||||
* - Event probability analysis
|
||||
*/
|
||||
|
||||
// Prediction market configuration
|
||||
const marketConfig = {
|
||||
platforms: ['Polymarket', 'Kalshi', 'PredictIt'],
|
||||
initialCapital: 10000,
|
||||
maxPositionSize: 0.10,
|
||||
minEdge: 0.03,
|
||||
fees: {
|
||||
Polymarket: 0.02,
|
||||
Kalshi: 0.02,
|
||||
PredictIt: 0.10
|
||||
}
|
||||
};
|
||||
|
||||
// Sample market data
|
||||
const predictionMarkets = [
|
||||
{
|
||||
id: 'fed-rate-jan-2025',
|
||||
question: 'Will the Fed cut rates in January 2025?',
|
||||
category: 'Economics',
|
||||
endDate: '2025-01-31',
|
||||
platforms: {
|
||||
Polymarket: { yes: 0.22, no: 0.78, volume: 1250000 },
|
||||
Kalshi: { yes: 0.24, no: 0.76, volume: 850000 }
|
||||
},
|
||||
modelProbability: 0.18
|
||||
},
|
||||
{
|
||||
id: 'btc-100k-jan-2025',
|
||||
question: 'Will Bitcoin reach $100,000 by January 31, 2025?',
|
||||
category: 'Crypto',
|
||||
endDate: '2025-01-31',
|
||||
platforms: {
|
||||
Polymarket: { yes: 0.65, no: 0.35, volume: 5200000 },
|
||||
Kalshi: { yes: 0.62, no: 0.38, volume: 2100000 }
|
||||
},
|
||||
modelProbability: 0.70
|
||||
},
|
||||
{
|
||||
id: 'sp500-6000-q1-2025',
|
||||
question: 'Will S&P 500 close above 6000 in Q1 2025?',
|
||||
category: 'Markets',
|
||||
endDate: '2025-03-31',
|
||||
platforms: {
|
||||
Polymarket: { yes: 0.58, no: 0.42, volume: 980000 },
|
||||
Kalshi: { yes: 0.55, no: 0.45, volume: 1450000 }
|
||||
},
|
||||
modelProbability: 0.62
|
||||
},
|
||||
{
|
||||
id: 'ai-regulation-2025',
|
||||
question: 'Will the US pass major AI regulation in 2025?',
|
||||
category: 'Politics',
|
||||
endDate: '2025-12-31',
|
||||
platforms: {
|
||||
Polymarket: { yes: 0.35, no: 0.65, volume: 750000 },
|
||||
PredictIt: { yes: 0.38, no: 0.62, volume: 420000 }
|
||||
},
|
||||
modelProbability: 0.28
|
||||
},
|
||||
{
|
||||
id: 'eth-merge-upgrade-2025',
|
||||
question: 'Will Ethereum complete Pectra upgrade by March 2025?',
|
||||
category: 'Crypto',
|
||||
endDate: '2025-03-31',
|
||||
platforms: {
|
||||
Polymarket: { yes: 0.72, no: 0.28, volume: 890000 }
|
||||
},
|
||||
modelProbability: 0.75
|
||||
}
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(70));
|
||||
console.log('Prediction Markets Analysis - Neural Trader');
|
||||
console.log('='.repeat(70));
|
||||
console.log();
|
||||
|
||||
// 1. Display configuration
|
||||
console.log('1. Configuration:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Capital: $${marketConfig.initialCapital.toLocaleString()}`);
|
||||
console.log(` Max Position: ${marketConfig.maxPositionSize * 100}%`);
|
||||
console.log(` Min Edge: ${marketConfig.minEdge * 100}%`);
|
||||
console.log(` Platforms: ${marketConfig.platforms.join(', ')}`);
|
||||
console.log();
|
||||
|
||||
// 2. Market overview
|
||||
console.log('2. Market Overview:');
|
||||
console.log('-'.repeat(70));
|
||||
console.log(' Market | Category | End Date | Volume');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
predictionMarkets.forEach(market => {
|
||||
const totalVolume = Object.values(market.platforms)
|
||||
.reduce((sum, p) => sum + p.volume, 0);
|
||||
console.log(` ${market.question.substring(0, 40).padEnd(40)} | ${market.category.padEnd(10)} | ${market.endDate} | $${(totalVolume / 1e6).toFixed(2)}M`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 3. Analyze each market
|
||||
console.log('3. Market Analysis:');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
const opportunities = [];
|
||||
|
||||
for (const market of predictionMarkets) {
|
||||
console.log();
|
||||
console.log(` 📊 ${market.question}`);
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Category: ${market.category} | End: ${market.endDate} | Model P(Yes): ${(market.modelProbability * 100).toFixed(0)}%`);
|
||||
console.log();
|
||||
|
||||
// Platform comparison
|
||||
console.log(' Platform | Yes Price | No Price | Implied P | Spread | Volume');
|
||||
console.log(' ' + '-'.repeat(60));
|
||||
|
||||
for (const [platform, data] of Object.entries(market.platforms)) {
|
||||
const impliedYes = data.yes;
|
||||
const spread = Math.abs(data.yes + data.no - 1);
|
||||
console.log(` ${platform.padEnd(12)} | $${data.yes.toFixed(2).padStart(8)} | $${data.no.toFixed(2).padStart(8)} | ${(impliedYes * 100).toFixed(1).padStart(8)}% | ${(spread * 100).toFixed(1)}% | $${(data.volume / 1e6).toFixed(2)}M`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Calculate EV opportunities
|
||||
console.log(' Expected Value Analysis:');
|
||||
console.log(' Position | Platform | Price | Model P | EV | Action');
|
||||
console.log(' ' + '-'.repeat(60));
|
||||
|
||||
for (const [platform, data] of Object.entries(market.platforms)) {
|
||||
const fee = marketConfig.fees[platform];
|
||||
|
||||
// YES position
|
||||
const yesEV = calculateEV(market.modelProbability, data.yes, fee);
|
||||
const yesAction = yesEV > marketConfig.minEdge ? '✅ BUY YES' : 'PASS';
|
||||
|
||||
console.log(` ${'YES'.padEnd(11)} | ${platform.padEnd(12)} | $${data.yes.toFixed(2).padStart(5)} | ${(market.modelProbability * 100).toFixed(0).padStart(6)}% | ${formatEV(yesEV).padStart(8)} | ${yesAction}`);
|
||||
|
||||
// NO position
|
||||
const noEV = calculateEV(1 - market.modelProbability, data.no, fee);
|
||||
const noAction = noEV > marketConfig.minEdge ? '✅ BUY NO' : 'PASS';
|
||||
|
||||
console.log(` ${'NO'.padEnd(11)} | ${platform.padEnd(12)} | $${data.no.toFixed(2).padStart(5)} | ${((1 - market.modelProbability) * 100).toFixed(0).padStart(6)}% | ${formatEV(noEV).padStart(8)} | ${noAction}`);
|
||||
|
||||
// Track opportunities
|
||||
if (yesEV > marketConfig.minEdge) {
|
||||
opportunities.push({
|
||||
market: market.question,
|
||||
platform,
|
||||
position: 'YES',
|
||||
price: data.yes,
|
||||
ev: yesEV,
|
||||
modelProb: market.modelProbability
|
||||
});
|
||||
}
|
||||
if (noEV > marketConfig.minEdge) {
|
||||
opportunities.push({
|
||||
market: market.question,
|
||||
platform,
|
||||
position: 'NO',
|
||||
price: data.no,
|
||||
ev: noEV,
|
||||
modelProb: 1 - market.modelProbability
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Cross-platform arbitrage check
|
||||
if (Object.keys(market.platforms).length > 1) {
|
||||
const arbResult = checkCrossArbitrage(market);
|
||||
if (arbResult.hasArbitrage) {
|
||||
console.log(` 🎯 ARBITRAGE: Buy YES on ${arbResult.yesPlatform} ($${arbResult.yesPrice.toFixed(2)})`);
|
||||
console.log(` Buy NO on ${arbResult.noPlatform} ($${arbResult.noPrice.toFixed(2)})`);
|
||||
console.log(` Guaranteed profit: ${(arbResult.profit * 100).toFixed(2)}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Portfolio recommendations
|
||||
console.log();
|
||||
console.log('4. Portfolio Recommendations:');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
if (opportunities.length === 0) {
|
||||
console.log(' No positions currently meet the minimum edge criteria.');
|
||||
} else {
|
||||
// Sort by EV
|
||||
opportunities.sort((a, b) => b.ev - a.ev);
|
||||
|
||||
console.log(' Rank | Market | Position | Platform | EV | Size');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
let totalAllocation = 0;
|
||||
opportunities.slice(0, 5).forEach((opp, i) => {
|
||||
const kelly = calculateKelly(opp.modelProb, opp.price);
|
||||
const size = Math.min(kelly * 0.25, marketConfig.maxPositionSize) * marketConfig.initialCapital;
|
||||
totalAllocation += size;
|
||||
|
||||
console.log(` ${(i + 1).toString().padStart(4)} | ${opp.market.substring(0, 38).padEnd(38)} | ${opp.position.padEnd(8)} | ${opp.platform.padEnd(12)} | ${formatEV(opp.ev).padStart(7)} | $${size.toFixed(0)}`);
|
||||
});
|
||||
|
||||
console.log('-'.repeat(70));
|
||||
console.log(` Total Allocation: $${totalAllocation.toFixed(0)} (${(totalAllocation / marketConfig.initialCapital * 100).toFixed(1)}% of capital)`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// 5. Market making opportunities
|
||||
console.log('5. Market Making Opportunities:');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
console.log(' Markets with high spread (>5%):');
|
||||
predictionMarkets.forEach(market => {
|
||||
for (const [platform, data] of Object.entries(market.platforms)) {
|
||||
const spread = Math.abs(data.yes + data.no - 1);
|
||||
if (spread > 0.05) {
|
||||
console.log(` - ${market.question.substring(0, 45)} (${platform}): ${(spread * 100).toFixed(1)}% spread`);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log();
|
||||
|
||||
// 6. Risk analysis
|
||||
console.log('6. Risk Analysis:');
|
||||
console.log('-'.repeat(70));
|
||||
|
||||
const categoryExposure = {};
|
||||
opportunities.forEach(opp => {
|
||||
const market = predictionMarkets.find(m => m.question === opp.market);
|
||||
if (market) {
|
||||
categoryExposure[market.category] = (categoryExposure[market.category] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(' Category concentration:');
|
||||
Object.entries(categoryExposure).forEach(([cat, count]) => {
|
||||
console.log(` - ${cat}: ${count} positions`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
console.log(' Correlation warnings:');
|
||||
console.log(' - BTC $100K and S&P 6000 may be correlated (risk-on assets)');
|
||||
console.log(' - Consider hedging or reducing combined exposure');
|
||||
console.log();
|
||||
|
||||
console.log('='.repeat(70));
|
||||
console.log('Prediction markets analysis completed!');
|
||||
console.log('='.repeat(70));
|
||||
}
|
||||
|
||||
// Calculate Expected Value
|
||||
function calculateEV(trueProb, price, fee) {
|
||||
const netProfit = (1 - fee) / price - 1;
|
||||
const ev = trueProb * netProfit - (1 - trueProb);
|
||||
return ev;
|
||||
}
|
||||
|
||||
// Format EV for display
|
||||
function formatEV(ev) {
|
||||
const pct = ev * 100;
|
||||
return pct >= 0 ? `+${pct.toFixed(1)}%` : `${pct.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
// Calculate Kelly Criterion for prediction markets
|
||||
function calculateKelly(prob, price) {
|
||||
const b = 1 / price - 1; // Potential profit per dollar
|
||||
const p = prob;
|
||||
const q = 1 - prob;
|
||||
|
||||
const kelly = (b * p - q) / b;
|
||||
return Math.max(0, kelly);
|
||||
}
|
||||
|
||||
// Check for cross-platform arbitrage
|
||||
function checkCrossArbitrage(market) {
|
||||
const platforms = Object.entries(market.platforms);
|
||||
if (platforms.length < 2) return { hasArbitrage: false };
|
||||
|
||||
let bestYes = { price: 1, platform: '' };
|
||||
let bestNo = { price: 1, platform: '' };
|
||||
|
||||
platforms.forEach(([platform, data]) => {
|
||||
if (data.yes < bestYes.price) {
|
||||
bestYes = { price: data.yes, platform };
|
||||
}
|
||||
if (data.no < bestNo.price) {
|
||||
bestNo = { price: data.no, platform };
|
||||
}
|
||||
});
|
||||
|
||||
const totalCost = bestYes.price + bestNo.price;
|
||||
if (totalCost < 1) {
|
||||
return {
|
||||
hasArbitrage: true,
|
||||
yesPlatform: bestYes.platform,
|
||||
yesPrice: bestYes.price,
|
||||
noPlatform: bestNo.platform,
|
||||
noPrice: bestNo.price,
|
||||
profit: 1 / totalCost - 1
|
||||
};
|
||||
}
|
||||
|
||||
return { hasArbitrage: false };
|
||||
}
|
||||
|
||||
// Run the example
|
||||
main().catch(console.error);
|
||||
335
vendor/ruvector/examples/neural-trader/specialized/sports-betting.js
vendored
Normal file
335
vendor/ruvector/examples/neural-trader/specialized/sports-betting.js
vendored
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