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

800 lines
18 KiB
Go
Raw Permalink 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"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
func FootballHistoryProcessor(match *TeamMatch) {
}
/*
Алгоритм 2
ТБ 0.5 в Матче
До матча:
ТБ 2.5 (в матче) <=1.7
В 75% игр был гол
В Лайве:
Сумма атак обычных и опасных >= 135
6 ударов в сторону ворот OFF TARGET
4 удара в створ ON TARGET
До 70 минуты
*/
type FootballOver05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballOver05(opt StrategyOptions) FootballOver05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballOver05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballOver05) ID() int64 {
return s.id
}
func (s FootballOver05) ShortName() string {
return fmt.Sprintf("#%d FT Over 0.5 M", s.id)
}
func (s FootballOver05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballOver05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballOver05) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
type FootballOver05Notes struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
ZeroDrawsInH2H int `json:"zeroDrawsInH2H"`
ZeroDrawsPercentInH2H float64 `json:"zeroDrawsPercentInH2H"`
}
func (s FootballOver05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
//f flashscore.TotalOffer
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
zeroDrawsInH2HCount int
zeroDrawsPercent float64
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
} else {
zeroDrawsInH2HCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// h2hMatchCount - 100%
// zeroDrawsInH2HCount - x%
if zeroDrawsInH2HCount > 0 {
// Не более 2 сухих ничьи
if zeroDrawsInH2HCount > 2 {
return
}
zeroDrawsPercent = float64(len(report.H2H)*100) / float64(zeroDrawsInH2HCount)
// Не более 20% сухих ничьих
if zeroDrawsPercent > 20 {
return
}
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 80 {
return
}
obj := FootballOver05Notes{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
ZeroDrawsInH2H: zeroDrawsInH2HCount,
ZeroDrawsPercentInH2H: zeroDrawsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballOver05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забили", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 135 {
return fmt.Sprintf("%d атак < 135", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s FootballOver05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}
////////// P1 OVER 0.5
type FootballP1Over05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballP1Over05(opt StrategyOptions) FootballP1Over05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballP1Over05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballP1Over05) ID() int64 {
return s.id
}
func (s FootballP1Over05) ShortName() string {
return fmt.Sprintf("#%d H1 Over 0.5 M", s.id)
}
func (s FootballP1Over05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballP1Over05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Первый тайм, Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballP1Over05) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
if liveMatch.CurrentPeriod == 1 {
return true
}
return false
}
type isInterestingNotesFootballP1Over05 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInP1Matches int `json:"hasGoalInP1Matches"`
P1GoalsPercent float64 `json:"p1GoalsPercent"`
HasGoalInP1H2HMatches int `json:"hasGoalInP1H2HMatches"`
P1H2HGoalsPercent float64 `json:"p1H2HGoalsPercent"`
}
func (s FootballP1Over05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.58 {
return
}
var (
hasStatsMatchCount int
hasGoalsInP1MatchCount int
hasH2HStatsMatchCount int
hasGoalsInP1H2HMatchCount int
p1H2HGoalsPercent float64
)
for _, match := range report.HomeTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.AwayTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.H2H {
if match.HasScoreByPeriods {
hasH2HStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1H2HMatchCount++
}
}
}
if hasStatsMatchCount == 0 {
return
}
// hasStatsMatchCount - 100%
// hasGoalsInP1MatchCount - x%
p1GoalsPercent := float64(hasGoalsInP1MatchCount*100) / float64(hasStatsMatchCount)
if p1GoalsPercent < 75 {
return
}
if hasH2HStatsMatchCount > 0 {
// hasH2HStatsMatchCount - 100%
// hasGoalsInP1H2HMatchCount - x%
p1H2HGoalsPercent = float64(hasGoalsInP1H2HMatchCount*100) / float64(hasH2HStatsMatchCount)
if p1H2HGoalsPercent < 80 {
return
}
}
obj := isInterestingNotesFootballP1Over05{
PriceOver25: priceOver25,
MatchesAnalysed: hasStatsMatchCount,
HasGoalInP1Matches: hasGoalsInP1MatchCount,
P1GoalsPercent: p1GoalsPercent,
HasGoalInP1H2HMatches: hasGoalsInP1H2HMatchCount,
P1H2HGoalsPercent: p1H2HGoalsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballP1Over05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 20 минуты включительно.
maxTime := 20 * 60
if upd.CurrentTime > maxTime {
return "20 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забили", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.H1.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
// соотношение атак по времени >= 2.1 (за 10 минут от 21 атаки)
attacks := upd.HomeAttacks + upd.AwayAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
attacksRatio := float64(attacks*60) / float64(upd.CurrentTime)
if attacksRatio < 2.1 {
return fmt.Sprintf("отношение атак ко времени %.2f < 2.1", attacksRatio), nil, Waiting
}
if shotsOffTarget < 3 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotalH1,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s FootballP1Over05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
h1, ok := stats.ScoreByPeriods[1]
if !ok {
return
}
_, ok = stats.ScoreByPeriods[2]
if !ok {
return
}
res := new(TipResult)
h1TotalGoals := h1.HomePoints + h1.AwayPoints
if h1TotalGoals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints)
return res
}
//////////////// OVER 1.5
type FootballOver15 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballOver15(opt StrategyOptions) FootballOver15 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballOver15{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballOver15) ID() int64 {
return s.id
}
func (s FootballOver15) ShortName() string {
return fmt.Sprintf("#%d FT Over 1.5 M", s.id)
}
func (s FootballOver15) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballOver15) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет 2й ГОЛ!
Алгоритм %d
Тотал Больше 1.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballOver15) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
type isInterestingNotesFootballOver15 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
FavoritePrice float64 `json:"favoritePrice"`
Favorite string `json:"favorite"`
}
func (s FootballOver15) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var favoritePrice float64 = 2
var favorite string
// Проходим по всем ценам, каждый раз обновляя минимальную
for _, offer := range report.Odds.Winner {
if offer.Home < favoritePrice && offer.Home > 1.01 {
favoritePrice = offer.Home
favorite = "home"
}
if offer.Away < favoritePrice && offer.Away > 1.01 {
favoritePrice = offer.Away
favorite = "away"
}
}
if favoritePrice > 1.4 {
return
}
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 75 {
return
}
obj := isInterestingNotesFootballOver15{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
FavoritePrice: favoritePrice,
Favorite: favorite,
}
report.Favorite = favorite
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballOver15) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
goals := upd.HomeGoals + upd.AwayGoals
if goals > 1 {
// Если 2 гола уже забили - выходим
return "забили более 1 гола", nil, Unwatch
}
if goals == 0 {
return "счет 0-0", nil, Waiting
}
if (upd.HomeGoals == 1 && match.Favorite == "away") || (upd.AwayGoals == 1 && match.Favorite == "home") {
// все ок, фаворит уступает - анализируем дальше
} else {
// забил фаворит - далее неинтересно
return "забил фаворит", nil, Unwatch
}
// Ищем тотал Больше 1.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "1.5" {
if over.Price < minPrice {
comment := fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice)
return comment, nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 1.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 125 {
return fmt.Sprintf("%d атак < 125", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "1.5",
Price: currentPrice,
}, Bet
}
func (s FootballOver15) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 1 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}