1st
This commit is contained in:
1262
daemon/daemon.go
Normal file
1262
daemon/daemon.go
Normal file
File diff suppressed because it is too large
Load Diff
77
daemon/daemon_test.go
Normal file
77
daemon/daemon_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
func TestCorrectTotal(t *testing.T) {
|
||||
//total := 58.5
|
||||
//total = total - 8
|
||||
|
||||
total, _ := strconv.ParseFloat("68.000", 64)
|
||||
|
||||
//total := 68.0
|
||||
|
||||
total = correctTotal(total, roundUp)
|
||||
|
||||
fmt.Printf("%.2f\n", total)
|
||||
}
|
||||
*/
|
||||
/*
|
||||
func TestOver(t *testing.T) {
|
||||
s := NewHandballFTOver(StrategyOptions{
|
||||
ID: 1,
|
||||
})
|
||||
|
||||
match := &TeamMatch{
|
||||
TipTotalOver: 50.5,
|
||||
BasicUnderGoalsPerMinute: 1, // 1 гол в минуту
|
||||
}
|
||||
|
||||
upd := onexbet.TeamLiveMatchData{
|
||||
CurrentTime: 20 * 60, // 30 min
|
||||
HomeGoals: 12,
|
||||
AwayGoals: 10,
|
||||
Total: onexbet.Total{
|
||||
Over: []onexbet.ParamOffer{
|
||||
{
|
||||
Price: 1.41,
|
||||
Param: "48.5",
|
||||
},
|
||||
{
|
||||
Price: 1.5,
|
||||
Param: "49.5",
|
||||
},
|
||||
{
|
||||
Price: 1.51,
|
||||
Param: "50.5",
|
||||
},
|
||||
{
|
||||
Price: 1.65,
|
||||
Param: "51.5",
|
||||
},
|
||||
{
|
||||
Price: 1.85,
|
||||
Param: "52.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
comment, bet, code := s.GetTip(match, upd)
|
||||
|
||||
fmt.Printf("Code: %d\n", code)
|
||||
fmt.Printf("Comment: %s\n", comment)
|
||||
|
||||
buf, _ := json.MarshalIndent(bet, "", " ")
|
||||
fmt.Printf("Bet: %s\n", buf)
|
||||
}
|
||||
*/
|
||||
|
||||
func TestTelegram(t *testing.T) {
|
||||
err := sendMessageToTelegramGroup("754217958", "Персональное сообщение Максиму")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
799
daemon/football.go
Normal file
799
daemon/football.go
Normal file
@@ -0,0 +1,799 @@
|
||||
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
|
||||
}
|
||||
1165
daemon/handball.go
Normal file
1165
daemon/handball.go
Normal file
File diff suppressed because it is too large
Load Diff
41
daemon/helpers.go
Normal file
41
daemon/helpers.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gordenko.dev/dima/onexbet"
|
||||
)
|
||||
|
||||
func isFloatEqual(a, b, eps float64) bool {
|
||||
if math.Abs(a-b) < eps {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SortOffersByParamMaxMin(offers []onexbet.ParamOffer) {
|
||||
sort.Slice(offers, func(i, j int) bool {
|
||||
if offers[i].ParamFloat > offers[j].ParamFloat {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func SortOffersByParamMinMax(offers []onexbet.ParamOffer) {
|
||||
sort.Slice(offers, func(i, j int) bool {
|
||||
if offers[i].ParamFloat < offers[j].ParamFloat {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func isTennisSetCompleted(score onexbet.PeriodScore) (_ bool) {
|
||||
if (score.HomePoints == 6 && score.AwayPoints < 5) || score.HomePoints == 7 ||
|
||||
(score.AwayPoints == 6 && score.HomePoints < 5) || score.AwayPoints == 7 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
875
daemon/onexbet.go
Executable file
875
daemon/onexbet.go
Executable file
@@ -0,0 +1,875 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/onexbet"
|
||||
"gordenko.dev/dima/tipper/model"
|
||||
"gordenko.dev/dima/web/api"
|
||||
"gordenko.dev/dima/ws"
|
||||
)
|
||||
|
||||
func (s *Daemon) tryLinkToLiveMatch(sportID int, liveMatch onexbet.LiveMatch) {
|
||||
matchName := fmt.Sprintf("%s - %s", liveMatch.Home, liveMatch.Away)
|
||||
|
||||
s.logger.Printf("try link: %s\n", matchName)
|
||||
// Если уже распознан - пропускаем
|
||||
|
||||
// На всякий пожарный проверка
|
||||
//_, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
|
||||
//if ok {
|
||||
// s.logger.Printf("try link: %s\n", matchName)
|
||||
// return
|
||||
//}
|
||||
|
||||
// Если этот матч уже проверяли - игнорируем
|
||||
s.mutex.Lock()
|
||||
|
||||
unrecognizedMatches, ok := s.unrecognizedOnexbetMatches[sportID]
|
||||
if !ok {
|
||||
s.logger.Printf("unrecognized matches for the sport %d not found\n", sportID)
|
||||
return
|
||||
}
|
||||
|
||||
if unrecognizedMatches[liveMatch.MatchID] {
|
||||
//s.logger.Printf("Match (%s) is unrecognized. Don't repeat identification\n", matchName)
|
||||
s.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
s.mutex.Unlock()
|
||||
|
||||
homeTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Home)
|
||||
if err != nil {
|
||||
s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
//s.logger.Printf("homeTeamCandidates (for %s): %q", liveMatch.Home, homeTeamCandidates)
|
||||
|
||||
awayTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Away)
|
||||
if err != nil {
|
||||
s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
//s.logger.Printf("awayTeamCandidates (for %s): %q", liveMatch.Away, awayTeamCandidates)
|
||||
//
|
||||
|
||||
teamMatches, ok := s.teamMatches[sportID]
|
||||
if !ok {
|
||||
err = fmt.Errorf("Not found matches for the sportID %d", sportID)
|
||||
return
|
||||
}
|
||||
|
||||
for _, match := range teamMatches {
|
||||
var isTeamsSwapped bool
|
||||
|
||||
isIdentified := s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
|
||||
homeTeamCandidates, awayTeamCandidates)
|
||||
|
||||
if !isIdentified {
|
||||
// Пробуем опознать если home и away поменять местами
|
||||
isIdentified = s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
|
||||
awayTeamCandidates, homeTeamCandidates)
|
||||
|
||||
isTeamsSwapped = true
|
||||
}
|
||||
|
||||
if isIdentified {
|
||||
|
||||
if !match.Home.WasLinked {
|
||||
err = s.fsmodel.SetTeamWasLinked(match.Home.TeamID)
|
||||
if err != nil {
|
||||
s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
|
||||
err, match.Home.TeamID)
|
||||
}
|
||||
s.mutex.Lock()
|
||||
match.Home.WasLinked = true
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
if !match.Away.WasLinked {
|
||||
err = s.fsmodel.SetTeamWasLinked(match.Away.TeamID)
|
||||
if err != nil {
|
||||
s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
|
||||
err, match.Away.TeamID)
|
||||
}
|
||||
s.mutex.Lock()
|
||||
match.Away.WasLinked = true
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
match.IsLinkedToOnexbet = true
|
||||
match.OnexbetMatchID = liveMatch.MatchID
|
||||
match.OnexbetStatsMatchID = liveMatch.StatsMatchID
|
||||
match.Score = liveMatch.Score
|
||||
match.ScoreByPeriods = liveMatch.ScoreByPeriods
|
||||
match.CurrentPeriod = liveMatch.CurrentPeriod
|
||||
match.IsTeamsSwapped = isTeamsSwapped
|
||||
|
||||
s.onexbetMatchToTeamMatch[liveMatch.MatchID] = match
|
||||
s.identifiedMatches[liveMatch.MatchID] = true
|
||||
|
||||
s.logger.Printf("Identified: %s; %s; %s\n", matchName, liveMatch.MatchID, match.MatchID)
|
||||
|
||||
// Матч опознан и перешел в inplay
|
||||
s.publisher.Publish(
|
||||
getChannel(match.SportID),
|
||||
WSMessageInplayStatusChanged,
|
||||
InplayStatusMessage{
|
||||
MatchID: match.MatchID,
|
||||
IsInplay: match.IsInplay,
|
||||
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
||||
},
|
||||
)
|
||||
s.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
// Кешируем негативный результат, чтобы не искать в базе ежеминутно
|
||||
// Если пользователь распознает новые матчи, - кэш будет очищен
|
||||
unrecognizedMatches, ok = s.unrecognizedOnexbetMatches[sportID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
unrecognizedMatches[liveMatch.MatchID] = true
|
||||
s.mutex.Unlock()
|
||||
|
||||
s.logger.Printf("Match (%s; %s) is not identified. Added to unrecognized\n", matchName, liveMatch.MatchID)
|
||||
}
|
||||
|
||||
func (s *Daemon) tryIdentifyMatch(homeTeamID, awayTeamID string, homeTeamCandidates, awayTeamCandidates []string) (_ bool) {
|
||||
for _, candidateHomeTeamID := range homeTeamCandidates {
|
||||
if homeTeamID == candidateHomeTeamID {
|
||||
// Если нашли совпадение для home - ищем совпадение для away
|
||||
for _, candidateAwayTeamID := range awayTeamCandidates {
|
||||
if awayTeamID == candidateAwayTeamID {
|
||||
// Mатч идентифицирован
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Daemon) onOnexbetWsClientConnected(conn *ws.Conn) {
|
||||
s.logger.Printf("Onexbet Ws Client Connected\n")
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Daemon) onOnexbetMatchFinished(conn *ws.Conn, in onexbet.WatchReq) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
match, ok := s.onexbetMatchToTeamMatch[in.MatchID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
match.IsInplay = false
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *Daemon) onOnexbetTeamLiveMatches(conn *ws.Conn, in onexbet.LiveMatchesMessage) {
|
||||
//s.logger.Println("onOnexbetTeamLiveMatches")
|
||||
//s.logger.Printf("onexbetMatchToTeamMatch size: %d\n", len(s.onexbetMatchToTeamMatch))
|
||||
|
||||
sportID, ok := onexbetSportToFlashscoreSport[in.SportID]
|
||||
if !ok {
|
||||
s.logger.Printf("Bug: unknown OnexbetSport %d\n", in.SportID)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("Onexbet live matches: %d, watching: %d; Inplay: %d\n",
|
||||
len(in.Matches), len(in.Watching), len(s.onexbetMatchToTeamMatch))
|
||||
|
||||
liveMap := make(map[string]onexbet.LiveMatch)
|
||||
|
||||
// В цикле рашем 3 задачи:
|
||||
// - формируем словарь из live матчей на сайте 1xBet, для удобства дальнейшей обработки
|
||||
// - для матчей в inplay проверяем не появился ли liveMatch.StatsMatchID, по которому
|
||||
// можно будет узнать результат матча на сайте 1xBet
|
||||
// - пытаемся распознать новые матчи
|
||||
for _, liveMatch := range in.Matches {
|
||||
liveMap[liveMatch.MatchID] = liveMatch
|
||||
|
||||
match, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
|
||||
if ok {
|
||||
s.logger.Printf("Match (%s - %s) already identified\n", match.Home.CanonicalName, match.Away.CanonicalName)
|
||||
// ВАЖНО!
|
||||
// liveMatch.StatsMatchID появится когда матч перейдет в лайв.
|
||||
if match.OnexbetStatsMatchID == "" && liveMatch.StatsMatchID != "" {
|
||||
match.OnexbetStatsMatchID = liveMatch.StatsMatchID
|
||||
match.Score = liveMatch.Score
|
||||
match.ScoreByPeriods = liveMatch.ScoreByPeriods
|
||||
match.CurrentPeriod = liveMatch.CurrentPeriod
|
||||
}
|
||||
} else {
|
||||
if !s.identifiedMatches[liveMatch.MatchID] {
|
||||
// Матча нет среди распознанных - пытаемся опознать
|
||||
s.tryLinkToLiveMatch(sportID, liveMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var watchingMap = make(map[string]bool)
|
||||
|
||||
for _, onexbetMatchID := range in.Watching {
|
||||
watchingMap[onexbetMatchID] = true
|
||||
|
||||
_, isMatchInInplay := s.onexbetMatchToTeamMatch[onexbetMatchID]
|
||||
if !isMatchInInplay {
|
||||
// Матча больше нет в inplay. Если 1xbet присылает его в Watching -
|
||||
// значит API-запрос Unwatch где-то потерялся. Поэтому отправляем снова.
|
||||
s.onexbetAPIClient.Call(api.CallReq{
|
||||
FuncName: "unwatch",
|
||||
In: onexbet.WatchReq{
|
||||
SportID: in.SportID,
|
||||
MatchID: onexbetMatchID,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Цикл по inplay матчам
|
||||
//if sportID == flashscore.Tennis {
|
||||
//
|
||||
//s.checkInplayTennisMatches(liveMap, watchingMap)
|
||||
//} else {
|
||||
s.checkInplayTeamMatches(sportID, in.SportID, liveMap, watchingMap)
|
||||
//}
|
||||
}
|
||||
|
||||
//func (s *Daemon) checkInplayTennisMatches(liveMap map[string]bool, watchingMap map[string]bool) {
|
||||
//for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
|
||||
//if match.
|
||||
//}
|
||||
//}
|
||||
|
||||
func (s *Daemon) checkInplayTeamMatches(sportID int, onexbetSportID onexbet.OnexbetSport, liveMap map[string]onexbet.LiveMatch, watchingMap map[string]bool) {
|
||||
for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
|
||||
if match.SportID != sportID {
|
||||
// Если другой спорт - пропускаем матч
|
||||
continue
|
||||
}
|
||||
|
||||
liveMatch, isIn1xbetLive := liveMap[onexbetMatchID]
|
||||
|
||||
if !isIn1xbetLive {
|
||||
// МАТЧИ могут просто исчезать из ЛАЙВ списка, поэтому просто удалять
|
||||
// нельзя! Сперва запросим результат матча у 1xbet и решим что делать.
|
||||
//err := s.processMatchDisappearedFromLive(match, onexbetMatchID)
|
||||
//if err != nil {
|
||||
// s.logger.Printf("processMatchDisappearedFromLive: %s\n", err)
|
||||
//}
|
||||
|
||||
s.logger.Printf("Inplay match (%s - %s) not found in 1xbet live matches. Remove from inplay\n",
|
||||
match.Home.CanonicalName, match.Away.CanonicalName)
|
||||
|
||||
s.removeMatchFromInplay(match, onexbetMatchID)
|
||||
} else {
|
||||
if !watchingMap[onexbetMatchID] {
|
||||
// Матч есть в лайве 1xbet (liveMap) - но нет в наблюдаемых. Например,
|
||||
// перезагрузился 1xbet демон, или отписались.
|
||||
if len(match.Strategies) > 0 {
|
||||
// Если еще есть не сработавшие стратегии - начинаем наблюдать.
|
||||
//s.logger.Printf("Watch: %d, %s\n", in.SportID, onexbetMatchID)
|
||||
|
||||
// Подписываемся на матчи, которые только что идентифицировали + на те, которые
|
||||
// пропали из watch списка (например из-за перезагрузки 1xBET демона)
|
||||
|
||||
for _, strategy := range match.Strategies {
|
||||
if strategy.IsNeedToWatch(liveMatch) {
|
||||
// Если хотя бы одной стратегии нужно наблюдать за матчем
|
||||
// - подписываемся и прерываем цикл
|
||||
match.IsInplay = true
|
||||
|
||||
s.onexbetAPIClient.Call(api.CallReq{
|
||||
FuncName: "watch",
|
||||
In: onexbet.WatchReq{
|
||||
SportID: onexbetSportID,
|
||||
MatchID: onexbetMatchID,
|
||||
},
|
||||
})
|
||||
|
||||
s.mutex.Lock()
|
||||
// Матч дабавили в inplay
|
||||
s.publisher.Publish(
|
||||
getChannel(match.SportID),
|
||||
WSMessageInplayStatusChanged,
|
||||
InplayStatusMessage{
|
||||
MatchID: match.MatchID,
|
||||
IsInplay: match.IsInplay,
|
||||
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
||||
},
|
||||
)
|
||||
s.mutex.Unlock()
|
||||
// ВАЖНО!
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.mutex.Lock()
|
||||
//s.logger.Printf("Unwatch: %d, %s\n", in.SportID, onexbetMatchID)
|
||||
// Матч больше не интересен
|
||||
// Все стратегии сработали - отписываемся от матча
|
||||
delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
|
||||
match.IsInplay = false
|
||||
|
||||
s.onexbetAPIClient.Call(api.CallReq{
|
||||
FuncName: "unwatch",
|
||||
In: onexbet.WatchReq{
|
||||
SportID: onexbetSportID,
|
||||
MatchID: onexbetMatchID,
|
||||
},
|
||||
})
|
||||
|
||||
// Матч убрали из inplay
|
||||
s.publisher.Publish(
|
||||
getChannel(match.SportID),
|
||||
WSMessageInplayStatusChanged,
|
||||
InplayStatusMessage{
|
||||
MatchID: match.MatchID,
|
||||
IsInplay: match.IsInplay,
|
||||
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
||||
},
|
||||
)
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
} else {
|
||||
// Матч есть в лайве 1xbet (liveMap) и в наблюдаемых - все OK.
|
||||
// Ничего не делаем.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Daemon) processMatchDisappearedFromLive(match *TeamMatch, onexbetMatchID string) (err error) {
|
||||
if match.OnexbetStatsMatchID == "" {
|
||||
// Ситуация возвожна, когда матч появился на короткий срок, OnexbetStatsMatchID еще
|
||||
// не получил и затем исчез.
|
||||
err = fmt.Errorf("1xBet match %s has empty OnexbetStatsMatchID", onexbetMatchID)
|
||||
return
|
||||
}
|
||||
|
||||
matchResult, err := onexbet.GetTeamMatchResult(match.OnexbetStatsMatchID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("onexbet.GetTeamMatchResult: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if matchResult.Status != onexbet.StatusMatchCompleted {
|
||||
// Если матч не завершен - пропускаем. Предполагаем что матч просто
|
||||
// временно исчез из лайва
|
||||
return
|
||||
}
|
||||
// Матч завершен - загружаем прогнозы, чтобы выставить результаты
|
||||
tips, err := s.model.ListMatchTips(match.MatchID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("model.ListMatchTips: %s; matchID=%s", err, match.MatchID)
|
||||
return
|
||||
}
|
||||
|
||||
if len(tips) > 0 {
|
||||
// На один матч может быть несколько прогнозов, поэтому важно знать что результаты
|
||||
// всех прогнозов удалось записать в БД. Пока все результаты записать не удастся -
|
||||
// это метод будет вызыватся снова и снова.
|
||||
for _, tip := range tips {
|
||||
err = s.trySetTipResult(tip, matchResult)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("trySetTipResult: %s; tipID=%d", err, tip.TipID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Результат всех прогнозов по матчу успешно записан либо прогнозов не было.
|
||||
// В любом случае - удаляем матч из лайва.
|
||||
s.removeMatchFromInplay(match, onexbetMatchID)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *Daemon) removeMatchFromInplay(match *TeamMatch, onexbetMatchID string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
|
||||
match.IsInplay = false
|
||||
|
||||
// Матч убрали из inplay
|
||||
s.publisher.Publish(
|
||||
getChannel(match.SportID),
|
||||
WSMessageInplayStatusChanged,
|
||||
InplayStatusMessage{
|
||||
MatchID: match.MatchID,
|
||||
IsInplay: match.IsInplay,
|
||||
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type WhyNot struct {
|
||||
MatchID string `json:"matchID"`
|
||||
Comment string `json:"comment"`
|
||||
Time string `json:"time"`
|
||||
HomeGoals int `json:"homeGoals"`
|
||||
AwayGoals int `json:"awayGoals"`
|
||||
GamePace float64 `json:"gamePace"`
|
||||
GamePaceFrame int `json:"gamePaceFrame"` // count from Zero
|
||||
}
|
||||
|
||||
//func (s *Daemon) onOnexbetTennisLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
|
||||
// s.mutex.Lock()
|
||||
// defer s.mutex.Unlock()
|
||||
|
||||
// s.logger.Printf("tennis MatchData: %s\n", upd.MatchID)
|
||||
|
||||
//}
|
||||
/*
|
||||
type TeamLiveMatchData struct {
|
||||
MatchID string `json:"matchID"`
|
||||
HomeAttacks int `json:"homeAttacks"`
|
||||
HomeDangerousAttacks int `json:"homeDangerousAttacks"`
|
||||
HomeShotsOnTarget int `json:"homeShotsOnTarget"`
|
||||
HomeShotsOffTarget int `json:"homeShotsOffTarget"`
|
||||
AwayAttacks int `json:"awayAttacks"`
|
||||
AwayDangerousAttacks int `json:"awayDangerousAttacks"`
|
||||
AwayShotsOnTarget int `json:"awayShotsOnTarget"`
|
||||
AwayShotsOffTarget int `json:"awayShotsOffTarget"`
|
||||
CurrentTime int `json:"currentTime"`
|
||||
HomeGoals int `json:"homeGoals"`
|
||||
AwayGoals int `json:"awayGoals"`
|
||||
Winner *Winner3Way `json:"winner"`
|
||||
Total Total `json:"total"`
|
||||
Handicap Handicap `json:"handicap"`
|
||||
IndividualTotalHome Total `json:"individualTotalHome"`
|
||||
IndividualTotalAway Total `json:"individualTotalAway"`
|
||||
H1 Half `json:"h1"` // 1st half
|
||||
// tennis
|
||||
Winner2Way *Winner2Way `json:"winner2way"`
|
||||
|
||||
// score
|
||||
Score Score `json:"score"`
|
||||
ScoreByPeriods map[int]Score `json:"scoreByPeriods"`
|
||||
CurrentPeriod int `json:"currentPeriod"`
|
||||
Periods map[int]Period `json:"periods"`
|
||||
}
|
||||
*/
|
||||
|
||||
func swapTeamLiveMatchData(d onexbet.TeamLiveMatchData) onexbet.TeamLiveMatchData {
|
||||
d.AwayAttacks, d.HomeAttacks = d.HomeAttacks, d.AwayAttacks
|
||||
d.AwayDangerousAttacks, d.HomeDangerousAttacks = d.HomeDangerousAttacks, d.AwayDangerousAttacks
|
||||
d.AwayShotsOnTarget, d.HomeShotsOnTarget = d.HomeShotsOnTarget, d.AwayShotsOnTarget
|
||||
d.AwayShotsOffTarget, d.HomeShotsOffTarget = d.HomeShotsOffTarget, d.AwayShotsOffTarget
|
||||
d.AwayGoals, d.HomeGoals = d.HomeGoals, d.AwayGoals
|
||||
|
||||
if d.Winner != nil {
|
||||
d.Winner.Away, d.Winner.Home = d.Winner.Home, d.Winner.Away
|
||||
}
|
||||
|
||||
if d.Winner2Way != nil {
|
||||
d.Winner2Way.Away, d.Winner2Way.Home = d.Winner2Way.Home, d.Winner2Way.Away
|
||||
}
|
||||
|
||||
d.Handicap.Away, d.Handicap.Home = d.Handicap.Home, d.Handicap.Away
|
||||
d.IndividualTotalAway, d.IndividualTotalHome = d.IndividualTotalHome, d.IndividualTotalAway
|
||||
|
||||
d.H1.IndividualTotalAway, d.H1.IndividualTotalHome = d.H1.IndividualTotalHome, d.H1.IndividualTotalAway
|
||||
|
||||
d.Score.Away, d.Score.Home = d.Score.Home, d.Score.Away
|
||||
|
||||
for periodNumber, score := range d.ScoreByPeriods {
|
||||
score.Away, score.Home = score.Home, score.Away
|
||||
|
||||
d.ScoreByPeriods[periodNumber] = score
|
||||
}
|
||||
|
||||
for periodNumber, period := range d.Periods {
|
||||
period.Handicap.Away, period.Handicap.Home = period.Handicap.Home, period.Handicap.Away
|
||||
|
||||
d.Periods[periodNumber] = period
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *Daemon) onOnexbetTeamLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.logger.Printf("MatchData: %s\n", upd.MatchID)
|
||||
|
||||
// маппинг -
|
||||
match, ok := s.onexbetMatchToTeamMatch[upd.MatchID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if match.IsTeamsSwapped {
|
||||
upd = swapTeamLiveMatchData(upd)
|
||||
}
|
||||
|
||||
//if match.SportID == flashscore.Handball {
|
||||
//x, _ := json.MarshalIndent(upd, "", " ")
|
||||
//fmt.Printf("MatchID: %s (1xbet MatchID: %s), Time: %d, HomeGoals: %d, AwayGoals: %d\n",
|
||||
// match.MatchID, upd.MatchID, upd.CurrentTime, upd.HomeGoals, upd.AwayGoals)
|
||||
//}
|
||||
|
||||
channel := getChannel(match.SportID)
|
||||
|
||||
//s.logger.Printf("Onexbet TeamLiveMatchData: sportID %d, channel %s\n", match.SportID, channel)
|
||||
|
||||
var (
|
||||
triggeredStrategyIDs []int64
|
||||
comments []string
|
||||
strategyIDs []int
|
||||
)
|
||||
|
||||
for strategyID := range match.Strategies {
|
||||
strategyIDs = append(strategyIDs, int(strategyID))
|
||||
}
|
||||
|
||||
sort.Ints(strategyIDs)
|
||||
|
||||
for _, strategyID := range strategyIDs {
|
||||
|
||||
strategy := match.Strategies[int64(strategyID)]
|
||||
// Если по стратегии уже дали прогноз
|
||||
//if match.EndedStrategies[strategy.ID()] {
|
||||
// continue
|
||||
//}
|
||||
|
||||
whyNot, bet, code := strategy.GetTip(match, upd)
|
||||
|
||||
switch code {
|
||||
case Waiting:
|
||||
// pass
|
||||
//s.logger.Printf("Waiting: %s\n", whyNot)
|
||||
|
||||
// Отправить в websocket
|
||||
comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))
|
||||
|
||||
case Unwatch:
|
||||
s.logger.Printf("Unwatch: %s\n", whyNot)
|
||||
|
||||
triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())
|
||||
|
||||
comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))
|
||||
|
||||
case Bet:
|
||||
|
||||
triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())
|
||||
|
||||
var err error
|
||||
//var tip model.Tip
|
||||
//var tipPrice float64
|
||||
|
||||
tip := model.Tip{
|
||||
SportID: int64(match.SportID),
|
||||
StrategyID: strategy.ID(),
|
||||
MatchID: match.MatchID,
|
||||
TipTime: time.Now().Unix(),
|
||||
ChampID: match.Champ.ChampID,
|
||||
ChampName: match.Champ.Name,
|
||||
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
||||
Market: bet.Market,
|
||||
Side: bet.Side,
|
||||
Param: bet.Param,
|
||||
Price: bet.Price,
|
||||
CurrentMatchTime: upd.CurrentTime,
|
||||
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
||||
IsTeamsSwapped: match.IsTeamsSwapped,
|
||||
}
|
||||
|
||||
champRatings, ok := s.champRatings[match.SportID]
|
||||
if ok {
|
||||
// обновляем рейтинги чемпионатов
|
||||
key := ChampRatingKey{
|
||||
ChampID: tip.ChampID,
|
||||
StrategyID: tip.StrategyID,
|
||||
}
|
||||
|
||||
rating, ok := champRatings[key]
|
||||
if ok {
|
||||
//if (rating.Won+rating.Lost) >= 6 && rating.GetAccuracy() >= 75 {
|
||||
if rating.GetAccuracy() >= 70 {
|
||||
tip.IsRated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Printf("Bet: %#v\n", tip)
|
||||
|
||||
tip.TipID, err = s.model.AddTip(tip)
|
||||
if err != nil {
|
||||
s.logger.Printf("model.AddTip: %s\n", err)
|
||||
} else {
|
||||
// Отправить в websocket
|
||||
// Добавили новый Tip
|
||||
s.publisher.Publish(channel, WSMessageTipsChanged, nil)
|
||||
|
||||
if match.SportID == flashscore.Football {
|
||||
// Для футбола только рейтинговые чемпионаты
|
||||
// Отправить в телеграм
|
||||
if strategy.IsNotifyInTelegram() && tip.IsRated {
|
||||
// Отправлять ли в телеграм
|
||||
msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
|
||||
match.Zone.Name, match.Champ.Name,
|
||||
match.Home.CanonicalName, match.Away.CanonicalName,
|
||||
bet.Price)
|
||||
|
||||
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
||||
if hasChat {
|
||||
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
||||
if err != nil {
|
||||
s.logger.Printf("send tip to Telegram: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
||||
}
|
||||
}
|
||||
} else if match.SportID == flashscore.Handball {
|
||||
if strategy.IsNotifyInTelegram() && tip.IsRated {
|
||||
param, err := strconv.ParseFloat(bet.Param, 64)
|
||||
if err != nil {
|
||||
s.logger.Printf("Wrong bet param: %s; param=%s\n", err, bet.Param)
|
||||
} else {
|
||||
|
||||
var betterParam1, betterParam2 float64
|
||||
|
||||
switch strategy.(type) {
|
||||
case HandballFTOver, HandballP1Over:
|
||||
betterParam1 = param - 1
|
||||
betterParam2 = param - 2
|
||||
|
||||
case HandballFTUnder, HandballP1Under:
|
||||
betterParam1 = param + 1
|
||||
betterParam2 = param + 2
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown strategy %#v\n", strategy))
|
||||
}
|
||||
// Для гандбола отправляем все прогнозы
|
||||
|
||||
msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
|
||||
match.Zone.Name, match.Champ.Name,
|
||||
match.Home.CanonicalName, match.Away.CanonicalName,
|
||||
param, bet.Price, param, betterParam1, betterParam2)
|
||||
|
||||
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
||||
if hasChat {
|
||||
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
||||
if err != nil {
|
||||
s.logger.Printf("send tip to Telegram: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if match.SportID == flashscore.Tennis {
|
||||
if strategy.IsNotifyInTelegram() {
|
||||
msg := fmt.Sprintf(bet.TelegramMessage, tip.TipID)
|
||||
|
||||
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
||||
if hasChat {
|
||||
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
||||
if err != nil {
|
||||
s.logger.Printf("send tip to Telegram: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, strategyID := range triggeredStrategyIDs {
|
||||
delete(match.Strategies, strategyID)
|
||||
}
|
||||
|
||||
if len(match.Strategies) == 0 {
|
||||
// Все стратегии сработали - отписываемся от матча
|
||||
|
||||
s.logger.Printf("Unwatch: %d, %s\n", match.SportID, upd.MatchID)
|
||||
|
||||
// Матч больше не интересен
|
||||
delete(s.onexbetMatchToTeamMatch, upd.MatchID)
|
||||
match.IsInplay = false
|
||||
|
||||
s.onexbetAPIClient.Call(api.CallReq{
|
||||
FuncName: "unwatch",
|
||||
In: onexbet.WatchReq{
|
||||
SportID: flashscoreSportToOnexbetSport[match.SportID],
|
||||
MatchID: upd.MatchID, // onexbetMatchID
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if match.SportID == flashscore.Tennis {
|
||||
if len(comments) > 0 {
|
||||
s.publisher.Publish(
|
||||
channel,
|
||||
WSMessageWhyNot,
|
||||
WhyNot{
|
||||
MatchID: match.MatchID,
|
||||
Comment: strings.Join(comments, "<br><br>"),
|
||||
Time: "",
|
||||
HomeGoals: upd.HomeGoals,
|
||||
AwayGoals: upd.AwayGoals,
|
||||
GamePace: match.GamePace,
|
||||
GamePaceFrame: match.GamePaceFrame,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if len(comments) > 0 {
|
||||
s.publisher.Publish(
|
||||
channel,
|
||||
WSMessageWhyNot,
|
||||
WhyNot{
|
||||
MatchID: match.MatchID,
|
||||
Comment: strings.Join(comments, "<br><br>"),
|
||||
Time: fmt.Sprintf("%s", time.Duration(upd.CurrentTime)*time.Second),
|
||||
HomeGoals: upd.HomeGoals,
|
||||
AwayGoals: upd.AwayGoals,
|
||||
GamePace: match.GamePace,
|
||||
GamePaceFrame: match.GamePaceFrame,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var tipTextPattern string
|
||||
|
||||
switch strategyID {
|
||||
case 1:
|
||||
tipTextPattern = tipTextAlgo1Pattern
|
||||
|
||||
case 2:
|
||||
tipTextPattern = tipTextAlgo2Pattern
|
||||
|
||||
case 3:
|
||||
tipTextPattern = tipTextAlgo3Pattern
|
||||
|
||||
case 100:
|
||||
tipTextPattern = tipTextAlgo100Pattern
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
switch strategyID {
|
||||
case 1:
|
||||
var price float64
|
||||
for _, over := range upd.H1.Total.Over {
|
||||
if over.Param == "0.5" {
|
||||
price = over.Price
|
||||
}
|
||||
}
|
||||
|
||||
tipPrice = price
|
||||
|
||||
tip = model.Tip{
|
||||
SportID: SportFootball,
|
||||
StrategyID: int64(strategyID),
|
||||
MatchID: match.MatchID,
|
||||
TipTime: time.Now().Unix(),
|
||||
ChampName: match.Champ.Name,
|
||||
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
||||
Market: MarketTotal1stHalf,
|
||||
Side: SideOver,
|
||||
Param: "0.5",
|
||||
Price: price,
|
||||
CurrentMatchTime: upd.CurrentTime,
|
||||
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
||||
}
|
||||
|
||||
case 2:
|
||||
var price float64
|
||||
for _, over := range upd.Total.Over {
|
||||
if over.Param == "0.5" {
|
||||
price = over.Price
|
||||
}
|
||||
}
|
||||
|
||||
tipPrice = price
|
||||
|
||||
tip = model.Tip{
|
||||
SportID: SportFootball,
|
||||
StrategyID: int64(strategyID),
|
||||
MatchID: match.MatchID,
|
||||
TipTime: time.Now().Unix(),
|
||||
ChampName: match.Champ.Name,
|
||||
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
||||
Market: MarketTotal,
|
||||
Side: SideOver,
|
||||
Param: "0.5",
|
||||
Price: price,
|
||||
CurrentMatchTime: upd.CurrentTime,
|
||||
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
||||
}
|
||||
|
||||
case 3:
|
||||
var price float64
|
||||
for _, over := range upd.Total.Over {
|
||||
if over.Param == "1.5" {
|
||||
price = over.Price
|
||||
}
|
||||
}
|
||||
|
||||
tipPrice = price
|
||||
|
||||
tip = model.Tip{
|
||||
SportID: SportFootball,
|
||||
StrategyID: int64(strategyID),
|
||||
MatchID: match.MatchID,
|
||||
TipTime: time.Now().Unix(),
|
||||
ChampName: match.Champ.Name,
|
||||
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
||||
Market: MarketTotal,
|
||||
Side: SideOver,
|
||||
Param: "1.5",
|
||||
Price: price,
|
||||
CurrentMatchTime: upd.CurrentTime,
|
||||
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
||||
}
|
||||
|
||||
case 100:
|
||||
var price float64
|
||||
for _, over := range upd.H1.Total.Over {
|
||||
if over.Param == "0.5" {
|
||||
price = over.Price
|
||||
}
|
||||
}
|
||||
|
||||
tipPrice = price
|
||||
|
||||
tip = model.Tip{
|
||||
SportID: SportFootball,
|
||||
StrategyID: int64(strategyID),
|
||||
MatchID: match.MatchID,
|
||||
TipTime: time.Now().Unix(),
|
||||
ChampName: match.Champ.Name,
|
||||
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
||||
Market: MarketTotal1stHalf,
|
||||
Side: SideOver,
|
||||
Param: "0.5",
|
||||
Price: price,
|
||||
CurrentMatchTime: upd.CurrentTime,
|
||||
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
||||
}
|
||||
}
|
||||
*/
|
||||
250
daemon/over05.go
Executable file
250
daemon/over05.go
Executable file
@@ -0,0 +1,250 @@
|
||||
package daemon
|
||||
|
||||
/*
|
||||
Футбольная стратегия.
|
||||
ТБ 0.5 в 1м тайме
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/onexbet"
|
||||
"gordenko.dev/dima/tipper/model"
|
||||
)
|
||||
|
||||
/*
|
||||
Алгоритм 2
|
||||
ТБ 0.5 в Матче
|
||||
|
||||
До матча:
|
||||
ТБ 2.5 (в матче) <=1.7
|
||||
В 75% игр был гол
|
||||
|
||||
В Лайве:
|
||||
Сумма атак обычных и опасных >= 135
|
||||
6 ударов в сторону ворот OFF TARGET
|
||||
4 удара в створ ON TARGET
|
||||
|
||||
До 70 минуты
|
||||
*/
|
||||
|
||||
type Over05 struct {
|
||||
id int64
|
||||
notifyInTelegram bool
|
||||
isChampAccepted func(string) bool
|
||||
}
|
||||
|
||||
func NewOver05(opt StrategyOptions) Over05 {
|
||||
if opt.ID == 0 {
|
||||
panic("StrategyID not defined")
|
||||
}
|
||||
s := Over05{}
|
||||
s.id = opt.ID
|
||||
s.notifyInTelegram = opt.NotifyInTelegram
|
||||
s.isChampAccepted = opt.IsChampAccepted
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Over05) ID() int64 {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s Over05) ShortName() string {
|
||||
return fmt.Sprintf("#%d FT Over 0.5", s.id)
|
||||
}
|
||||
|
||||
func (s Over05) IsNotifyInTelegram() bool {
|
||||
return s.notifyInTelegram
|
||||
}
|
||||
|
||||
func (s Over05) GetTelegramTipPattern() string {
|
||||
return `Сигнал # %d.
|
||||
Будет ГОЛ!
|
||||
Алгоритм %d
|
||||
Тотал Больше 0.5
|
||||
Футбол. %s. %s
|
||||
%s - %s
|
||||
Коэф. %.3f`
|
||||
}
|
||||
|
||||
type isInterestingNotesOver05 struct {
|
||||
PriceOver25 float64 `json:"Over 2.5 price"`
|
||||
MatchesAnalysed int `json:"matchesAnalysed"`
|
||||
HasGoalInMatches int `json:"hasGoalInMatches"`
|
||||
GoalsPercent float64 `json:"goalsPercent"`
|
||||
}
|
||||
|
||||
func (s Over05) 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
|
||||
)
|
||||
|
||||
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 := isInterestingNotesOver05{
|
||||
PriceOver25: priceOver25,
|
||||
MatchesAnalysed: matchCount,
|
||||
HasGoalInMatches: hasGoalsInMatchCount,
|
||||
GoalsPercent: goalsPercent,
|
||||
}
|
||||
|
||||
//buf, _ := json.MarshalIndent(obj, "", " ")
|
||||
|
||||
return obj, true
|
||||
}
|
||||
|
||||
func (s Over05) 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 Over05) 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
|
||||
}
|
||||
275
daemon/over15.go
Executable file
275
daemon/over15.go
Executable file
@@ -0,0 +1,275 @@
|
||||
package daemon
|
||||
|
||||
/*
|
||||
Футбольная стратегия.
|
||||
ТБ 0.5 в 1м тайме
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/onexbet"
|
||||
"gordenko.dev/dima/tipper/model"
|
||||
)
|
||||
|
||||
/*
|
||||
Алгоритм 3
|
||||
ТБ 1.5 в Матче
|
||||
|
||||
До матча:
|
||||
ТБ 2.5 (в матче) <=1.7
|
||||
В 75% игр был гол
|
||||
КФ фаворита <=1.4
|
||||
|
||||
В Лайве:
|
||||
Сумма атак обычных и опасных >= 125
|
||||
6 ударов в сторону ворот
|
||||
4 удара в створ
|
||||
|
||||
До 70 минуты
|
||||
*/
|
||||
|
||||
type Over15 struct {
|
||||
id int64
|
||||
notifyInTelegram bool
|
||||
isChampAccepted func(string) bool
|
||||
}
|
||||
|
||||
func NewOver15(opt StrategyOptions) Over15 {
|
||||
if opt.ID == 0 {
|
||||
panic("StrategyID not defined")
|
||||
}
|
||||
s := Over15{}
|
||||
s.id = opt.ID
|
||||
s.notifyInTelegram = opt.NotifyInTelegram
|
||||
s.isChampAccepted = opt.IsChampAccepted
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Over15) ID() int64 {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s Over15) ShortName() string {
|
||||
return fmt.Sprintf("#%d FT Over 1.5", s.id)
|
||||
}
|
||||
|
||||
func (s Over15) IsNotifyInTelegram() bool {
|
||||
return s.notifyInTelegram
|
||||
}
|
||||
|
||||
func (s Over15) GetTelegramTipPattern() string {
|
||||
return `Сигнал # %d.
|
||||
Будет 2й ГОЛ!
|
||||
Алгоритм %d
|
||||
Тотал Больше 1.5
|
||||
Футбол. %s. %s
|
||||
%s - %s
|
||||
Коэф. %.3f`
|
||||
}
|
||||
|
||||
type isInterestingNotesOver15 struct {
|
||||
PriceOver25 float64 `json:"Over 2.5 price"`
|
||||
MatchesAnalysed int `json:"matchesAnalysed"`
|
||||
HasGoalInMatches int `json:"hasGoalInMatches"`
|
||||
GoalsPercent float64 `json:"goalsPercent"`
|
||||
FavoritePrice float64 `json:"favoritePrice"`
|
||||
}
|
||||
|
||||
func (s Over15) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
|
||||
var favoritePrice float64 = 2
|
||||
|
||||
for _, offer := range report.Odds.Winner {
|
||||
if offer.Home < favoritePrice && offer.Home > 1.01 {
|
||||
favoritePrice = offer.Home
|
||||
}
|
||||
|
||||
if offer.Away < favoritePrice && offer.Away > 1.01 {
|
||||
favoritePrice = offer.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 := isInterestingNotesOver15{
|
||||
PriceOver25: priceOver25,
|
||||
MatchesAnalysed: matchCount,
|
||||
HasGoalInMatches: hasGoalsInMatchCount,
|
||||
GoalsPercent: goalsPercent,
|
||||
FavoritePrice: favoritePrice,
|
||||
}
|
||||
|
||||
//buf, _ := json.MarshalIndent(obj, "", " ")
|
||||
|
||||
return obj, true
|
||||
}
|
||||
|
||||
func (s Over15) 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
|
||||
}
|
||||
|
||||
// Ищем тотал Больше 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 {
|
||||
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), 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 Over15) 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
|
||||
}
|
||||
249
daemon/p1over05.go
Executable file
249
daemon/p1over05.go
Executable file
@@ -0,0 +1,249 @@
|
||||
package daemon
|
||||
|
||||
/*
|
||||
Футбольная стратегия.
|
||||
ТБ 0.5 в 1м тайме
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/onexbet"
|
||||
"gordenko.dev/dima/tipper/model"
|
||||
)
|
||||
|
||||
type P1Over05 struct {
|
||||
id int64
|
||||
notifyInTelegram bool
|
||||
isChampAccepted func(string) bool
|
||||
}
|
||||
|
||||
func NewP1Over05(opt StrategyOptions) P1Over05 {
|
||||
if opt.ID == 0 {
|
||||
panic("StrategyID not defined")
|
||||
}
|
||||
s := P1Over05{}
|
||||
s.id = opt.ID
|
||||
s.notifyInTelegram = opt.NotifyInTelegram
|
||||
s.isChampAccepted = opt.IsChampAccepted
|
||||
return s
|
||||
}
|
||||
|
||||
func (s P1Over05) ID() int64 {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s P1Over05) ShortName() string {
|
||||
return fmt.Sprintf("#%d H1 Over 0.5", s.id)
|
||||
}
|
||||
|
||||
func (s P1Over05) IsNotifyInTelegram() bool {
|
||||
return s.notifyInTelegram
|
||||
}
|
||||
|
||||
func (s P1Over05) GetTelegramTipPattern() string {
|
||||
return `Сигнал # %d.
|
||||
Будет ГОЛ!
|
||||
Алгоритм %d
|
||||
Первый тайм, Тотал Больше 0.5
|
||||
Футбол. %s. %s
|
||||
%s - %s
|
||||
Коэф. %.3f`
|
||||
}
|
||||
|
||||
type isInterestingNotesP1Over05 struct {
|
||||
PriceOver25 float64 `json:"Over 2.5 price"`
|
||||
MatchesAnalysed int `json:"matchesAnalysed"`
|
||||
HasGoalInP1Matches int `json:"hasGoalInP1Matches"`
|
||||
P1GoalsPercent float64 `json:"p1GoalsPercent"`
|
||||
}
|
||||
|
||||
func (s P1Over05) 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
|
||||
)
|
||||
|
||||
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 {
|
||||
hasStatsMatchCount++
|
||||
|
||||
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
|
||||
hasGoalsInP1MatchCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasStatsMatchCount == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// hasStatsMatchCount - 100%
|
||||
// hasGoalsInP1MatchCount - x%
|
||||
|
||||
p1GoalsPercent := float64(hasGoalsInP1MatchCount*100) / float64(hasStatsMatchCount)
|
||||
|
||||
if p1GoalsPercent < 75 {
|
||||
return
|
||||
}
|
||||
|
||||
obj := isInterestingNotesP1Over05{
|
||||
PriceOver25: priceOver25,
|
||||
MatchesAnalysed: hasStatsMatchCount,
|
||||
HasGoalInP1Matches: hasGoalsInP1MatchCount,
|
||||
P1GoalsPercent: p1GoalsPercent,
|
||||
}
|
||||
|
||||
//buf, _ := json.MarshalIndent(obj, "", " ")
|
||||
|
||||
return obj, true
|
||||
}
|
||||
|
||||
func (s P1Over05) 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 P1Over05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
|
||||
//buf, _ := json.MarshalIndent(stats, "", " ")
|
||||
//fmt.Printf("%s\n", buf)
|
||||
|
||||
if stats.Status != onexbet.StatusMatchCompleted {
|
||||
return
|
||||
}
|
||||
|
||||
h1, ok := stats.ScoreByPeriods[1]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
//if stats.H1 == nil || stats.H2 == nil {
|
||||
//return
|
||||
//}
|
||||
|
||||
res := new(TipResult)
|
||||
h1Goals := h1.HomePoints + h1.AwayPoints
|
||||
if h1Goals > 0 {
|
||||
res.Status = model.Won
|
||||
} else {
|
||||
res.Status = model.Lost
|
||||
}
|
||||
res.Result = fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints)
|
||||
return res
|
||||
}
|
||||
165
daemon/pages.go
Normal file
165
daemon/pages.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/timeutil"
|
||||
"gordenko.dev/dima/tipper/model"
|
||||
"gordenko.dev/dima/web"
|
||||
)
|
||||
|
||||
func (s *Daemon) PageMatches(state *web.State, reply *web.Reply) (err error) {
|
||||
buf, _ := json.MarshalIndent(s.teamMatches, "", " ")
|
||||
//reply.Render("matches", buf)
|
||||
reply.Write(buf)
|
||||
return
|
||||
}
|
||||
|
||||
type TipView struct {
|
||||
Time string
|
||||
Name string
|
||||
Champ string
|
||||
Link string
|
||||
Result string
|
||||
Status model.TipStatus
|
||||
Strategy string
|
||||
}
|
||||
|
||||
type DayTips struct {
|
||||
Date string
|
||||
Tips []TipView
|
||||
}
|
||||
|
||||
type PageTipsData struct {
|
||||
Days []DayTips
|
||||
}
|
||||
|
||||
func (s *Daemon) PageHandballByDays(state *web.State, reply *web.Reply) (err error) {
|
||||
data, err := s.getPageTipsData(flashscore.Handball)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply.Render("temp_tips", data)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Daemon) PageFootballByDays(state *web.State, reply *web.Reply) (err error) {
|
||||
data, err := s.getPageTipsData(flashscore.Football)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply.Render("temp_tips", data)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Daemon) PageTennisByDays(state *web.State, reply *web.Reply) (err error) {
|
||||
data, err := s.getPageTipsData(flashscore.Tennis)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply.Render("temp_tips", data)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Daemon) getPageTipsData(sportID int) (_ PageTipsData, err error) {
|
||||
tips, err := s.ListSportTips(sportID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var days []DayTips
|
||||
|
||||
var currentDay int64
|
||||
var dayTips DayTips
|
||||
|
||||
for _, tip := range tips {
|
||||
if tip.Status != model.Won && tip.Status != model.Lost {
|
||||
continue
|
||||
}
|
||||
|
||||
tm := time.Unix(tip.TipTime, 0)
|
||||
|
||||
tm = timeutil.FirstSecondInPeriod(tm, "d")
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
|
||||
day := tm.Unix()
|
||||
|
||||
if day != currentDay {
|
||||
if currentDay != 0 {
|
||||
days = append(days, dayTips)
|
||||
}
|
||||
|
||||
dayTips = DayTips{
|
||||
Date: tm.Format("Monday Jan 2, 2006"),
|
||||
}
|
||||
|
||||
currentDay = day
|
||||
}
|
||||
|
||||
dayTips.Tips = append(dayTips.Tips, TipView{
|
||||
Time: tm.Format("15:04"),
|
||||
Name: tip.MatchName,
|
||||
Champ: tip.ChampName,
|
||||
Link: fmt.Sprintf("https://www.flashscore.com/match/%s/#h2h;overall", tip.MatchID),
|
||||
Result: tip.Result,
|
||||
Status: tip.Status,
|
||||
Strategy: tip.Strategy,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if currentDay != 0 {
|
||||
days = append(days, dayTips)
|
||||
}
|
||||
|
||||
return PageTipsData{
|
||||
Days: days,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Tipper) pageLinked(state *web.State, reply *web.Reply) (err error) {
|
||||
//reply.Render("aliases", nil)
|
||||
var list []*interestingFootballMatch
|
||||
|
||||
for _, x := range s.onexbetMatchToInterestingFootballMatch {
|
||||
list = append(list, x)
|
||||
}
|
||||
|
||||
buf, _ := json.MarshalIndent(list, "", " ")
|
||||
|
||||
reply.WriteString(string(buf))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (s *Tipper) pageWatch(state *web.State, reply *web.Reply) (err error) {
|
||||
//reply.Render("aliases", nil)
|
||||
var matchID string
|
||||
state.Val("id", &matchID)
|
||||
|
||||
s.onexbetAPIClient.Call("watch", matchID)
|
||||
|
||||
reply.WriteString(matchID)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Tipper) pageUnwatch(state *web.State, reply *web.Reply) (err error) {
|
||||
//reply.Render("aliases", nil)
|
||||
var matchID string
|
||||
state.Val("id", &matchID)
|
||||
|
||||
s.onexbetAPIClient.Call("unwatch", matchID)
|
||||
|
||||
reply.WriteString(matchID)
|
||||
return
|
||||
}
|
||||
*/
|
||||
55
daemon/telegram.go
Executable file
55
daemon/telegram.go
Executable file
@@ -0,0 +1,55 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"gordenko.dev/dima/httpreq"
|
||||
)
|
||||
|
||||
// @getidsbot - показывает ID чата
|
||||
|
||||
const (
|
||||
// tipper, sport_oracle_bot
|
||||
//telegramBotToken = "1059580991:AAFHO4RrQCIF-JWYQI2em1D5aLJ4KbjDPZU"
|
||||
telegramBotToken = "5637682609:AAH2MJG-r0k9EIUQMagdZOXFUjY4nid514I"
|
||||
//telegramChannel = "@Bet.One.Group"
|
||||
tennisTelegramChatID = "-1001189544230"
|
||||
footballTelegramChatID = "-1001480163278"
|
||||
|
||||
handballTelegramChatID = "-1001372665077"
|
||||
)
|
||||
|
||||
var (
|
||||
telegramSendMessageURL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", telegramBotToken)
|
||||
)
|
||||
|
||||
//chat_id=[MY_CHANNEL_NAME]&text=[MY_MESSAGE_TEXT]
|
||||
|
||||
func sendMessageToTelegramGroup(telegramChatID string, msg string) (err error) {
|
||||
data := make(url.Values)
|
||||
data.Set("chat_id", telegramChatID)
|
||||
data.Set("text", msg)
|
||||
data.Set("parse_mode", "HTML")
|
||||
|
||||
u := telegramSendMessageURL + "?" + data.Encode()
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := httpreq.Send(req, 10*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
err = fmt.Errorf("StatusCode: %d, Body: %s", resp.StatusCode, resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
1125
daemon/tennis.go
Normal file
1125
daemon/tennis.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user