package daemon import ( "fmt" "math" "strconv" "time" "gordenko.dev/dima/flashscore" fsmodel "gordenko.dev/dima/flashscore/model" "gordenko.dev/dima/onexbet" "gordenko.dev/dima/tipper/model" ) var ( p1TimeFrame = timeFrame{ Since: 5 * 60, Until: 22 * 60, } ftTimeFrame = timeFrame{ Since: 10 * 60, Until: 52 * 60, } ftOverTimeFrame = timeFrame{ Since: 10 * 60, Until: 52 * 60, } ) type timeFrame struct { Since int Until int } type HandballStrategyOptions struct { ID int64 NotifyInTelegram bool TotalDiff float64 FixateGamePaceEveryNMinutes int Disable bool } // FIX - calculator, который будет проверять флаг isMatchScoreLoaded func HandballHistoryProcessor(match *TeamMatch) { // Делаем reset match.HomeGoalsPerMinute = 0 match.AwayGoalsPerMinute = 0 match.H2HGoalsPerMinute = 0 match.BasicTotal = 0 /* match.BasicOverGoalsPerMinute = 0 match.BasicUnderGoalsPerMinute = 0 match.TipTotalOver = 0 match.TipTotalUnder = 0 */ // Считаем if len(match.HomeTeamMatches) > 0 { match.HomeGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.HomeTeamMatches, 5) } if len(match.AwayTeamMatches) > 0 { match.AwayGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.AwayTeamMatches, 5) } if len(match.H2H) > 0 { match.H2HGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.H2H, 5) } // Basic Totals match.BasicTotal = findBasicTotal(match.Odds.FullTimeTotal) match.H1BasicTotal = findBasicTotal(match.Odds.H1Total) } func findBasicTotal(totals []flashscore.Total) (_ float64) { var ( minDif float64 bestOffer flashscore.Total isBestOfferFound bool ) // Находим средний тотал - это когда разница между коэффициентами минимальна for _, total := range totals { if len(total.Offers) > 0 { var dif float64 for _, offer := range total.Offers { dif += math.Abs(offer.Over - offer.Under) } // Вычисляем для каждого тотала среднюю (среди всех букмекеров) разницу // между Over и Under коэф. dif = dif / float64(len(total.Offers)) if (!isBestOfferFound) || (dif < minDif) { minDif = dif bestOffer = total isBestOfferFound = true } } } if isBestOfferFound { basicTotal, err := strconv.ParseFloat(bestOffer.Total, 64) if err != nil { return } return basicTotal } return } const ( Gte = 1 // >= Lte = 2 // <= ) func checkGamePace(basicGoalsPerMinute float64, match *TeamMatch, upd onexbet.TeamLiveMatchData, fixateGamePaceEveryNMinutes int, moreOrLess int) (comment string, isPassed bool) { currentGoalsPerMinute := (float64(upd.HomeGoals+upd.AwayGoals) / float64(upd.CurrentTime)) * 60 gamePaceFrame := upd.CurrentTime / (fixateGamePaceEveryNMinutes * 60) if gamePaceFrame == 0 { match.GamePace = currentGoalsPerMinute } else { if gamePaceFrame != match.GamePaceFrame { match.GamePaceFrame = gamePaceFrame match.GamePace = currentGoalsPerMinute } } var timeStr string if match.GamePaceFrame == 0 { timeStr = fmt.Sprintf("%s", time.Duration(upd.CurrentTime)*time.Second) } else { timeStr = fmt.Sprintf("%d мин", match.GamePaceFrame*fixateGamePaceEveryNMinutes) } switch moreOrLess { case Gte: // Темп должен быть >= if match.GamePace < basicGoalsPerMinute { comment = fmt.Sprintf("Текущий темп %.2f (за %s) < базового темпа %.2f", match.GamePace, timeStr, basicGoalsPerMinute) return comment, false } /* if len(match.H2H) > 0 { // H2H матчи необязательныеs if match.GamePace < match.H2HGoalsPerMinute { comment = fmt.Sprintf("Текущий темп %.2f (за %s) < H2H темпа %.2f", match.GamePace, timeStr, match.H2HGoalsPerMinute) return comment, false } } */ case Lte: // Темп должен быть <= if match.GamePace > basicGoalsPerMinute { comment = fmt.Sprintf("Текущий темп %.2f (за %s) > базового темпа %.2f", match.GamePace, timeStr, basicGoalsPerMinute) return comment, false } /* if len(match.H2H) > 0 { // H2H матчи необязательныеs if match.GamePace > match.H2HGoalsPerMinute { comment = fmt.Sprintf("Текущий темп %.2f (за %s) > H2H темпа %.2f", match.GamePace, timeStr, match.H2HGoalsPerMinute) return comment, false } } */ default: panic(fmt.Sprintf("Bug: unknown moreOrLess: %d", moreOrLess)) } return "", true } func correctTotal(total float64, roundDir int) float64 { _, frac := math.Modf(total) eps := 0.001 if math.Abs(frac-0.25) < eps { // Если тотал завершается на 0.25 - округляем в ту или иную сторону switch roundDir { case roundUp: total = math.Floor(total) + 0.5 case roundDown: total = math.Floor(total) } } else if frac < eps { // Круглый тотал: 24, 60 switch roundDir { case roundUp: total = total + 0.5 case roundDown: total = total - 0.5 } } return total } type HandballFTOver struct { id int64 notifyInTelegram bool totalDiff float64 fixateGamePaceEveryNMinutes int isDisabled bool } func NewHandballFTOver(opt HandballStrategyOptions) HandballFTOver { if opt.ID == 0 { panic("StrategyID not defined") } s := HandballFTOver{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.totalDiff = opt.TotalDiff s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes s.isDisabled = opt.Disable return s } func (s HandballFTOver) ID() int64 { return s.id } func (s HandballFTOver) ShortName() string { return fmt.Sprintf("#%d FT Over (-%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes) } func (s HandballFTOver) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s HandballFTOver) GetTelegramTipPattern() string { return `Сигнал # %d. Алгоритм %d. Тотал Больше в матче. %s. %s %s - %s Точка входа %.1f кф. %.3f Рекомендация: тотал больше %.1f и ниже (%.1f; %.1f)` } func (s HandballFTOver) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { return true } func caclulateGoalsPerMinuteOfLastNMatches(matches []fsmodel.TeamMatch, n int) (goalsPerMinute float64) { if len(matches) < n { n = len(matches) } count := 0 for _, m := range matches[:n] { if m.IsMatchScoreLoaded { goalsPerMinute += float64(m.HomeGoals+m.AwayGoals) / 60 count++ } } if count > 0 { return goalsPerMinute / float64(count) } return } func (s HandballFTOver) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) { if s.isDisabled { return } eps := 0.01 // Проверки на то, что полные данные if match.BasicTotal < 1 { return nil, false } tipTotal := match.BasicTotal - s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundDown) basicGoalsPerMinute := tipTotal / 60 if math.Abs(match.H2HGoalsPerMinute) > eps { // Если команды между собой забивают меньше чем дает бук (с учетом погрешности) // - матч не интересен if match.H2HGoalsPerMinute < basicGoalsPerMinute { return } } else { if math.Abs(match.HomeGoalsPerMinute) < eps { return } if math.Abs(match.AwayGoalsPerMinute) < eps { return } // Считаем средний темп с которым играют обе команды avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2 // Если в среднем команды забивают меньше чем дает бук (с учетом погрешности) // - матч не интересен if avgGoalsPerMinute < basicGoalsPerMinute { return } } return nil, true } func (s HandballFTOver) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) { if upd.CurrentTime == 1800 { return "в перерыве не ставим (багфикс)", nil, Waiting } if upd.CurrentTime < ftOverTimeFrame.Since { return "еще рано", nil, Waiting } if upd.CurrentTime > ftOverTimeFrame.Until { return "время вышло", nil, Unwatch } tipTotal := match.BasicTotal - s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundDown) basicGoalsPerMinute := tipTotal / 60 tipTotalStr := fmt.Sprintf("%.1f", tipTotal) if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal { return "Тотал пробит", nil, Unwatch } // Текущий темп матча опережает рассчитанный на истории // проверяем коэффициенты. var isPassed bool comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Gte) if !isPassed { return comment, nil, Waiting } // Задача - найти минимальный тотал <= tipTotal, чтобы коэфф. >= 1.5 tipTotal = tipTotal + 0.01 // Для удобного сравнивания float //fmt.Printf("tipTotal: %.2f\n", tipTotal) var ( minTotalValue float64 bestOffer onexbet.ParamOffer // строковое представление тотала ) for _, offer := range upd.Total.Over { if offer.Price < 1.5 { continue } totalValue, err := strconv.ParseFloat(offer.Param, 64) if err != nil { continue } if totalValue > tipTotal { //fmt.Printf("totalValue > tipTotal: %.2f > %.2f\n", totalValue, tipTotal) continue } //fmt.Printf("Best: totalValue: %2.f (%s), price: %.2f\n", totalValue, offer.Param, offer.Price) if bestOffer.Param == "" { // инициализация minTotalValue = totalValue bestOffer = offer } else { if totalValue < minTotalValue { minTotalValue = totalValue bestOffer = offer } } } if bestOffer.Param == "" { comment = fmt.Sprintf("Тотал %s и меньше с коэф. >= 1.5 не найден", tipTotalStr) return comment, nil, Waiting } return "", &BetDetails{ Market: MarketTotal, Side: SideOver, Param: bestOffer.Param, Price: bestOffer.Price, }, Bet } func (s HandballFTOver) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) { //buf, _ := json.MarshalIndent(stats, "", " ") //fmt.Printf("%s\n", buf) if stats.Status != onexbet.StatusMatchCompleted { return } tipTotal, err := strconv.ParseFloat(tip.Param, 64) if err != nil { return } res := &TipResult{ Result: fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints), } goals := float64(stats.HomePoints + stats.AwayPoints) // Сперва проверяем что тоталы не равны dif := goals - tipTotal eps := 0.0001 if math.Abs(dif) < eps { // Равны - расход res.Status = model.Void } else { if goals > tipTotal { res.Status = model.Won } else { res.Status = model.Lost } } return res } type HandballFTUnder struct { id int64 notifyInTelegram bool totalDiff float64 fixateGamePaceEveryNMinutes int isDisabled bool } func NewHandballFTUnder(opt HandballStrategyOptions) HandballFTUnder { if opt.ID == 0 { panic("StrategyID not defined") } s := HandballFTUnder{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.totalDiff = opt.TotalDiff s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes s.isDisabled = opt.Disable return s } func (s HandballFTUnder) ID() int64 { return s.id } func (s HandballFTUnder) ShortName() string { return fmt.Sprintf("#%d FT Under (+%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes) } func (s HandballFTUnder) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s HandballFTUnder) GetTelegramTipPattern() string { return `Сигнал # %d. Алгоритм %d. Тотал Меньше в матче. %s. %s %s - %s Точка входа %.1f кф. %.3f Рекомендация: тотал меньше %.1f и выше (%.1f; %.1f)` } func (s HandballFTUnder) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { return true } func (s HandballFTUnder) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) { if s.isDisabled { return } eps := 0.01 // Проверки на то, что полные данные if match.BasicTotal < 1 { return nil, false } tipTotal := match.BasicTotal + s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundUp) basicGoalsPerMinute := tipTotal / 60 if math.Abs(match.H2HGoalsPerMinute) > eps { // Если команды между собой забивают больше чем дает бук (с учетом погрешности) // - матч не интересен (ибо опасно) if match.H2HGoalsPerMinute > basicGoalsPerMinute { return } } else { if math.Abs(match.HomeGoalsPerMinute) < eps { return } if math.Abs(match.AwayGoalsPerMinute) < eps { return } // Считаем средний темп с которым играют обе команды avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2 // Если в среднем команды забивают больше чем дает бук (с учетом погрешности) // - матч не интересен (ибо опасно) if avgGoalsPerMinute > basicGoalsPerMinute { return } } return nil, true } func (s HandballFTUnder) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) { if upd.CurrentTime == 1800 { return "в перерыве не ставим (багфикс)", nil, Waiting } if upd.CurrentTime < ftTimeFrame.Since { return "еще рано", nil, Waiting } if upd.CurrentTime > ftTimeFrame.Until { return "время вышло", nil, Unwatch } tipTotal := match.BasicTotal + s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundUp) basicGoalsPerMinute := tipTotal / 60 tipTotalStr := fmt.Sprintf("%.1f", tipTotal) if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal { return "Тотал пробит", nil, Unwatch } var isPassed bool comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Lte) if !isPassed { return comment, nil, Waiting } // Текущий темп матча опережает рассчитанный на истории // проверяем коэффициенты. // Задача - найти максимальный тотал >= tipTotal, чтобы коэфф. >= 1.5 tipTotal = tipTotal - 0.01 // Для удобного сравнивания float var ( maxTotalValue float64 bestOffer onexbet.ParamOffer // строковое представление тотала ) for _, offer := range upd.Total.Under { if offer.Price < 1.5 { continue } totalValue, err := strconv.ParseFloat(offer.Param, 64) if err != nil { continue } if totalValue < tipTotal { continue } if bestOffer.Param == "" { // инициализация maxTotalValue = totalValue bestOffer = offer } else { if totalValue > maxTotalValue { maxTotalValue = totalValue bestOffer = offer } } } if bestOffer.Param == "" { comment = fmt.Sprintf("Тотал %s и больше с коэф. >= 1.5 не найден", tipTotalStr) return comment, nil, Waiting } return "", &BetDetails{ Market: MarketTotal, Side: SideUnder, Param: bestOffer.Param, Price: bestOffer.Price, }, Bet } func (s HandballFTUnder) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) { //buf, _ := json.MarshalIndent(stats, "", " ") //fmt.Printf("%s\n", buf) if stats.Status != onexbet.StatusMatchCompleted { return } tipTotal, err := strconv.ParseFloat(tip.Param, 64) if err != nil { return } res := &TipResult{ Result: fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints), } goals := float64(stats.HomePoints + stats.AwayPoints) // Сперва проверяем что тоталы не равны dif := goals - tipTotal eps := 0.0001 if math.Abs(dif) < eps { // Равны - расход res.Status = model.Void } else { if goals < tipTotal { res.Status = model.Won } else { res.Status = model.Lost } } return res } // P1 type HandballP1Over struct { id int64 notifyInTelegram bool totalDiff float64 fixateGamePaceEveryNMinutes int isDisabled bool } func NewHandballP1Over(opt HandballStrategyOptions) HandballP1Over { if opt.ID == 0 { panic("StrategyID not defined") } s := HandballP1Over{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.totalDiff = opt.TotalDiff s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes s.isDisabled = opt.Disable return s } func (s HandballP1Over) ID() int64 { return s.id } func (s HandballP1Over) ShortName() string { return fmt.Sprintf("#%d P1 Over (-%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes) } func (s HandballP1Over) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s HandballP1Over) GetTelegramTipPattern() string { return `Сигнал # %d. Алгоритм %d. Тотал Больше в 1м тайме. %s. %s %s - %s Точка входа %.1f кф. %.3f Рекомендация: тотал больше %.1f и ниже (%.1f; %.1f)` } func (s HandballP1Over) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.CurrentPeriod == 1 { return true } return false } func (s HandballP1Over) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) { if s.isDisabled { return } eps := 0.01 // Проверки на то, что полные данные if match.BasicTotal < 1 { return nil, false } var tipTotal float64 /* if match.H1BasicTotal < 1 { tipTotal = (match.BasicTotal / 2) - s.totalDiff } else { tipTotal = match.H1BasicTotal - s.totalDiff } */ if match.H1BasicTotal < 1 { return } tipTotal = match.H1BasicTotal - s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundDown) basicGoalsPerMinute := tipTotal / 30 if math.Abs(match.H2HGoalsPerMinute) > eps { // Если команды между собой забивают меньше чем дает бук (с учетом погрешности) // - матч не интересен if match.H2HGoalsPerMinute < basicGoalsPerMinute { return } } else { if math.Abs(match.HomeGoalsPerMinute) < eps { return } if math.Abs(match.AwayGoalsPerMinute) < eps { return } // Считаем средний темп с которым играют обе команды avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2 // Если в среднем команды забивают меньше чем дает бук (с учетом погрешности) // - матч не интересен if avgGoalsPerMinute < basicGoalsPerMinute { return } } return nil, true } func (s HandballP1Over) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) { if upd.CurrentTime == 1800 { return "в перерыве не ставим (багфикс)", nil, Waiting } if upd.CurrentTime < p1TimeFrame.Since { return "еще рано", nil, Waiting } if upd.CurrentTime > p1TimeFrame.Until { return "время вышло", nil, Unwatch } var tipTotal float64 if match.H1BasicTotal < 1 { tipTotal = (match.BasicTotal / 2) - s.totalDiff } else { tipTotal = match.H1BasicTotal - s.totalDiff } // ВАЖНО! // После деления тотал может быть 25.25, либо просто круглый 24, 60 tipTotal = correctTotal(tipTotal, roundDown) basicGoalsPerMinute := tipTotal / 30 tipTotalStr := fmt.Sprintf("%.1f", tipTotal) if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal { return "Тотал пробит", nil, Unwatch } var isPassed bool comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Gte) if !isPassed { return comment, nil, Waiting } // Текущий темп матча опережает рассчитанный на истории // проверяем коэффициенты. // Задача - найти минимальный тотал <= tipTotal, чтобы коэфф. >= 1.5 tipTotal = tipTotal + 0.01 // Для удобного сравнивания float //fmt.Printf("tipTotal: %.2f\n", tipTotal) var ( minTotalValue float64 bestOffer onexbet.ParamOffer // строковое представление тотала ) for _, offer := range upd.H1.Total.Over { if offer.Price < 1.5 { continue } totalValue, err := strconv.ParseFloat(offer.Param, 64) if err != nil { continue } if totalValue > tipTotal { //fmt.Printf("totalValue > tipTotal: %.2f > %.2f\n", totalValue, tipTotal) continue } //fmt.Printf("Best: totalValue: %2.f (%s), price: %.2f\n", totalValue, offer.Param, offer.Price) if bestOffer.Param == "" { // инициализация minTotalValue = totalValue bestOffer = offer } else { if totalValue < minTotalValue { minTotalValue = totalValue bestOffer = offer } } } if bestOffer.Param == "" { comment = fmt.Sprintf("Тотал %s и меньше с коэф. >= 1.5 не найден", tipTotalStr) return comment, nil, Waiting } return "", &BetDetails{ Market: MarketTotalH1, Side: SideOver, Param: bestOffer.Param, Price: bestOffer.Price, }, Bet } func (s HandballP1Over) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) { //buf, _ := json.MarshalIndent(stats, "", " ") //fmt.Printf("%s\n", buf) if stats.Status != onexbet.StatusMatchCompleted { return } h1, ok := stats.ScoreByPeriods[1] if !ok { return } tipTotal, err := strconv.ParseFloat(tip.Param, 64) if err != nil { return } res := &TipResult{ Result: fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints), } goals := float64(h1.HomePoints + h1.AwayPoints) // Сперва проверяем что тоталы не равны dif := goals - tipTotal eps := 0.0001 if math.Abs(dif) < eps { // Равны - расход res.Status = model.Void } else { if goals > tipTotal { res.Status = model.Won } else { res.Status = model.Lost } } return res } type HandballP1Under struct { id int64 notifyInTelegram bool totalDiff float64 fixateGamePaceEveryNMinutes int isDisabled bool } func NewHandballP1Under(opt HandballStrategyOptions) HandballP1Under { if opt.ID == 0 { panic("StrategyID not defined") } s := HandballP1Under{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.totalDiff = opt.TotalDiff s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes s.isDisabled = opt.Disable return s } func (s HandballP1Under) ID() int64 { return s.id } func (s HandballP1Under) ShortName() string { return fmt.Sprintf("#%d P1 Under (+%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes) } func (s HandballP1Under) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s HandballP1Under) GetTelegramTipPattern() string { return `Сигнал # %d. Алгоритм %d. Тотал Меньше в 1м тайме. %s. %s %s - %s Точка входа %.1f кф. %.3f Рекомендация: тотал меньше %.1f и выше (%.1f; %.1f)` } func (s HandballP1Under) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.CurrentPeriod == 1 { return true } return false } func (s HandballP1Under) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) { if s.isDisabled { return } eps := 0.01 // Проверки на то, что полные данные if match.BasicTotal < 1 { return nil, false } var tipTotal float64 /* if match.H1BasicTotal < 1 { tipTotal = (match.BasicTotal / 2) + s.totalDiff } else { tipTotal = match.H1BasicTotal + s.totalDiff } */ if match.H1BasicTotal < 1 { return } tipTotal = match.H1BasicTotal + s.totalDiff // ВАЖНО! // После деления тотал может быть 25.25 tipTotal = correctTotal(tipTotal, roundUp) basicGoalsPerMinute := tipTotal / 30 if math.Abs(match.H2HGoalsPerMinute) > eps { // Если команды между собой забивают больше чем дает бук (с учетом погрешности) // - матч не интересен (ибо опасно) if match.H2HGoalsPerMinute > basicGoalsPerMinute { return } } else { if math.Abs(match.HomeGoalsPerMinute) < eps { return } if math.Abs(match.AwayGoalsPerMinute) < eps { return } // Считаем средний темп с которым играют обе команды avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2 // Если в среднем команды забивают больше чем дает бук (с учетом погрешности) // - матч не интересен (ибо опасно) if avgGoalsPerMinute > basicGoalsPerMinute { return } } return nil, true } func (s HandballP1Under) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) { if upd.CurrentTime == 1800 { return "в перерыве не ставим (багфикс)", nil, Waiting } if upd.CurrentTime < p1TimeFrame.Since { return "еще рано", nil, Waiting } if upd.CurrentTime > p1TimeFrame.Until { return "время вышло", nil, Unwatch } var tipTotal float64 if match.H1BasicTotal < 1 { tipTotal = (match.BasicTotal / 2) + s.totalDiff } else { tipTotal = match.H1BasicTotal + s.totalDiff } // ВАЖНО! // После деления тотал может быть 25.25 tipTotal = correctTotal(tipTotal, roundUp) basicGoalsPerMinute := tipTotal / 30 tipTotalStr := fmt.Sprintf("%.1f", tipTotal) if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal { return "Тотал пробит", nil, Unwatch } var isPassed bool comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Lte) if !isPassed { return comment, nil, Waiting } // Задача - найти максимальный тотал >= tipTotal, чтобы коэфф. >= 1.5 tipTotal = tipTotal - 0.01 // Для удобного сравнивания float var ( maxTotalValue float64 bestOffer onexbet.ParamOffer // строковое представление тотала ) for _, offer := range upd.H1.Total.Under { if offer.Price < 1.5 { continue } totalValue, err := strconv.ParseFloat(offer.Param, 64) if err != nil { continue } if totalValue < tipTotal { continue } if bestOffer.Param == "" { // инициализация maxTotalValue = totalValue bestOffer = offer } else { if totalValue > maxTotalValue { maxTotalValue = totalValue bestOffer = offer } } } if bestOffer.Param == "" { comment = fmt.Sprintf("Тотал %s и больше с коэф. >= 1.5 не найден", tipTotalStr) return comment, nil, Waiting } return "", &BetDetails{ Market: MarketTotalH1, Side: SideUnder, Param: bestOffer.Param, Price: bestOffer.Price, }, Bet } func (s HandballP1Under) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) { //buf, _ := json.MarshalIndent(stats, "", " ") //fmt.Printf("%s\n", buf) if stats.Status != onexbet.StatusMatchCompleted { return } h1, ok := stats.ScoreByPeriods[1] if !ok { return } //if stats.H1 == nil || stats.H2 == nil { //return //} tipTotal, err := strconv.ParseFloat(tip.Param, 64) if err != nil { return } res := &TipResult{ Result: fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints), } goals := float64(h1.HomePoints + h1.AwayPoints) // Сперва проверяем что тоталы не равны dif := goals - tipTotal eps := 0.0001 if math.Abs(dif) < eps { // Равны - расход res.Status = model.Void } else { if goals < tipTotal { res.Status = model.Won } else { res.Status = model.Lost } } return res }