-
Notifications
You must be signed in to change notification settings - Fork 0
[Phase 3] Arbitrage Detection #9
Copy link
Copy link
Open
Labels
analyticsAdvanced analytics featuresAdvanced analytics featuresenhancementNew feature or requestNew feature or requestphase-3Phase 3: Advanced FeaturesPhase 3: Advanced Features
Description
name: "Phase 3: Arbitrage Detection"
about: Find guaranteed profit opportunities across bookmakers
title: "[Phase 3] Arbitrage Detection"
labels: enhancement, phase-3, analytics, advanced
assignees: ''
Overview
Detect arbitrage opportunities (guaranteed profit by betting all sides across different bookmakers) and middle opportunities (chance to win both sides of a bet).
Business Value
- Guaranteed Profit: Arbitrage is risk-free money
- Market Inefficiency: Shows when bookmakers disagree significantly
- User Value: Premium feature for advanced users
- Real-time Alerts: Time-sensitive opportunities
Technical Requirements
Database Changes
model ArbitrageOpportunity {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
gameId String @map("game_id") @db.Uuid
detectedAt DateTime @default(now()) @map("detected_at") @db.Timestamptz(6)
expiresAt DateTime @map("expires_at") @db.Timestamptz(6)
// Opportunity type
arbType String @map("arb_type") @db.VarChar(20) // two-way, three-way, middle
marketType String @map("market_type") @db.VarChar(20)
// Profitability
profitPercentage Decimal @map("profit_percentage") @db.Decimal(5,2)
stake Decimal @db.Decimal(10,2)
expectedProfit Decimal @map("expected_profit") @db.Decimal(10,2)
// Legs
legs Json // [{bookmaker, selection, odds, stake, toWin}]
// Risk assessment
riskLevel String @map("risk_level") @db.VarChar(20) // low, medium, high
limitRisk Boolean @map("limit_risk") // Risk of hitting betting limits
oddsDrift Decimal? @map("odds_drift") @db.Decimal(5,2) // How fast odds moving
// Status
status String @default("active") @db.VarChar(20)
takenAt DateTime? @map("taken_at") @db.Timestamptz(6)
userId String? @map("user_id") @db.Uuid
actualProfit Decimal? @map("actual_profit") @db.Decimal(10,2)
game Game @relation(fields: [gameId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@index([gameId])
@@index([status])
@@index([profitPercentage])
@@index([detectedAt])
@@map("arbitrage_opportunities")
}Backend Services
File: src/services/arbitrage.service.ts
class ArbitrageService {
async scanForArbitrage(): Promise<Opportunity[]>
async calculateStakes(odds: number[], totalStake: number): Promise<number[]>
async assessRisk(opportunity: Opportunity): Promise<RiskAssessment>
async trackOpportunity(id: string): Promise<void>
async notifyUsers(opportunity: Opportunity): Promise<void>
}Arbitrage Detection Algorithm
Two-Way Arbitrage (moneyline, spread, total):
function detectTwoWayArbitrage(game: Game, marketType: string): Opportunity | null {
const allOdds = getOddsForMarket(game, marketType);
// Find best odds for each side
const bestHome = Math.max(...allOdds.map(o => o.homePrice));
const bestAway = Math.max(...allOdds.map(o => o.awayPrice));
// Convert to implied probabilities
const homeProb = toImpliedProbability(bestHome);
const awayProb = toImpliedProbability(bestAway);
// Check if arbitrage exists
const totalProb = homeProb + awayProb;
if (totalProb < 1.0) {
// Arbitrage exists!
const profitPct = ((1 / totalProb) - 1) * 100;
return {
type: 'two-way',
profitPercentage: profitPct,
legs: [
{ side: 'home', odds: bestHome, bookmaker: findBookmaker(allOdds, 'home', bestHome) },
{ side: 'away', odds: bestAway, bookmaker: findBookmaker(allOdds, 'away', bestAway) }
]
};
}
return null;
}Stake Calculation (to guarantee equal profit):
function calculateOptimalStakes(odds: number[], totalStake: number): number[] {
const probs = odds.map(o => toImpliedProbability(o));
const totalProb = probs.reduce((a, b) => a + b);
return probs.map(prob => (prob / totalProb) * totalStake);
}
// Example: $100 total stake, odds: +120, -110
// Home stake: $47.62, Away stake: $52.38
// Both outcomes win: ~$4.50 profitMiddle Detection (win both sides):
function detectMiddle(game: Game): Middle | null {
const allSpreads = getSpreadOdds(game);
// Find spread discrepancies
for (const spread1 of allSpreads) {
for (const spread2 of allSpreads) {
if (spread1.bookmaker === spread2.bookmaker) continue;
const gap = Math.abs(spread1.homeSpread - spread2.awaySpread);
// Middle opportunity if gap >= 2 points
if (gap >= 2.0) {
const middleProb = estimateMiddleProbability(gap);
if (middleProb > 0.10) { // 10%+ chance both win
return {
type: 'middle',
spread1: spread1,
spread2: spread2,
gap: gap,
middleProb: middleProb,
expectedValue: calculateMiddleEV(spread1, spread2, middleProb)
};
}
}
}
}
return null;
}Real-time Scanning
Scheduled Job: Every 30 seconds
- Scan all games with multiple bookmaker odds
- Detect arbitrage and middle opportunities
- Calculate profitability
- Assess risk level
- Store in database
- Trigger notifications
Performance Optimization:
// Use parallel processing for speed
async function scanAllGames(): Promise<Opportunity[]> {
const upcomingGames = await getGamesWithOdds();
const opportunities = await Promise.all(
upcomingGames.map(game =>
Promise.all([
detectTwoWayArbitrage(game, 'h2h'),
detectTwoWayArbitrage(game, 'spreads'),
detectTwoWayArbitrage(game, 'totals'),
detectMiddle(game)
])
)
);
return opportunities.flat().filter(o => o !== null);
}Risk Assessment
function assessRisk(opportunity: Opportunity): RiskAssessment {
let risk = 'low';
const factors = [];
// Check if odds are changing rapidly
if (opportunity.oddsDrift > 5.0) {
risk = 'medium';
factors.push('Fast-moving odds');
}
// Check if bookmakers involved are reliable
const suspiciousBooks = ['unknown_book', 'new_book'];
if (opportunity.legs.some(leg => suspiciousBooks.includes(leg.bookmaker))) {
risk = 'high';
factors.push('Unreliable bookmaker');
}
// Check profit margin (too good to be true?)
if (opportunity.profitPercentage > 10.0) {
risk = 'high';
factors.push('Suspiciously high profit');
}
// Check time to game start
const minutesToStart = getMinutesToStart(opportunity.gameId);
if (minutesToStart < 15) {
risk = 'medium';
factors.push('Very close to start time');
}
return { risk, factors, limitRisk: estimateLimitRisk(opportunity) };
}API Endpoints
GET /api/arbitrage/live- Current arbitrage opportunitiesGET /api/arbitrage/history- Historical arbitrage opportunitiesGET /api/arbitrage/:id- Details for specific opportunityPOST /api/arbitrage/:id/take- Mark opportunity as takenGET /api/arbitrage/stats- User's arbitrage statsGET /api/arbitrage/calculator- Arbitrage calculator tool
Frontend Components
Arbitrage Dashboard: ArbitrageDashboard.tsx
┌────────────────────────────────────────────────┐
│ 🎯 Live Arbitrage Opportunities │
├────────────────────────────────────────────────┤
│ Lakers vs Warriors | NBA │
│ Profit: 3.2% | Stake: $1000 | Win: $32 │
│ ├─ DraftKings: Lakers -5.5 @ -110 ($523) │
│ └─ FanDuel: Warriors +6.5 @ -105 ($477) │
│ [VIEW DETAILS] [BET NOW] │
├────────────────────────────────────────────────┤
│ ... 4 more opportunities ... │
└────────────────────────────────────────────────┘
Arbitrage Calculator: ArbitrageCalculator.tsx
- Input: Total stake, odds for each side
- Output: Individual stakes, guaranteed profit
- Support for 2-way and 3-way bets
- Export bet instructions
Middle Finder: MiddleFinder.tsx
- Show all middle opportunities
- Calculate both-win probability
- Expected value calculation
- Historical middle success rate
Alert Settings: ArbitrageAlerts.tsx
- Minimum profit percentage
- Maximum stake
- Preferred bookmakers
- Sports to monitor
- Push notification settings
Notifications
Real-time Alerts:
- Browser push notifications
- Email for high-value arbs (>5% profit)
- SMS for premium users (optional)
- Webhook for integration with other tools
Alert Format:
🎯 ARBITRAGE: 4.2% Profit Available
Lakers vs Warriors - NBA
Stake $1000 → Win $42
Act now - odds changing!
[VIEW NOW]
Acceptance Criteria
- Database migration completed
- Arbitrage detection algorithm implemented
- Real-time scanning every 30 seconds
- Stake calculator working correctly
- Risk assessment accurate
- Dashboard showing live opportunities
- Push notifications functional
- Calculator tool available
- API endpoints documented
- Unit tests for detection logic
- Load testing for scanning performance
Dependencies
- Odds sync with multiple bookmakers (minimum 5)
- Fast odds updates (every 1-2 minutes)
- Market consensus calculations (Phase 2)
- Push notification service
Estimated Effort
- Backend: 8 days
- Frontend: 6 days
- Testing & Optimization: 4 days
- Total: 18 days
Success Metrics
- Detect 5-10 arbitrage opportunities per day
- 95%+ accuracy (no false positives)
- Alerts delivered within 10 seconds of detection
- Users successfully execute 50%+ of alerted arbs
- Average profit 2-5% per opportunity
Compliance & Legal
Important Considerations:
- Arbitrage is legal but bookmakers may limit accounts
- Disclose risks to users
- Some bookmakers prohibit arbitrage in ToS
- Recommend using different bookmakers for each leg
- Track limit/ban reports from users
Future Enhancements
- Machine learning for opportunity prediction
- Historical arbitrage ROI tracking
- Automated bet placement (with user approval)
- Cross-sport arbitrage detection
- Arbitrage portfolio optimization
- "Arb protection" insurance feature
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
analyticsAdvanced analytics featuresAdvanced analytics featuresenhancementNew feature or requestNew feature or requestphase-3Phase 3: Advanced FeaturesPhase 3: Advanced Features