package daemon import ( "fmt" "math" "sort" "strconv" "strings" "gordenko.dev/dima/flashscore" "gordenko.dev/dima/onexbet" "gordenko.dev/dima/tipper/model" ) /* type TennisMatch struct { flashscoreMatchID string totalBreakpoints int lastPrice float64 last3rdSetTotal int closeCh chan struct{} } func NewTennisMatch() *TennisMatch { s := new(TennisMatch) s.closeCh = make(chan struct{}) return s } func (s *TennisMatch) checker() { ticker := time.NewTicker(7 * time.Second) for { select { case <-ticker.C: s.loadMatchStat() case <-s.closeCh: ticker.Stop() // drain channel? return } } } func (s *TennisMatch) loadMatchStat() { stat, err := flashscore.LoadTennisMatchStat(s.flashscoreMatchID) if err != nil { // log return } if stat.HomeBreakpoints != nil && stat.AwayBreakpoints != nil { s.totalBreakpoints = stat.HomeBreakpoints.Qty + stat.AwayBreakpoints.Qty } s.tryGetTip() } func (s *TennisMatch) tryGetTip() { } func (s *TennisMatch) Close() { s.closeCh <- struct{}{} } */ // test strategy // ТМ в 3 сете // 1) Женщины все покрытия // 2) Мужчины (кроме быстрых покрытий) (Грунт!!!) // Условия: // 1) 1 и 2 сет ТБ 10.5 геймов (7:5; 6:7) сумарно не мение 6 и более (брейк поинтов) майскор // 2) 3 сет при счете 1:1 смотрим 10.5 меньше но не позднее 2:2 type Tennis3rdSetTotalUnder struct { id int64 notifyInTelegram bool isChampAccepted func(string) bool } func NewTennis3rdSetTotalUnder(opt StrategyOptions) Tennis3rdSetTotalUnder { if opt.ID == 0 { panic("StrategyID not defined") } s := Tennis3rdSetTotalUnder{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.isChampAccepted = opt.IsChampAccepted return s } func (s Tennis3rdSetTotalUnder) ID() int64 { return s.id } func (s Tennis3rdSetTotalUnder) ShortName() string { return fmt.Sprintf("#%d S3 Under 10.5", s.id) } func (s Tennis3rdSetTotalUnder) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s Tennis3rdSetTotalUnder) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.Score.Home == 1 && liveMatch.Score.Away == 1 { return true } return false } func (s Tennis3rdSetTotalUnder) IsInteresting(_ *TeamMatch) (interface{}, bool) { return nil, true } func (s Tennis3rdSetTotalUnder) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) { if upd.CurrentPeriod < 3 { return "не 3й сет", nil, Waiting } if upd.CurrentPeriod > 3 { return "3й сет завершен", nil, Unwatch } s1score, ok := upd.ScoreByPeriods[1] if !ok { // нет счета 1 сета return "счет 1го сета не найден", nil, Waiting } s1total := s1score.Home + s1score.Away if float64(s1total) < 11.5 { return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch } s2score, ok := upd.ScoreByPeriods[2] if !ok { // нет счета 2 сета return "счет 2го сета не найден", nil, Waiting } s2total := s2score.Home + s2score.Away if float64(s2total) < 11.5 { return fmt.Sprintf("тотал 2го сета %d", s2total), nil, Unwatch } s3score, ok := upd.ScoreByPeriods[3] if !ok { // нет счета 3 сета return "счет 3го сета не найден", nil, Waiting } if s3score.Home > 2 || s3score.Away > 2 { return "счет в 3м сете превысил 2:2", nil, Unwatch } if s3score.Home == 0 || s3score.Away == 0 { return "ждем счет в 3м сете: 1:1, 1:2, 2:1 или 2:2", nil, Waiting } sex := flashscore.ParseTennisSexFromChampName(match.FullChampName) if sex == "" { return "не удалось определить мужчины или женщины играют", nil, Unwatch } if sex == flashscore.SexMen { surface := flashscore.ParseTennisSurfaceFromChampName(match.FullChampName) if surface != flashscore.SurfaceClay { return fmt.Sprintf("слишком быстрое покрытие для мужиков: %s", surface), nil, Unwatch } } if match.TwoSetsBreakpoints == 0 { matchStat, err := flashscore.LoadTennisMatchStat(match.MatchID) if err != nil { return fmt.Sprintf("не удалось загрузить статистику по брейкпоинтам: %s", err), nil, Waiting } var breakpoints int // Запросить стату по брейкпоинтам с сайта Flashscore if matchStat.HomeBreakpoints != nil { breakpoints += matchStat.HomeBreakpoints.Qty } if matchStat.AwayBreakpoints != nil { breakpoints += matchStat.AwayBreakpoints.Qty } match.TwoSetsBreakpoints = breakpoints } if match.TwoSetsBreakpoints < 6 { return fmt.Sprintf("к-во брейкпоинтов < 6: %d", match.TwoSetsBreakpoints), nil, Unwatch } // Поиски тотала с коэф. >= 1.5 set3, ok := upd.Periods[3] if !ok { return "1xbet не дает тотал 3го сета", nil, Waiting } under := set3.Total.Under sort.Slice(under, func(i, j int) bool { if under[i].ParamFloat > under[j].ParamFloat { return true } return false }) for _, offer := range under { if offer.ParamFloat > 10.4 { if offer.Price >= 1.5 { telegramMessage := fmt.Sprintf(`Сигнал # %%d. Алгоритм %d. Тотал меньше в 3м сете. %s %s - %s Точка входа %.1f кф. %.3f Рекомендация: тотал меньше %.1f и выше (+%.1f; +%.1f)`, s.id, match.Champ.Name, match.Home.CanonicalName, match.Away.CanonicalName, offer.ParamFloat, offer.Price, offer.ParamFloat, offer.ParamFloat+1, offer.ParamFloat+2) return "", &BetDetails{ Market: MarketTotal, Side: SideUnder, Param: offer.Param, Price: offer.Price, TelegramMessage: telegramMessage, }, Bet } } } return "не найден тотал 10.5 и выше с коэф. 1.5 и выше", nil, Waiting } func (s Tennis3rdSetTotalUnder) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) { score, ok := result.ScoreByPeriods[3] if !ok { // Сперва проверяем что счет есть. return } // Проверяем что сет завершен if !isTennisSetCompleted(score) { return } total := float64(score.HomePoints + score.AwayPoints) param, err := strconv.ParseFloat(tip.Param, 64) if err != nil { fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param) return } resultText := fmt.Sprintf(`3й сет: %d-%d`, score.HomePoints, score.AwayPoints) if isFloatEqual(param, total, 0.1) { // Совпасть могут только целые форы +3, +4 return &TipResult{ Status: model.Void, Result: resultText, } } if total < param { return &TipResult{ Status: model.Won, Result: resultText, } } else { return &TipResult{ Status: model.Lost, Result: resultText, } } } func (s Tennis3rdSetTotalUnder) GetTelegramTipPattern() string { return "" } ////////////////////////////////// // Плюсовая фора на Аутсайдера. // 1) Определяем Фаворита до лайва (коэф. <= 1.5) // В лайве наблюдаем и ждем выполнения 2 условий: // 2) Фаворит выигрывает 1й сет с тоталом < 8.5 // 3) В первом сете на фаворита давали фору -2.5 по ровной линии (коэф. от 1.75 до 1.95) // Прогноз: // Если на фаворита в 1м сете давали фору -2.5 в ровной линии, // то входим +3.5 на аутсайдера во 2м сете. Если давали -3.5 то входим +4.5; // -4.5 то входим +5.5 // Коэф. входа от 1.5 до 2.5, текущий счет во 2м сете не имеет значения. type Tennis2ndSetPlusHandicap struct { id int64 notifyInTelegram bool isChampAccepted func(string) bool } func NewTennis2ndSetPlusHandicap(opt StrategyOptions) Tennis2ndSetPlusHandicap { if opt.ID == 0 { panic("StrategyID not defined") } s := Tennis2ndSetPlusHandicap{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.isChampAccepted = opt.IsChampAccepted return s } func (s Tennis2ndSetPlusHandicap) ID() int64 { return s.id } func (s Tennis2ndSetPlusHandicap) ShortName() string { return fmt.Sprintf("#%d S2 Outsider Handicap", s.id) } func (s Tennis2ndSetPlusHandicap) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s Tennis2ndSetPlusHandicap) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 { return true } return false } func (s Tennis2ndSetPlusHandicap) IsInteresting(match *TeamMatch) (interface{}, bool) { // Поиск фаворита for _, offer := range match.Odds.Winner2Way { if offer.Home <= 1.5 { match.FavoriteSide = FavoriteHome return fmt.Sprintf("фаворит %s, коэф. %.2f", match.Home.CanonicalName, offer.Home), true } else if offer.Away <= 1.5 { match.FavoriteSide = FavoriteAway return fmt.Sprintf("фаворит %s, коэф. %.2f", match.Away.CanonicalName, offer.Away), true } } return nil, false } func (s Tennis2ndSetPlusHandicap) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) { if upd.CurrentPeriod == 1 { // поиски форы на 1й сет по ровной линии p, ok := upd.Periods[1] if !ok { return "ставки на 1й сет не найдены", nil, Waiting } var offers []onexbet.ParamOffer switch match.FavoriteSide { case FavoriteHome: offers = p.Handicap.Home case FavoriteAway: offers = p.Handicap.Away default: return "условие 'фаворит в матче' не выполнено", nil, Unwatch } // Масимальные минусовые форы первыми SortOffersByParamMinMax(offers) for _, offer := range offers { switch offer.Param { case "-2.5", "-3.0", "-3.5", "-4.0", "-4.5", "-5.0": if offer.Price >= 1.75 && offer.Price <= 1.95 { local := match.MaxFavoritHandicapIn1stSet if local == nil { match.MaxFavoritHandicapIn1stSet = &onexbet.ParamOffer{ Price: offer.Price, Param: offer.Param, ParamFloat: offer.ParamFloat, } } else { // нужно сравнить параметры if offer.ParamFloat < local.ParamFloat { // Фора стала еще больше - обновляем, ибо размер форы влияет // на плюсовую фору в прогнозе local.Param = offer.Param local.ParamFloat = offer.ParamFloat local.Price = offer.Price } } } case "-5.5": if offer.Price >= 1.75 && offer.Price <= 1.95 { return "1й сет, фора на фаворита -5.5", nil, Unwatch } } } } if upd.CurrentPeriod < 2 { return "не 2й сет", nil, Waiting } if upd.CurrentPeriod > 2 { return "2й сет завершен", nil, Unwatch } s1score, ok := upd.ScoreByPeriods[1] if !ok { // нет счета 1 сета return "счет 1го сета не найден", nil, Waiting } s1total := s1score.Home + s1score.Away if float64(s1total) > 8.5 { return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch } // Поиски тотала с коэф. >= 1.5 if match.MaxFavoritHandicapIn1stSet == nil { return "фора на фаворита в 1м сете от -2.5 и выше не была найдена", nil, Unwatch } p, ok := upd.Periods[2] if !ok { return "ставки на 2й сет не найдены", nil, Waiting } // Находим список фор на аутсайдера var ( offers []onexbet.ParamOffer betSide int ) switch match.FavoriteSide { case FavoriteHome: offers = p.Handicap.Away betSide = SideAway if (s1score.Home - s1score.Away) <= 0 { return "фаворит 1й сет проиграл", nil, Unwatch } case FavoriteAway: offers = p.Handicap.Home betSide = SideHome if (s1score.Away - s1score.Home) <= 0 { return "фаворит 1й сет проиграл", nil, Unwatch } default: return "условие 'фаворит в матче' не выполнено", nil, Unwatch } // Масимальные плюсовые форы первыми SortOffersByParamMaxMin(offers) // -2.5 превращаем в 3.4, -3 превращаем в 3.9 minPlusHandicap := math.Abs(match.MaxFavoritHandicapIn1stSet.ParamFloat) - 0.1 + 1 for _, offer := range offers { if offer.Price < 1.5 || offer.Price > 2.5 { continue } if offer.ParamFloat > minPlusHandicap { var sideName string switch betSide { case SideHome: sideName = match.Home.CanonicalName case SideAway: sideName = match.Away.CanonicalName } var additionalHandicaps string if offer.ParamFloat < 3.6 { // +3.5, +2.5 additionalHandicaps = fmt.Sprintf("и выше (+%.1f; +%.1f)", offer.ParamFloat+1, offer.ParamFloat+2) } else if offer.ParamFloat < 4.6 { // // +4.5, +4.0 additionalHandicaps = fmt.Sprintf("и выше (+%.1f)", offer.ParamFloat+1) } telegramMessage := fmt.Sprintf(`Сигнал # %%d. Алгоритм %d. Плюсовая фора на аутсайдера во 2м сете. %s %s - %s Точка входа +%.1f кф. %.3f Рекомендация: %s +%.1f %s`, s.id, match.Champ.Name, match.Home.CanonicalName, match.Away.CanonicalName, offer.ParamFloat, offer.Price, sideName, offer.ParamFloat, additionalHandicaps) return "", &BetDetails{ Market: MarketHandicap, Side: betSide, Param: offer.Param, Price: offer.Price, TelegramMessage: telegramMessage, }, Bet } } msg := fmt.Sprintf("не найдена фора %.1f и выше с коэф. от 1.5 до 2.5", math.Abs(match.MaxFavoritHandicapIn1stSet.ParamFloat)+1) return msg, nil, Waiting } func (s Tennis2ndSetPlusHandicap) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) { score, ok := result.ScoreByPeriods[2] if !ok { // Сперва проверяем что счет есть. return } // Проверяем что сет завершен if !isTennisSetCompleted(score) { return } var handicap float64 switch tip.Side { case SideHome: handicap = float64(score.AwayPoints - score.HomePoints) case SideAway: handicap = float64(score.HomePoints - score.AwayPoints) } param, err := strconv.ParseFloat(tip.Param, 64) if err != nil { fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param) return } resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints) if isFloatEqual(param, handicap, 0.1) { // Совпасть могут только целые форы +3, +4 return &TipResult{ Status: model.Void, Result: resultText, } } // Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно // просто сравнить if handicap < 0.1 { // если меньше нуля (наш теннисит выиграл 2й сет) return &TipResult{ Status: model.Won, Result: resultText, } } if handicap < param { return &TipResult{ Status: model.Won, Result: resultText, } } else { return &TipResult{ Status: model.Lost, Result: resultText, } } } func (s Tennis2ndSetPlusHandicap) GetTelegramTipPattern() string { return "" } //////////////////////////////////////// // Алгоритм 3 теннис // Плюсовая фора 2 сет // Равные соперники // Это ровная линия в кофициентах // 1.9 - 1.8 // 1.87 - 1.87 // 1.95 - 1.85 // Все покрытия: но в идеале Хард и грунт // Только мужчины // Условия: // Один спортсмен должен выиграть сет с большим преимуществом // Идеал: // 6:0 // 6:1 // Можно и 6:2 // Победитель первого сета должен получить КФ на победу 1.5 // Во втором сете мы ставим на плюсовую фору второго спортсмена // +2.5 // +3.5 // С КФ не ниже 1.5 // Диапазон 1.5 - 1.7 type Tennis2ndSetPlusHandicapAny struct { id int64 notifyInTelegram bool isChampAccepted func(string) bool } func NewTennis2ndSetPlusHandicapAny(opt StrategyOptions) Tennis2ndSetPlusHandicapAny { if opt.ID == 0 { panic("StrategyID not defined") } s := Tennis2ndSetPlusHandicapAny{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.isChampAccepted = opt.IsChampAccepted return s } func (s Tennis2ndSetPlusHandicapAny) ID() int64 { return s.id } func (s Tennis2ndSetPlusHandicapAny) ShortName() string { return fmt.Sprintf("#%d S2 Equal Handicap", s.id) } func (s Tennis2ndSetPlusHandicapAny) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s Tennis2ndSetPlusHandicapAny) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 { return true } return false } func (s Tennis2ndSetPlusHandicapAny) IsInteresting(match *TeamMatch) (interface{}, bool) { // Поиск фаворита for _, offer := range match.Odds.Winner2Way { if offer.Home >= 1.75 && offer.Home <= 1.97 { return nil, true } else if offer.Away >= 1.75 && offer.Away <= 1.97 { return nil, true } } return nil, false } func (s Tennis2ndSetPlusHandicapAny) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) { if upd.CurrentPeriod < 2 { return "не 2й сет", nil, Waiting } if upd.CurrentPeriod > 2 { return "2й сет завершен", nil, Unwatch } s1score, ok := upd.ScoreByPeriods[1] if !ok { // нет счета 1 сета return "счет 1го сета не найден", nil, Waiting } s1total := s1score.Home + s1score.Away if float64(s1total) > 8.5 { return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch } var ( favorite FavoriteSide favoriteWinnerPrice float64 ) if upd.Winner2Way == nil { return "коэф. на победу в матче не найден", nil, Waiting } if s1score.Home > s1score.Away { favorite = FavoriteHome favoriteWinnerPrice = upd.Winner2Way.Home } else { favorite = FavoriteAway favoriteWinnerPrice = upd.Winner2Way.Away } if favoriteWinnerPrice > 1.5 { return fmt.Sprintf("коэф. на победу в матче %.3f", favoriteWinnerPrice), nil, Waiting } sex := flashscore.ParseTennisSexFromChampName(match.FullChampName) if sex == "" { return "не удалось определить мужчины или женщины играют", nil, Unwatch } if sex == flashscore.SexWomen { champName := strings.ToLower(match.FullChampName) if !strings.Contains(champName, "wta") { return "женский матч должен играться на турнире серии WTA", nil, Unwatch } } p, ok := upd.Periods[2] if !ok { return "ставки на 2й сет не найдены", nil, Waiting } // Находим список фор на аутсайдера var ( offers []onexbet.ParamOffer betSide int ) switch favorite { case FavoriteHome: offers = p.Handicap.Away betSide = SideAway case FavoriteAway: offers = p.Handicap.Home betSide = SideHome default: return "условие 'фаворит в матче' не выполнено", nil, Unwatch } // Масимальные плюсовые форы первыми SortOffersByParamMaxMin(offers) // +2.5 minPlusHandicap := 2.4 for _, offer := range offers { if offer.Price < 1.5 || offer.Price > 1.7 { continue } if offer.ParamFloat > minPlusHandicap { var sideName string switch betSide { case SideHome: sideName = match.Home.CanonicalName case SideAway: sideName = match.Away.CanonicalName } var additionalHandicaps string if offer.ParamFloat < 3.6 { // +3.5, +2.5 additionalHandicaps = fmt.Sprintf("и выше (+%.1f; +%.1f)", offer.ParamFloat+1, offer.ParamFloat+2) } else if offer.ParamFloat < 4.6 { // // +4.5, +4.0 additionalHandicaps = fmt.Sprintf("и выше (+%.1f)", offer.ParamFloat+1) } telegramMessage := fmt.Sprintf(`Сигнал # %%d. Алгоритм %d. Плюсовая фора во 2м сете. %s %s - %s Точка входа +%.1f кф. %.3f Рекомендация: %s +%.1f %s`, s.id, match.Champ.Name, match.Home.CanonicalName, match.Away.CanonicalName, offer.ParamFloat, offer.Price, sideName, offer.ParamFloat, additionalHandicaps) return "", &BetDetails{ Market: MarketHandicap, Side: betSide, Param: offer.Param, Price: offer.Price, TelegramMessage: telegramMessage, }, Bet } } msg := fmt.Sprintf("не найдена фора +%.1f и выше с коэф. от 1.5 до 1.7", 2.5) return msg, nil, Waiting } func (s Tennis2ndSetPlusHandicapAny) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) { score, ok := result.ScoreByPeriods[2] if !ok { // Сперва проверяем что счет есть. return } // Проверяем что сет завершен if !isTennisSetCompleted(score) { return } var handicap float64 switch tip.Side { case SideHome: handicap = float64(score.AwayPoints - score.HomePoints) case SideAway: handicap = float64(score.HomePoints - score.AwayPoints) } param, err := strconv.ParseFloat(tip.Param, 64) if err != nil { fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param) return } resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints) if isFloatEqual(param, handicap, 0.1) { // Совпасть могут только целые форы +3, +4 return &TipResult{ Status: model.Void, Result: resultText, } } // Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно // просто сравнить if handicap < 0.1 { // если меньше нуля (наш теннисит выиграл 2й сет) return &TipResult{ Status: model.Won, Result: resultText, } } if handicap < param { return &TipResult{ Status: model.Won, Result: resultText, } } else { return &TipResult{ Status: model.Lost, Result: resultText, } } } func (s Tennis2ndSetPlusHandicapAny) GetTelegramTipPattern() string { return "" } //////////////////////////////////////// // 4 Алгоритм // плюсовая фора на Фаворита // Фаорит кф 1,6 и ниже проигрывает первый сет с большим преимуществом // 0:6 // 1:6 // 2:6 // Во втором сете мы ставим на плюсовую фору фаворита // +2.5 // +3.5 // Коэф. в диапазоне 1.5 - 2.5 type Tennis2ndSetFavoriteHandicap struct { id int64 notifyInTelegram bool isChampAccepted func(string) bool } func NewTennis2ndSetFavoriteHandicap(opt StrategyOptions) Tennis2ndSetFavoriteHandicap { if opt.ID == 0 { panic("StrategyID not defined") } s := Tennis2ndSetFavoriteHandicap{} s.id = opt.ID s.notifyInTelegram = opt.NotifyInTelegram s.isChampAccepted = opt.IsChampAccepted return s } func (s Tennis2ndSetFavoriteHandicap) ID() int64 { return s.id } func (s Tennis2ndSetFavoriteHandicap) ShortName() string { return fmt.Sprintf("#%d S2 Favorite Handicap", s.id) } func (s Tennis2ndSetFavoriteHandicap) IsNotifyInTelegram() bool { return s.notifyInTelegram } func (s Tennis2ndSetFavoriteHandicap) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool { if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 { return true } return false } func (s Tennis2ndSetFavoriteHandicap) IsInteresting(match *TeamMatch) (interface{}, bool) { // Поиск фаворита for _, offer := range match.Odds.Winner2Way { if offer.Home <= 1.6 { match.Lte16FavoriteSide = FavoriteHome return fmt.Sprintf("фаворит %s, коэф. %.2f", match.Home.CanonicalName, offer.Home), true } else if offer.Away <= 1.6 { match.Lte16FavoriteSide = FavoriteAway return fmt.Sprintf("фаворит %s, коэф. %.2f", match.Away.CanonicalName, offer.Away), true } } return nil, false } func (s Tennis2ndSetFavoriteHandicap) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) { if upd.CurrentPeriod < 2 { return "не 2й сет", nil, Waiting } if upd.CurrentPeriod > 2 { return "2й сет завершен", nil, Unwatch } s1score, ok := upd.ScoreByPeriods[1] if !ok { // нет счета 1 сета return "счет 1го сета не найден", nil, Waiting } var dif int switch match.Lte16FavoriteSide { case FavoriteHome: dif = s1score.Away - s1score.Home case FavoriteAway: dif = s1score.Home - s1score.Away } // фаворит должен проиграть со счетом 6-0, 6-1 или 6-2 if dif < 4 { return fmt.Sprintf("фора первого сета %d", dif), nil, Unwatch } s2, ok := upd.Periods[2] if !ok { return "ставки на 2й сет не найдены", nil, Waiting } // Находим список фор на аутсайдера var ( offers []onexbet.ParamOffer betSide int sideName string ) switch match.Lte16FavoriteSide { case FavoriteHome: offers = s2.Handicap.Home betSide = SideHome sideName = match.Home.CanonicalName case FavoriteAway: offers = s2.Handicap.Away betSide = SideAway sideName = match.Away.CanonicalName } // Масимальные плюсовые форы первыми SortOffersByParamMaxMin(offers) for _, offer := range offers { if offer.Price < 1.5 || offer.Price > 2.5 { continue } switch offer.Param { case "2.5", "3.5": additionalHandicaps := fmt.Sprintf("и выше (+%.1f; +%.1f)", offer.ParamFloat+1, offer.ParamFloat+2) telegramMessage := fmt.Sprintf(`Сигнал # %%d. Алгоритм %d. Плюсовая фора во 2м сете. %s %s - %s Точка входа +%.1f кф. %.3f Рекомендация: %s +%.1f %s`, s.id, match.Champ.Name, match.Home.CanonicalName, match.Away.CanonicalName, offer.ParamFloat, offer.Price, sideName, offer.ParamFloat, additionalHandicaps) return "", &BetDetails{ Market: MarketHandicap, Side: betSide, Param: offer.Param, Price: offer.Price, TelegramMessage: telegramMessage, }, Bet } } msg := fmt.Sprintf("не найдена фора +2.5 / +3.5 с коэф. от 1.5 до 2.5") return msg, nil, Waiting } // Общий код всех фор - можно выделить в отдельную функцию func (s Tennis2ndSetFavoriteHandicap) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) { score, ok := result.ScoreByPeriods[2] if !ok { // Сперва проверяем что счет есть. return } // Проверяем что сет завершен if !isTennisSetCompleted(score) { return } var handicap float64 switch tip.Side { case SideHome: handicap = float64(score.AwayPoints - score.HomePoints) case SideAway: handicap = float64(score.HomePoints - score.AwayPoints) } param, err := strconv.ParseFloat(tip.Param, 64) if err != nil { fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param) return } resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints) if isFloatEqual(param, handicap, 0.1) { // Совпасть могут только целые форы +3, +4 return &TipResult{ Status: model.Void, Result: resultText, } } // Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно // просто сравнить if handicap < 0.1 { // если меньше нуля (наш теннисит выиграл 2й сет) return &TipResult{ Status: model.Won, Result: resultText, } } if handicap < param { return &TipResult{ Status: model.Won, Result: resultText, } } else { return &TipResult{ Status: model.Lost, Result: resultText, } } } func (s Tennis2ndSetFavoriteHandicap) GetTelegramTipPattern() string { return "" }