Files
tipper/daemon/tennis.go

1126 lines
28 KiB
Go
Raw Permalink Normal View History

2026-07-21 05:29:37 +03:00
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 ""
}