Skip to content

[Phase 3] Arbitrage Detection #9

@WFord26

Description

@WFord26

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 profit

Middle 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 opportunities
  • GET /api/arbitrage/history - Historical arbitrage opportunities
  • GET /api/arbitrage/:id - Details for specific opportunity
  • POST /api/arbitrage/:id/take - Mark opportunity as taken
  • GET /api/arbitrage/stats - User's arbitrage stats
  • GET /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

Metadata

Metadata

Assignees

No one assigned

    Labels

    analyticsAdvanced analytics featuresenhancementNew feature or requestphase-3Phase 3: Advanced Features

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions