-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
analyticsAdvanced analytics featuresAdvanced analytics featuresenhancementNew feature or requestNew feature or requestphase-2Phase 2: Market IntelligencePhase 2: Market Intelligence
Description
name: "Phase 2: Market Consensus Calculations"
about: Calculate market consensus and identify outlier bookmakers
title: "[Phase 2] Market Consensus Calculations"
labels: enhancement, phase-2, analytics
assignees: ''
Overview
Calculate true market consensus by aggregating all bookmaker lines, identifying outliers, and providing users with a single "market truth" reference point for each game.
Business Value
- Value Identification: Find bookmakers offering lines significantly better than consensus
- Market Efficiency: Understand when markets are efficient vs inefficient
- Outlier Betting: Target bookmakers consistently offering best value
- Fair Value Reference: Use consensus as baseline for bet evaluation
Technical Requirements
Database Changes
Already defined in Phase 1 Disagreement Detection, enhanced here:
model MarketConsensus {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
gameId String @map("game_id") @db.Uuid
marketType String @map("market_type") @db.VarChar(20)
calculatedAt DateTime @default(now()) @map("calculated_at") @db.Timestamptz(6)
// Consensus metrics
consensusLine Decimal @map("consensus_line") @db.Decimal(5,1)
consensusPrice Int @map("consensus_price") // American odds
medianLine Decimal @map("median_line") @db.Decimal(5,1)
meanLine Decimal @map("mean_line") @db.Decimal(5,1)
mode Line Decimal? @map("mode_line") @db.Decimal(5,1)
// Dispersion
standardDeviation Decimal @map("standard_deviation") @db.Decimal(5,2)
range Decimal @db.Decimal(5,2) // max - min
interquartileRange Decimal @map("interquartile_range") @db.Decimal(5,2)
// Outliers
outlierBookmakers Json @map("outlier_bookmakers")
bestValueSide String @map("best_value_side") @db.VarChar(20)
bestValueBookmaker String @map("best_value_bookmaker") @db.VarChar(50)
bestValueLine Decimal @map("best_value_line") @db.Decimal(5,1)
// Quality metrics
bookmakerCount Int @map("bookmaker_count")
sharpBookWeight Decimal @map("sharp_book_weight") @db.Decimal(5,2)
disagreementScore Int @map("disagreement_score")
game Game @relation(fields: [gameId], references: [id])
@@index([gameId])
@@index([marketType])
@@index([calculatedAt])
@@map("market_consensus")
}Backend Services
File: src/services/market-consensus.service.ts
class MarketConsensusService {
async calculateConsensus(gameId: string, marketType: string): Promise<Consensus>
async identifyOutliers(gameId: string): Promise<Outlier[]>
async findBestValue(gameId: string, side: string): Promise<BestLine>
async trackConsensusMovement(gameId: string): Promise<Movement[]>
async generateConsensusReport(): Promise<Report>
}Calculation Methodology
Weighted Consensus (gives more weight to sharp books):
function calculateWeightedConsensus(odds: BookmakerOdds[]): number {
const weights = {
'pinnacle': 2.0, // Sharp book - double weight
'circa': 1.8, // Sharp book
'betcris': 1.5, // Sharp book
'draftkings': 1.0, // Standard recreational book
'fanduel': 1.0,
'betmgm': 1.0,
// ... other books default to 1.0
};
let weightedSum = 0;
let totalWeight = 0;
for (const odd of odds) {
const weight = weights[odd.bookmaker] || 1.0;
weightedSum += odd.line * weight;
totalWeight += weight;
}
return weightedSum / totalWeight;
}Median Consensus (robust against outliers):
function calculateMedianConsensus(odds: BookmakerOdds[]): number {
const sorted = odds.map(o => o.line).sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[mid - 1] + sorted[mid]) / 2;
}
return sorted[mid];
}Outlier Detection (Z-score method):
function detectOutliers(odds: BookmakerOdds[], consensus: number): Outlier[] {
const stdDev = calculateStdDev(odds.map(o => o.line));
const outliers = [];
for (const odd of odds) {
const zScore = Math.abs((odd.line - consensus) / stdDev);
if (zScore > 2.0) { // More than 2 standard deviations
outliers.push({
bookmaker: odd.bookmaker,
line: odd.line,
deviation: odd.line - consensus,
zScore: zScore,
category: zScore > 3.0 ? 'extreme' : 'moderate'
});
}
}
return outliers;
}Consensus Quality Score
function calculateQualityScore(consensus: Consensus): number {
let score = 100;
// Deduct for low bookmaker count
if (consensus.bookmakerCount < 5) {
score -= (5 - consensus.bookmakerCount) * 10;
}
// Deduct for high standard deviation (disagreement)
if (consensus.standardDeviation > 2.0) {
score -= (consensus.standardDeviation - 2.0) * 10;
}
// Bonus for sharp book inclusion
if (consensus.hasSharpBooks) {
score += 10;
}
return Math.max(0, Math.min(100, score));
}API Endpoints
GET /api/consensus/game/:gameId- Consensus for all markets on a gameGET /api/consensus/game/:gameId/:market- Specific market consensusGET /api/consensus/outliers- All current outlier opportunitiesGET /api/consensus/best-value- Best value lines vs consensusGET /api/consensus/movement/:gameId- How consensus has moved over timeGET /api/consensus/bookmaker/:bookmaker/accuracy- How often a bookmaker's lines match final consensus
Frontend Components
Consensus Overlay: ConsensusLineOverlay.tsx
- Show consensus line alongside bookmaker lines
- Visual indicator of where each bookmaker sits
- Highlight outliers
- Show consensus confidence score
Consensus: -6.5 (Quality: 85/100)
─────────────────────────────────
Pinnacle -6.0 ⬅ Best Value
DraftKings -6.5 ✓ At Consensus
FanDuel -7.0
Circa -6.5 ✓ At Consensus
BetMGM -5.5 🔴 Outlier
Best Value Finder: BestValueBoard.tsx
- List all games sorted by best value vs consensus
- Show which bookmaker offers best line
- Calculate edge percentage
- One-click bet placement
Consensus Movement Chart: ConsensusMovementChart.tsx
- Line chart of consensus over time
- Annotate with major news/events
- Show current vs opening consensus
- Identify consensus "traps"
Value Calculation
Edge Calculation:
function calculateEdge(bookmak erLine: number, consensusLine: number, marketType: string): number {
if (marketType === 'spread' || marketType === 'total') {
// For spreads/totals, edge is in points
return Math.abs(bookmakerLine - consensusLine);
} else {
// For moneylines, convert to implied probability
const bookmakerProb = toImpliedProbability(bookmakerLine);
const consensusProb = toImpliedProbability(consensusLine);
return (bookmakerProb - consensusProb) * 100; // % edge
}
}Acceptance Criteria
- Database migration completed
- Weighted and median consensus calculations
- Outlier detection algorithm
- Scheduled calculation every 10 minutes
- Consensus overlay on game cards
- Best value board page
- Consensus movement charts
- API endpoints documented
- Quality score calculation
- Unit tests for all calculation methods
Dependencies
- Odds sync service with multiple bookmakers (minimum 5)
- Bookmaker classification (sharp vs recreational)
- Historical data for consensus accuracy validation
Estimated Effort
- Backend: 5 days
- Frontend: 4 days
- Testing & Validation: 2 days
- Total: 11 days
Success Metrics
- Consensus calculated for 95%+ of games with odds
- Quality score >80 for 70%+ of consensus calculations
- Users find best value opportunities 10+ times per day
- Outlier detection accuracy >90% (manual validation)
Future Enhancements
- Machine learning for dynamic bookmaker weighting
- Consensus prediction model
- "Consensus trap" detection (when consensus is wrong)
- Bookmaker accuracy leaderboard
- Market efficiency score by sport/league
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
analyticsAdvanced analytics featuresAdvanced analytics featuresenhancementNew feature or requestNew feature or requestphase-2Phase 2: Market IntelligencePhase 2: Market Intelligence