Files
tipper/daemon/tennis.go
2026-07-21 05:29:37 +03:00

1126 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 ""
}