Commit 5bf2e5a0 authored by Your Name's avatar Your Name

Add matrix-based arbitrage detection and repair for sure bet analyzer

- Add check_matrix_arbitrage method using safety margin formula
- Add matrix_arbitrage issue type detection in analyze_match
- Add repair logic for matrix_arbitrage issue type
- Update routes to add has_sure_bets property for template compatibility
- Fix typo: match.number -> match.match_number
parent 4daa1528
......@@ -500,6 +500,10 @@ def repair_fixture_sure_bets(fixture_id):
# Perform repair
result = do_repair(fixture_id)
# Add has_sure_bets property for template compatibility
if result.get('success'):
result['has_sure_bets'] = result.get('remaining_issues', 0) > 0
if result.get('success'):
flash(f"Repaired {result.get('total_repairs', 0)} odds. Remaining issues: {result.get('remaining_issues', 0)}", 'success')
else:
......
......@@ -15,6 +15,12 @@ The extraction logic:
- win1 wins → only win1 wins
- win2 wins → only win2 wins
- draw wins → only draw wins
This module includes two types of checks:
1. Simple threshold-based checks (as described above)
2. Matrix-based arbitrage check using the safety margin formula:
- safety_margin = 1/draw + 1/(win1+ko2+ret2) + 1/(win2+ko1+ret1)
- If safety_margin <= 1.0, arbitrage exists
"""
import logging
......@@ -24,6 +30,8 @@ from datetime import datetime
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
class SureBetIssue:
"""Represents a detected sure bet issue in a match"""
......@@ -84,6 +92,62 @@ class SureBetAnalyzer:
'draw': ['draw']
}
def check_matrix_arbitrage(self, outcomes: Dict[str, float]) -> Tuple[bool, float, str]:
"""
Check for arbitrage using the matrix-based safety margin formula.
This uses the formula:
safety_margin = 1/draw + 1/(win1+ko2+ret2) + 1/(win2+ko1+ret1)
If safety_margin <= 1.0, arbitrage exists (guaranteed win).
Args:
outcomes: Dictionary of outcome names to odds
Returns:
Tuple of (has_arbitrage, safety_margin, description)
"""
# Get required odds
win1 = outcomes.get('win1')
win2 = outcomes.get('win2')
ko1 = outcomes.get('ko1')
ko2 = outcomes.get('ko2')
ret1 = outcomes.get('ret1')
ret2 = outcomes.get('ret2')
draw = outcomes.get('draw')
# Need all odds to perform the check
if not all([win1, win2, ko1, ko2, ret1, ret2, draw]):
return False, 0.0, "Insufficient odds for matrix check"
# Calculate branches (inverse of odds for each betting branch)
# Branch 1: Draw only wins
branch_draw = 1.0 / draw
# Branch 2: win1 OR ko2 OR ret2 (any of these wins means win1 payout)
branch_win1 = 1.0 / win1
branch_ko2 = 1.0 / ko2
branch_ret2 = 1.0 / ret2
branch_2_total = branch_win1 + branch_ko2 + branch_ret2
# Branch 3: win2 OR ko1 OR ret1 (any of these wins means win2 payout)
branch_win2 = 1.0 / win2
branch_ko1 = 1.0 / ko1
branch_ret1 = 1.0 / ret1
branch_3_total = branch_win2 + branch_ko1 + branch_ret1
# Safety margin = sum of all branches
# If <= 1.0, arbitrage is possible
safety_margin = branch_draw + branch_2_total + branch_3_total
if safety_margin <= 1.0:
description = (f"Matrix arbitrage detected! Safety margin: {safety_margin:.4f} (must be > 1.0). "
f"Betting on all outcomes guarantees profit.")
return True, safety_margin, description
else:
description = f"Matrix check passed. Safety margin: {safety_margin:.4f}"
return False, safety_margin, description
def analyze_match(self, match) -> List[SureBetIssue]:
"""
Analyze a single match for sure bet conditions.
......@@ -112,6 +176,22 @@ class SureBetAnalyzer:
)
issues.extend(second_extraction_issues)
# Also run the matrix-based arbitrage check
has_arbitrage, safety_margin, matrix_desc = self.check_matrix_arbitrage(outcomes_dict)
if has_arbitrage:
issue = SureBetIssue(
match_id=match.id,
match_number=match.match_number,
extraction='second',
issue_type='matrix_arbitrage',
description=matrix_desc,
outcomes_involved=['win1', 'win2', 'ko1', 'ko2', 'ret1', 'ret2', 'draw'],
odds_values=outcomes_dict,
guaranteed_profit=0.0 # Will be calculated based on safety margin
)
issues.append(issue)
logger.warning(f"Match #{match.match_number}: Matrix arbitrage detected - safety margin: {safety_margin:.4f}")
return issues
def _check_first_extraction(self, match_id: int, match_number: int,
......@@ -569,6 +649,37 @@ class SureBetRepair:
'old_value': win1_odds,
'new_value': new_win1
})
elif issue.issue_type == 'matrix_arbitrage':
# For matrix arbitrage, reduce the highest odds to bring safety margin > 1.0
# Get all relevant odds
all_odds = {
'win1': outcomes_dict.get('win1'),
'win2': outcomes_dict.get('win2'),
'ko1': outcomes_dict.get('ko1'),
'ko2': outcomes_dict.get('ko2'),
'ret1': outcomes_dict.get('ret1'),
'ret2': outcomes_dict.get('ret2'),
'draw': outcomes_dict.get('draw')
}
# Find the highest odd and reduce it
max_odd_name = max(all_odds, key=lambda k: all_odds[k] or 0)
max_odd_value = all_odds.get(max_odd_name)
if max_odd_value and max_odd_value > 2.0:
# Reduce the highest odd by 10%
new_value = max_odd_value * 0.90
outcome = MatchOutcome.query.filter_by(
match_id=match.id, column_name=max_odd_name
).first()
if outcome:
outcome.float_value = new_value
repairs_made.append({
'outcome': max_odd_name,
'old_value': max_odd_value,
'new_value': new_value
})
# Commit changes
if repairs_made:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment