package result import ( "fmt" "strconv" "strings" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) // Football evaluations // Full Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the full 90 minutes of play. func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { switch outcome.OddName { case "1": // Home win if score.Home > score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Draw": if score.Home == score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": // Away win if score.Away > score.Home { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } } // Over/Under betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be over or under a specified number. func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { totalGoals := float64(score.Home + score.Away) threshold, err := strconv.ParseFloat(outcome.OddName, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } if outcome.OddHeader == "Over" { if totalGoals > threshold { return domain.OUTCOME_STATUS_WIN, nil } else if totalGoals == threshold { return domain.OUTCOME_STATUS_VOID, nil } return domain.OUTCOME_STATUS_LOSS, nil } else if outcome.OddHeader == "Under" { if totalGoals < threshold { return domain.OUTCOME_STATUS_WIN, nil } else if totalGoals == threshold { return domain.OUTCOME_STATUS_VOID, nil } return domain.OUTCOME_STATUS_LOSS, nil } return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } // Correct Score betting is a type of bet where the bettor predicts the exact final score of a match. func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away) if outcome.OddName == expectedScore { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } // Half Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the first half. // This is the same as the full time result but only for the first half of the game func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { return evaluateFullTimeResult(outcome, score) } // This is a multiple outcome checker for the asian handicap and other kinds of bets // The only outcome that are allowed are "Both Bets win", "Both Bets Lose", "Half Win and Half Void" func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.OutcomeStatus) (domain.OutcomeStatus, error) { if secondOutcome == domain.OUTCOME_STATUS_PENDING { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("cannot check pending outcome") } if outcome == domain.OUTCOME_STATUS_ERROR || secondOutcome == domain.OUTCOME_STATUS_ERROR { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("❌ mutli outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) } // fmt.Printf("| Multi Outcome | %v -> %v \n", outcome.String(), secondOutcome.String()) switch outcome { case domain.OUTCOME_STATUS_PENDING: return secondOutcome, nil case domain.OUTCOME_STATUS_WIN: if secondOutcome == domain.OUTCOME_STATUS_WIN { return domain.OUTCOME_STATUS_WIN, nil } else if secondOutcome == domain.OUTCOME_STATUS_LOSS { return domain.OUTCOME_STATUS_LOSS, nil } else if secondOutcome == domain.OUTCOME_STATUS_HALF { return domain.OUTCOME_STATUS_VOID, nil } else if secondOutcome == domain.OUTCOME_STATUS_VOID { return domain.OUTCOME_STATUS_HALF, nil } else { fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome") } case domain.OUTCOME_STATUS_LOSS: if secondOutcome == domain.OUTCOME_STATUS_LOSS || secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF { return domain.OUTCOME_STATUS_LOSS, nil } else if secondOutcome == domain.OUTCOME_STATUS_VOID { return domain.OUTCOME_STATUS_VOID, nil } else { fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome") } case domain.OUTCOME_STATUS_VOID: if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS { return domain.OUTCOME_STATUS_VOID, nil } else if secondOutcome == domain.OUTCOME_STATUS_VOID || secondOutcome == domain.OUTCOME_STATUS_HALF { return domain.OUTCOME_STATUS_VOID, nil } else { fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome") } case domain.OUTCOME_STATUS_HALF: if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF { return domain.OUTCOME_STATUS_VOID, nil } else if secondOutcome == domain.OUTCOME_STATUS_LOSS { return domain.OUTCOME_STATUS_LOSS, nil } else if secondOutcome == domain.OUTCOME_STATUS_VOID { return domain.OUTCOME_STATUS_VOID, nil } else { fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome") } default: fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String()) return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome") } } // Asian Handicap betting is a type of betting that eliminates the possibility of a draw by giving one team a virtual advantage or disadvantage. // When the handicap has two values like "+0.5, +1.0" or "-0.5, -1.0", then it a multi outcome bet // . // // { // "id": "548319135", // "odds": "1.750", // "header": "1", // "handicap": "+0.5, +1.0" // }, // // { // "id": "548319139", // "odds": "1.950", // "header": "2", // "handicap": "-0.5, -1.0" // } func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { handicapList := strings.Split(outcome.OddHandicap, ",") newOutcome := domain.OUTCOME_STATUS_PENDING for _, handicapStr := range handicapList { handicapStr = strings.TrimSpace(handicapStr) handicap, err := strconv.ParseFloat(handicapStr, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap) } adjustedHomeScore := float64(score.Home) adjustedAwayScore := float64(score.Away) if outcome.OddHeader == "1" { // Home team adjustedHomeScore += handicap } else if outcome.OddHeader == "2" { // Away team adjustedAwayScore += handicap } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } if adjustedHomeScore > adjustedAwayScore { if outcome.OddHeader == "1" { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } continue } newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } continue } else if adjustedHomeScore < adjustedAwayScore { if outcome.OddHeader == "2" { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } continue } newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } continue } else if adjustedHomeScore == adjustedAwayScore { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } continue } } return newOutcome, nil } // Goal Line betting, also known as Over/Under betting, // involves predicting the total number of goals scored in a match, regardless of which team wins. // // { // "id": "548319141", // "odds": "1.800", // "header": "Over", // "name": "1.5, 2.0" // }, // // { // "id": "548319146", // "odds": "1.900", // "header": "Under", // "name": "1.5, 2.0" // } func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { totalGoals := float64(score.Home + score.Away) thresholdList := strings.Split(outcome.OddName, ",") newOutcome := domain.OUTCOME_STATUS_PENDING for _, thresholdStr := range thresholdList { thresholdStr = strings.TrimSpace(thresholdStr) threshold, err := strconv.ParseFloat(thresholdStr, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: '%s', %v", thresholdStr, err) } oddHeader := strings.TrimSpace(outcome.OddHeader) if oddHeader == "Over" { if totalGoals > threshold { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } else if totalGoals == threshold { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } else if oddHeader == "Under" { if totalGoals < threshold { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } else if totalGoals == threshold { newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS) if err != nil { return domain.OUTCOME_STATUS_ERROR, err } } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: '%s'", oddHeader) } } return newOutcome, nil } // First Team To Score betting is a type of bet where the bettor predicts which team will score first in a match. // We can get this from the "events" field on the result json func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) { for _, event := range events { if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") { if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" { return domain.OUTCOME_STATUS_WIN, nil } else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } } return domain.OUTCOME_STATUS_VOID, nil // No goals scored } // Goals Odd/Even betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be odd or even. func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { totalGoals := score.Home + score.Away isOdd := totalGoals%2 == 1 if outcome.OddName == "Odd" && isOdd { return domain.OUTCOME_STATUS_WIN, nil } else if outcome.OddName == "Even" && !isOdd { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } func evaluateTeamOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { switch outcome.OddHeader { case "1": if outcome.OddHandicap == "Odd" { if score.Home%2 == 1 { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else if outcome.OddHandicap == "Even" { if score.Home%2 == 0 { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap) } case "2": if outcome.OddHandicap == "Odd" { if score.Away%2 == 1 { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else if outcome.OddHandicap == "Even" { if score.Away%2 == 0 { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap) } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } } // Double Chance betting is a type of bet where the bettor predicts two of the three possible outcomes of a match. func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { isHomeWin := score.Home > score.Away isDraw := score.Home == score.Away isAwayWin := score.Away > score.Home switch outcome.OddName { case "1 or Draw", (outcome.HomeTeamName + " or " + "Draw"), ("Draw" + " or " + outcome.HomeTeamName): if isHomeWin || isDraw { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Draw or 2", ("Draw" + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + "Draw"): if isDraw || isAwayWin { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "1 or 2", (outcome.HomeTeamName + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + outcome.HomeTeamName): if isHomeWin || isAwayWin { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } } // Draw No Bet betting is a type of bet where the bettor predicts the outcome of a match, but if the match ends in a draw, the bet is voided. func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { if score.Home == score.Away { return domain.OUTCOME_STATUS_VOID, nil } if outcome.OddName == "1" && score.Home > score.Away { return domain.OUTCOME_STATUS_WIN, nil } else if outcome.OddName == "2" && score.Away > score.Home { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } func evaluateCorners(outcome domain.BetOutcome, corners struct{ Home, Away int }) (domain.OutcomeStatus, error) { totalCorners := corners.Home + corners.Away threshold, err := strconv.ParseFloat(outcome.OddName, 10) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } switch outcome.OddHeader { case "Over": if totalCorners > int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Under": if totalCorners < int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Exactly": if totalCorners == int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } } // Basketball evaluations // Game Lines is an aggregate of money line, spread and total betting markets in one func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { switch outcome.OddName { case "Money Line": return evaluateMoneyLine(outcome, score) case "Spread", "Line": // Since Spread betting is essentially the same thing return evaluateAsianHandicap(outcome, score) case "Total": return evaluateTotalOverUnder(outcome, score) default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } } // Money Line betting is a type of bet where the bettor predicts the outcome of a match without any point spread. func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { switch outcome.OddHeader { case "1": if score.Home > score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": if score.Home < score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Tie", "Draw": if score.Home == score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } } // Total Over/Under betting is a type of bet where the bettor predicts whether the total number of points scored in a match will be over or under a specified number. func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { // The handicap will be in the format "U {float}" or "O {float}" // U and O denoting over and under for this case overUnderStr := strings.Split(outcome.OddHandicap, " ") if len(overUnderStr) != 2 { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } threshold, err := strconv.ParseFloat(overUnderStr[1], 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } // Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet totalScore := float64(score.Home + score.Away) if overUnderStr[0] == "O" { if totalScore > threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else if overUnderStr[0] == "U" { if totalScore < threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } else if overUnderStr[0] == "E" { if totalScore == threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } // Result and Total betting is a type of bet where the bettor predicts // the outcome of a match and whether the total number of points scored will be over or under a specified number. func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { // The handicap will be in the format "U {float}" or "O {float}" // U and O denoting over and under for this case overUnderStr := strings.Split(outcome.OddHandicap, " ") if len(overUnderStr) != 2 { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } overUnder := overUnderStr[0] if overUnder != "Over" && overUnder != "Under" { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader) } threshold, err := strconv.ParseFloat(overUnderStr[1], 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } // Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet totalScore := float64(score.Home + score.Away) switch outcome.OddHeader { case "1": if score.Home < score.Away { return domain.OUTCOME_STATUS_LOSS, nil } if overUnder == "Over" && totalScore > threshold { return domain.OUTCOME_STATUS_WIN, nil } else if overUnder == "Under" && totalScore < threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": if score.Away < score.Home { return domain.OUTCOME_STATUS_LOSS, nil } if overUnder == "Over" && totalScore > threshold { return domain.OUTCOME_STATUS_WIN, nil } else if overUnder == "Under" && totalScore < threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName) } } // Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match // is over or under a specified number. func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { // The handicap will be in the format "U {float}" or "O {float}" // U and O denoting over and under for this case overUnderStr := strings.Split(outcome.OddHandicap, " ") if len(overUnderStr) != 2 { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap) } overUnder := overUnderStr[0] if overUnder != "Over" && overUnder != "Under" { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader) } threshold, err := strconv.ParseFloat(overUnderStr[1], 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap) } // Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet HomeScore := float64(score.Home) AwayScore := float64(score.Away) switch outcome.OddHeader { case "1": if overUnder == "Over" && HomeScore > threshold { return domain.OUTCOME_STATUS_WIN, nil } else if overUnder == "Under" && HomeScore < threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": if overUnder == "Over" && AwayScore > threshold { return domain.OUTCOME_STATUS_WIN, nil } else if overUnder == "Under" && AwayScore < threshold { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName) } } // Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points // and also the result fo the match func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { // The name parameter will hold value "name": "{team_name} and {Yes | No}" // The best way to do this is to evaluate backwards since there might be // teams with 'and' in their name // We know that there is going to be "Yes" and "No " oddNameSplit := strings.Split(outcome.OddName, " ") scoreCheckSplit := oddNameSplit[len(oddNameSplit)-1] var isScorePoints bool if scoreCheckSplit == "Yes" { isScorePoints = true } else if scoreCheckSplit == "No" { isScorePoints = false } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } teamName := strings.TrimSpace(strings.Join(oddNameSplit[:len(oddNameSplit)-2], "")) threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHeader) } switch teamName { case outcome.HomeTeamName: if score.Home > score.Away { if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } } case outcome.AwayTeamName: if score.Away > score.Home { if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("team name error: %s", teamName) } return domain.OUTCOME_STATUS_LOSS, nil } // Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points. func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { threshold, err := strconv.ParseInt(outcome.OddName, 10, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName) } switch outcome.OddHeader { case "Yes": if score.Home >= int(threshold) && score.Away >= int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } case "No": if score.Home < int(threshold) && score.Away < int(threshold) { return domain.OUTCOME_STATUS_WIN, nil } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } return domain.OUTCOME_STATUS_LOSS, nil } // Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw. func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { switch outcome.OddName { case "1": // Home win if score.Home > score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "Tie": if score.Home == score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": // Away win if score.Away > score.Home { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } } // Double Result betting is a type of bet where the bettor predicts the outcome of a match at both half-time and full-time. func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) { halfWins := strings.Split(outcome.OddName, "-") if len(halfWins) != 2 { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName) } firstHalfWinner := strings.TrimSpace(halfWins[0]) secondHalfWinner := strings.TrimSpace(halfWins[1]) if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner) } if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner) } switch { case firstHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away: return domain.OUTCOME_STATUS_LOSS, nil case firstHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home: return domain.OUTCOME_STATUS_LOSS, nil case firstHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away: return domain.OUTCOME_STATUS_LOSS, nil } switch { case secondHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away: return domain.OUTCOME_STATUS_LOSS, nil case secondHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home: return domain.OUTCOME_STATUS_LOSS, nil case secondHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away: return domain.OUTCOME_STATUS_LOSS, nil } return domain.OUTCOME_STATUS_WIN, nil } // Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score. func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) { firstHalfTotal := firstScore.Home + firstScore.Away secondHalfTotal := secondScore.Home + secondScore.Away switch outcome.OddName { case "1st Half": if firstHalfTotal > secondHalfTotal { return domain.OUTCOME_STATUS_WIN, nil } case "2nd Half": if firstHalfTotal < secondHalfTotal { return domain.OUTCOME_STATUS_WIN, nil } case "Tie": if firstHalfTotal == secondHalfTotal { return domain.OUTCOME_STATUS_WIN, nil } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } return domain.OUTCOME_STATUS_LOSS, nil } // Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score. func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) { firstQuarterTotal := firstScore.Home + firstScore.Away secondQuarterTotal := secondScore.Home + secondScore.Away thirdQuarterTotal := thirdScore.Home + thirdScore.Away fourthQuarterTotal := fourthScore.Home + fourthScore.Away switch outcome.OddName { case "1st Quarter": if firstQuarterTotal > secondQuarterTotal && firstQuarterTotal > thirdQuarterTotal && firstQuarterTotal > fourthQuarterTotal { return domain.OUTCOME_STATUS_WIN, nil } case "2nd Quarter": if secondQuarterTotal > firstQuarterTotal && secondQuarterTotal > thirdQuarterTotal && secondQuarterTotal > fourthQuarterTotal { return domain.OUTCOME_STATUS_WIN, nil } case "3rd Quarter": if thirdQuarterTotal > firstQuarterTotal && thirdQuarterTotal > secondQuarterTotal && thirdQuarterTotal > fourthQuarterTotal { return domain.OUTCOME_STATUS_WIN, nil } case "4th Quarter": if fourthQuarterTotal > firstQuarterTotal && fourthQuarterTotal > secondQuarterTotal && fourthQuarterTotal > thirdQuarterTotal { return domain.OUTCOME_STATUS_WIN, nil } case "Tie": if firstQuarterTotal == secondQuarterTotal || secondQuarterTotal == thirdQuarterTotal || thirdQuarterTotal == fourthQuarterTotal { return domain.OUTCOME_STATUS_WIN, nil } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } return domain.OUTCOME_STATUS_LOSS, nil } // Team With Highest Scoring Quarter betting is a type of bet where the bettor predicts which team will have the highest score in a specific quarter. func evaluateTeamWithHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) { homeTeamHighestQuarter := max(firstScore.Home, secondScore.Home, thirdScore.Home, fourthScore.Home) awayTeamHighestQuarter := max(firstScore.Away, secondScore.Away, thirdScore.Away, fourthScore.Away) switch outcome.OddName { case "1": if homeTeamHighestQuarter > awayTeamHighestQuarter { return domain.OUTCOME_STATUS_WIN, nil } case "2": if awayTeamHighestQuarter > homeTeamHighestQuarter { return domain.OUTCOME_STATUS_WIN, nil } case "Tie": if homeTeamHighestQuarter == awayTeamHighestQuarter { return domain.OUTCOME_STATUS_WIN, nil } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } return domain.OUTCOME_STATUS_LOSS, nil } // Handicap and Total betting is a combination of spread betting and total points betting // where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number. func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { nameSplit := strings.Split(outcome.OddName, " ") // Evaluate from bottom to get the threshold and find out if its over or under threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing threshold: %s", outcome.OddName) } total := float64(score.Home + score.Away) overUnder := nameSplit[len(nameSplit)-2] if overUnder == "Over" { if total < threshold { return domain.OUTCOME_STATUS_LOSS, nil } } else if overUnder == "Under" { if total > threshold { return domain.OUTCOME_STATUS_LOSS, nil } } else { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over and under: %s", outcome.OddName) } handicap, err := strconv.ParseFloat(nameSplit[len(nameSplit)-4], 10) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing handicap: %s", outcome.OddName) } teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], "")) adjustedHomeScore := float64(score.Home) adjustedAwayScore := float64(score.Away) switch teamName { case outcome.HomeTeamName: adjustedHomeScore += handicap if adjustedHomeScore > adjustedAwayScore { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case outcome.AwayTeamName: adjustedAwayScore += handicap if adjustedAwayScore > adjustedHomeScore { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing team name: %s", outcome.OddName) } } // Winning Margin betting is a type of bet where the bettor predicts the margin of victory in a match. func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { marginSplit := strings.Split(outcome.OddName, "") if len(marginSplit) < 1 { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } margin, err := strconv.ParseInt(marginSplit[0], 10, 64) if err != nil { return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } isGtr := false if len(marginSplit) == 2 { isGtr = marginSplit[1] == "+" } switch outcome.OddHeader { case "1": if score.Home == (score.Away + int(margin)) { return domain.OUTCOME_STATUS_WIN, nil } else if isGtr && score.Home > (score.Away+int(margin)) { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "2": if (score.Home + int(margin)) == score.Away { return domain.OUTCOME_STATUS_WIN, nil } else if isGtr && (score.Home+int(margin)) > score.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader) } // Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score. func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) { firstPeriodTotal := firstScore.Home + firstScore.Away secondPeriodTotal := secondScore.Home + secondScore.Away thirdPeriodTotal := thirdScore.Home + thirdScore.Away switch outcome.OddName { case "Period 1": if firstPeriodTotal > secondPeriodTotal && firstPeriodTotal > thirdPeriodTotal { return domain.OUTCOME_STATUS_WIN, nil } case "Period 2": if secondPeriodTotal > firstPeriodTotal && secondPeriodTotal > thirdPeriodTotal { return domain.OUTCOME_STATUS_WIN, nil } case "Period 3": if thirdPeriodTotal > firstPeriodTotal && thirdPeriodTotal > secondPeriodTotal { return domain.OUTCOME_STATUS_WIN, nil } case "Tie": if firstPeriodTotal == secondPeriodTotal || secondPeriodTotal == thirdPeriodTotal { return domain.OUTCOME_STATUS_WIN, nil } default: return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } return domain.OUTCOME_STATUS_LOSS, nil } // Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time. func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) { totalScore := struct{ Home, Away int }{0, 0} for _, score := range scores { totalScore.Home += score.Home totalScore.Away += score.Away } switch outcome.OddName { case "Yes": if totalScore.Home == totalScore.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil case "No": if totalScore.Home != totalScore.Away { return domain.OUTCOME_STATUS_WIN, nil } return domain.OUTCOME_STATUS_LOSS, nil } return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName) } // evaluateRugbyOutcome evaluates the outcome of a Rugby bet func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) { finalScore := parseSS(result.SS) switch outcome.MarketName { case "Money Line": return evaluateRugbyMoneyLine(outcome, finalScore) case "Spread": return evaluateRugbySpread(outcome, finalScore) case "Total Points": return evaluateRugbyTotalPoints(outcome, finalScore) default: return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName) } } // evaluateBaseballOutcome evaluates the outcome of a Baseball bet func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) { finalScore := parseSS(result.SS) switch outcome.MarketName { case "Money Line": return evaluateBaseballMoneyLine(outcome, finalScore) case "Spread": return evaluateBaseballSpread(outcome, finalScore) case "Total Runs": return evaluateBaseballTotalRuns(outcome, finalScore) default: return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName) } }