1126 lines
28 KiB
Go
1126 lines
28 KiB
Go
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 ""
|
||
}
|