Files
tipper/daemon/handball.go

1166 lines
29 KiB
Go
Raw Permalink Normal View History

2026-07-21 05:29:37 +03:00
package daemon
import (
"fmt"
"math"
"strconv"
"time"
"gordenko.dev/dima/flashscore"
fsmodel "gordenko.dev/dima/flashscore/model"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
var (
p1TimeFrame = timeFrame{
Since: 5 * 60,
Until: 22 * 60,
}
ftTimeFrame = timeFrame{
Since: 10 * 60,
Until: 52 * 60,
}
ftOverTimeFrame = timeFrame{
Since: 10 * 60,
Until: 52 * 60,
}
)
type timeFrame struct {
Since int
Until int
}
type HandballStrategyOptions struct {
ID int64
NotifyInTelegram bool
TotalDiff float64
FixateGamePaceEveryNMinutes int
Disable bool
}
// FIX - calculator, который будет проверять флаг isMatchScoreLoaded
func HandballHistoryProcessor(match *TeamMatch) {
// Делаем reset
match.HomeGoalsPerMinute = 0
match.AwayGoalsPerMinute = 0
match.H2HGoalsPerMinute = 0
match.BasicTotal = 0
/*
match.BasicOverGoalsPerMinute = 0
match.BasicUnderGoalsPerMinute = 0
match.TipTotalOver = 0
match.TipTotalUnder = 0
*/
// Считаем
if len(match.HomeTeamMatches) > 0 {
match.HomeGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.HomeTeamMatches, 5)
}
if len(match.AwayTeamMatches) > 0 {
match.AwayGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.AwayTeamMatches, 5)
}
if len(match.H2H) > 0 {
match.H2HGoalsPerMinute = caclulateGoalsPerMinuteOfLastNMatches(match.H2H, 5)
}
// Basic Totals
match.BasicTotal = findBasicTotal(match.Odds.FullTimeTotal)
match.H1BasicTotal = findBasicTotal(match.Odds.H1Total)
}
func findBasicTotal(totals []flashscore.Total) (_ float64) {
var (
minDif float64
bestOffer flashscore.Total
isBestOfferFound bool
)
// Находим средний тотал - это когда разница между коэффициентами минимальна
for _, total := range totals {
if len(total.Offers) > 0 {
var dif float64
for _, offer := range total.Offers {
dif += math.Abs(offer.Over - offer.Under)
}
// Вычисляем для каждого тотала среднюю (среди всех букмекеров) разницу
// между Over и Under коэф.
dif = dif / float64(len(total.Offers))
if (!isBestOfferFound) || (dif < minDif) {
minDif = dif
bestOffer = total
isBestOfferFound = true
}
}
}
if isBestOfferFound {
basicTotal, err := strconv.ParseFloat(bestOffer.Total, 64)
if err != nil {
return
}
return basicTotal
}
return
}
const (
Gte = 1 // >=
Lte = 2 // <=
)
func checkGamePace(basicGoalsPerMinute float64, match *TeamMatch, upd onexbet.TeamLiveMatchData, fixateGamePaceEveryNMinutes int, moreOrLess int) (comment string, isPassed bool) {
currentGoalsPerMinute := (float64(upd.HomeGoals+upd.AwayGoals) / float64(upd.CurrentTime)) * 60
gamePaceFrame := upd.CurrentTime / (fixateGamePaceEveryNMinutes * 60)
if gamePaceFrame == 0 {
match.GamePace = currentGoalsPerMinute
} else {
if gamePaceFrame != match.GamePaceFrame {
match.GamePaceFrame = gamePaceFrame
match.GamePace = currentGoalsPerMinute
}
}
var timeStr string
if match.GamePaceFrame == 0 {
timeStr = fmt.Sprintf("%s", time.Duration(upd.CurrentTime)*time.Second)
} else {
timeStr = fmt.Sprintf("%d мин", match.GamePaceFrame*fixateGamePaceEveryNMinutes)
}
switch moreOrLess {
case Gte:
// Темп должен быть >=
if match.GamePace < basicGoalsPerMinute {
comment = fmt.Sprintf("Текущий темп %.2f (за %s) < базового темпа %.2f",
match.GamePace, timeStr, basicGoalsPerMinute)
return comment, false
}
/*
if len(match.H2H) > 0 {
// H2H матчи необязательныеs
if match.GamePace < match.H2HGoalsPerMinute {
comment = fmt.Sprintf("Текущий темп %.2f (за %s) < H2H темпа %.2f",
match.GamePace, timeStr, match.H2HGoalsPerMinute)
return comment, false
}
}
*/
case Lte:
// Темп должен быть <=
if match.GamePace > basicGoalsPerMinute {
comment = fmt.Sprintf("Текущий темп %.2f (за %s) > базового темпа %.2f",
match.GamePace, timeStr, basicGoalsPerMinute)
return comment, false
}
/*
if len(match.H2H) > 0 {
// H2H матчи необязательныеs
if match.GamePace > match.H2HGoalsPerMinute {
comment = fmt.Sprintf("Текущий темп %.2f (за %s) > H2H темпа %.2f",
match.GamePace, timeStr, match.H2HGoalsPerMinute)
return comment, false
}
}
*/
default:
panic(fmt.Sprintf("Bug: unknown moreOrLess: %d", moreOrLess))
}
return "", true
}
func correctTotal(total float64, roundDir int) float64 {
_, frac := math.Modf(total)
eps := 0.001
if math.Abs(frac-0.25) < eps {
// Если тотал завершается на 0.25 - округляем в ту или иную сторону
switch roundDir {
case roundUp:
total = math.Floor(total) + 0.5
case roundDown:
total = math.Floor(total)
}
} else if frac < eps {
// Круглый тотал: 24, 60
switch roundDir {
case roundUp:
total = total + 0.5
case roundDown:
total = total - 0.5
}
}
return total
}
type HandballFTOver struct {
id int64
notifyInTelegram bool
totalDiff float64
fixateGamePaceEveryNMinutes int
isDisabled bool
}
func NewHandballFTOver(opt HandballStrategyOptions) HandballFTOver {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := HandballFTOver{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.totalDiff = opt.TotalDiff
s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes
s.isDisabled = opt.Disable
return s
}
func (s HandballFTOver) ID() int64 {
return s.id
}
func (s HandballFTOver) ShortName() string {
return fmt.Sprintf("#%d FT Over (-%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes)
}
func (s HandballFTOver) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s HandballFTOver) GetTelegramTipPattern() string {
return `Сигнал # %d.
Алгоритм %d. Тотал Больше в матче.
%s. %s
%s - %s
Точка входа %.1f кф. %.3f
Рекомендация: тотал больше %.1f и ниже (%.1f; %.1f)`
}
func (s HandballFTOver) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
func caclulateGoalsPerMinuteOfLastNMatches(matches []fsmodel.TeamMatch, n int) (goalsPerMinute float64) {
if len(matches) < n {
n = len(matches)
}
count := 0
for _, m := range matches[:n] {
if m.IsMatchScoreLoaded {
goalsPerMinute += float64(m.HomeGoals+m.AwayGoals) / 60
count++
}
}
if count > 0 {
return goalsPerMinute / float64(count)
}
return
}
func (s HandballFTOver) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) {
if s.isDisabled {
return
}
eps := 0.01
// Проверки на то, что полные данные
if match.BasicTotal < 1 {
return nil, false
}
tipTotal := match.BasicTotal - s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundDown)
basicGoalsPerMinute := tipTotal / 60
if math.Abs(match.H2HGoalsPerMinute) > eps {
// Если команды между собой забивают меньше чем дает бук (с учетом погрешности)
// - матч не интересен
if match.H2HGoalsPerMinute < basicGoalsPerMinute {
return
}
} else {
if math.Abs(match.HomeGoalsPerMinute) < eps {
return
}
if math.Abs(match.AwayGoalsPerMinute) < eps {
return
}
// Считаем средний темп с которым играют обе команды
avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2
// Если в среднем команды забивают меньше чем дает бук (с учетом погрешности)
// - матч не интересен
if avgGoalsPerMinute < basicGoalsPerMinute {
return
}
}
return nil, true
}
func (s HandballFTOver) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 1800 {
return "в перерыве не ставим (багфикс)", nil, Waiting
}
if upd.CurrentTime < ftOverTimeFrame.Since {
return "еще рано", nil, Waiting
}
if upd.CurrentTime > ftOverTimeFrame.Until {
return "время вышло", nil, Unwatch
}
tipTotal := match.BasicTotal - s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundDown)
basicGoalsPerMinute := tipTotal / 60
tipTotalStr := fmt.Sprintf("%.1f", tipTotal)
if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal {
return "Тотал пробит", nil, Unwatch
}
// Текущий темп матча опережает рассчитанный на истории
// проверяем коэффициенты.
var isPassed bool
comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Gte)
if !isPassed {
return comment, nil, Waiting
}
// Задача - найти минимальный тотал <= tipTotal, чтобы коэфф. >= 1.5
tipTotal = tipTotal + 0.01 // Для удобного сравнивания float
//fmt.Printf("tipTotal: %.2f\n", tipTotal)
var (
minTotalValue float64
bestOffer onexbet.ParamOffer // строковое представление тотала
)
for _, offer := range upd.Total.Over {
if offer.Price < 1.5 {
continue
}
totalValue, err := strconv.ParseFloat(offer.Param, 64)
if err != nil {
continue
}
if totalValue > tipTotal {
//fmt.Printf("totalValue > tipTotal: %.2f > %.2f\n", totalValue, tipTotal)
continue
}
//fmt.Printf("Best: totalValue: %2.f (%s), price: %.2f\n", totalValue, offer.Param, offer.Price)
if bestOffer.Param == "" {
// инициализация
minTotalValue = totalValue
bestOffer = offer
} else {
if totalValue < minTotalValue {
minTotalValue = totalValue
bestOffer = offer
}
}
}
if bestOffer.Param == "" {
comment = fmt.Sprintf("Тотал %s и меньше с коэф. >= 1.5 не найден",
tipTotalStr)
return comment, nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: bestOffer.Param,
Price: bestOffer.Price,
}, Bet
}
func (s HandballFTOver) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
tipTotal, err := strconv.ParseFloat(tip.Param, 64)
if err != nil {
return
}
res := &TipResult{
Result: fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints),
}
goals := float64(stats.HomePoints + stats.AwayPoints)
// Сперва проверяем что тоталы не равны
dif := goals - tipTotal
eps := 0.0001
if math.Abs(dif) < eps {
// Равны - расход
res.Status = model.Void
} else {
if goals > tipTotal {
res.Status = model.Won
} else {
res.Status = model.Lost
}
}
return res
}
type HandballFTUnder struct {
id int64
notifyInTelegram bool
totalDiff float64
fixateGamePaceEveryNMinutes int
isDisabled bool
}
func NewHandballFTUnder(opt HandballStrategyOptions) HandballFTUnder {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := HandballFTUnder{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.totalDiff = opt.TotalDiff
s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes
s.isDisabled = opt.Disable
return s
}
func (s HandballFTUnder) ID() int64 {
return s.id
}
func (s HandballFTUnder) ShortName() string {
return fmt.Sprintf("#%d FT Under (+%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes)
}
func (s HandballFTUnder) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s HandballFTUnder) GetTelegramTipPattern() string {
return `Сигнал # %d.
Алгоритм %d. Тотал Меньше в матче.
%s. %s
%s - %s
Точка входа %.1f кф. %.3f
Рекомендация: тотал меньше %.1f и выше (%.1f; %.1f)`
}
func (s HandballFTUnder) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
func (s HandballFTUnder) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) {
if s.isDisabled {
return
}
eps := 0.01
// Проверки на то, что полные данные
if match.BasicTotal < 1 {
return nil, false
}
tipTotal := match.BasicTotal + s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundUp)
basicGoalsPerMinute := tipTotal / 60
if math.Abs(match.H2HGoalsPerMinute) > eps {
// Если команды между собой забивают больше чем дает бук (с учетом погрешности)
// - матч не интересен (ибо опасно)
if match.H2HGoalsPerMinute > basicGoalsPerMinute {
return
}
} else {
if math.Abs(match.HomeGoalsPerMinute) < eps {
return
}
if math.Abs(match.AwayGoalsPerMinute) < eps {
return
}
// Считаем средний темп с которым играют обе команды
avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2
// Если в среднем команды забивают больше чем дает бук (с учетом погрешности)
// - матч не интересен (ибо опасно)
if avgGoalsPerMinute > basicGoalsPerMinute {
return
}
}
return nil, true
}
func (s HandballFTUnder) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 1800 {
return "в перерыве не ставим (багфикс)", nil, Waiting
}
if upd.CurrentTime < ftTimeFrame.Since {
return "еще рано", nil, Waiting
}
if upd.CurrentTime > ftTimeFrame.Until {
return "время вышло", nil, Unwatch
}
tipTotal := match.BasicTotal + s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundUp)
basicGoalsPerMinute := tipTotal / 60
tipTotalStr := fmt.Sprintf("%.1f", tipTotal)
if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal {
return "Тотал пробит", nil, Unwatch
}
var isPassed bool
comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Lte)
if !isPassed {
return comment, nil, Waiting
}
// Текущий темп матча опережает рассчитанный на истории
// проверяем коэффициенты.
// Задача - найти максимальный тотал >= tipTotal, чтобы коэфф. >= 1.5
tipTotal = tipTotal - 0.01 // Для удобного сравнивания float
var (
maxTotalValue float64
bestOffer onexbet.ParamOffer // строковое представление тотала
)
for _, offer := range upd.Total.Under {
if offer.Price < 1.5 {
continue
}
totalValue, err := strconv.ParseFloat(offer.Param, 64)
if err != nil {
continue
}
if totalValue < tipTotal {
continue
}
if bestOffer.Param == "" {
// инициализация
maxTotalValue = totalValue
bestOffer = offer
} else {
if totalValue > maxTotalValue {
maxTotalValue = totalValue
bestOffer = offer
}
}
}
if bestOffer.Param == "" {
comment = fmt.Sprintf("Тотал %s и больше с коэф. >= 1.5 не найден",
tipTotalStr)
return comment, nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideUnder,
Param: bestOffer.Param,
Price: bestOffer.Price,
}, Bet
}
func (s HandballFTUnder) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
tipTotal, err := strconv.ParseFloat(tip.Param, 64)
if err != nil {
return
}
res := &TipResult{
Result: fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints),
}
goals := float64(stats.HomePoints + stats.AwayPoints)
// Сперва проверяем что тоталы не равны
dif := goals - tipTotal
eps := 0.0001
if math.Abs(dif) < eps {
// Равны - расход
res.Status = model.Void
} else {
if goals < tipTotal {
res.Status = model.Won
} else {
res.Status = model.Lost
}
}
return res
}
// P1
type HandballP1Over struct {
id int64
notifyInTelegram bool
totalDiff float64
fixateGamePaceEveryNMinutes int
isDisabled bool
}
func NewHandballP1Over(opt HandballStrategyOptions) HandballP1Over {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := HandballP1Over{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.totalDiff = opt.TotalDiff
s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes
s.isDisabled = opt.Disable
return s
}
func (s HandballP1Over) ID() int64 {
return s.id
}
func (s HandballP1Over) ShortName() string {
return fmt.Sprintf("#%d P1 Over (-%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes)
}
func (s HandballP1Over) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s HandballP1Over) GetTelegramTipPattern() string {
return `Сигнал # %d.
Алгоритм %d. Тотал Больше в 1м тайме.
%s. %s
%s - %s
Точка входа %.1f кф. %.3f
Рекомендация: тотал больше %.1f и ниже (%.1f; %.1f)`
}
func (s HandballP1Over) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
if liveMatch.CurrentPeriod == 1 {
return true
}
return false
}
func (s HandballP1Over) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) {
if s.isDisabled {
return
}
eps := 0.01
// Проверки на то, что полные данные
if match.BasicTotal < 1 {
return nil, false
}
var tipTotal float64
/*
if match.H1BasicTotal < 1 {
tipTotal = (match.BasicTotal / 2) - s.totalDiff
} else {
tipTotal = match.H1BasicTotal - s.totalDiff
}
*/
if match.H1BasicTotal < 1 {
return
}
tipTotal = match.H1BasicTotal - s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundDown)
basicGoalsPerMinute := tipTotal / 30
if math.Abs(match.H2HGoalsPerMinute) > eps {
// Если команды между собой забивают меньше чем дает бук (с учетом погрешности)
// - матч не интересен
if match.H2HGoalsPerMinute < basicGoalsPerMinute {
return
}
} else {
if math.Abs(match.HomeGoalsPerMinute) < eps {
return
}
if math.Abs(match.AwayGoalsPerMinute) < eps {
return
}
// Считаем средний темп с которым играют обе команды
avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2
// Если в среднем команды забивают меньше чем дает бук (с учетом погрешности)
// - матч не интересен
if avgGoalsPerMinute < basicGoalsPerMinute {
return
}
}
return nil, true
}
func (s HandballP1Over) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 1800 {
return "в перерыве не ставим (багфикс)", nil, Waiting
}
if upd.CurrentTime < p1TimeFrame.Since {
return "еще рано", nil, Waiting
}
if upd.CurrentTime > p1TimeFrame.Until {
return "время вышло", nil, Unwatch
}
var tipTotal float64
if match.H1BasicTotal < 1 {
tipTotal = (match.BasicTotal / 2) - s.totalDiff
} else {
tipTotal = match.H1BasicTotal - s.totalDiff
}
// ВАЖНО!
// После деления тотал может быть 25.25, либо просто круглый 24, 60
tipTotal = correctTotal(tipTotal, roundDown)
basicGoalsPerMinute := tipTotal / 30
tipTotalStr := fmt.Sprintf("%.1f", tipTotal)
if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal {
return "Тотал пробит", nil, Unwatch
}
var isPassed bool
comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Gte)
if !isPassed {
return comment, nil, Waiting
}
// Текущий темп матча опережает рассчитанный на истории
// проверяем коэффициенты.
// Задача - найти минимальный тотал <= tipTotal, чтобы коэфф. >= 1.5
tipTotal = tipTotal + 0.01 // Для удобного сравнивания float
//fmt.Printf("tipTotal: %.2f\n", tipTotal)
var (
minTotalValue float64
bestOffer onexbet.ParamOffer // строковое представление тотала
)
for _, offer := range upd.H1.Total.Over {
if offer.Price < 1.5 {
continue
}
totalValue, err := strconv.ParseFloat(offer.Param, 64)
if err != nil {
continue
}
if totalValue > tipTotal {
//fmt.Printf("totalValue > tipTotal: %.2f > %.2f\n", totalValue, tipTotal)
continue
}
//fmt.Printf("Best: totalValue: %2.f (%s), price: %.2f\n", totalValue, offer.Param, offer.Price)
if bestOffer.Param == "" {
// инициализация
minTotalValue = totalValue
bestOffer = offer
} else {
if totalValue < minTotalValue {
minTotalValue = totalValue
bestOffer = offer
}
}
}
if bestOffer.Param == "" {
comment = fmt.Sprintf("Тотал %s и меньше с коэф. >= 1.5 не найден",
tipTotalStr)
return comment, nil, Waiting
}
return "", &BetDetails{
Market: MarketTotalH1,
Side: SideOver,
Param: bestOffer.Param,
Price: bestOffer.Price,
}, Bet
}
func (s HandballP1Over) 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
}
tipTotal, err := strconv.ParseFloat(tip.Param, 64)
if err != nil {
return
}
res := &TipResult{
Result: fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints),
}
goals := float64(h1.HomePoints + h1.AwayPoints)
// Сперва проверяем что тоталы не равны
dif := goals - tipTotal
eps := 0.0001
if math.Abs(dif) < eps {
// Равны - расход
res.Status = model.Void
} else {
if goals > tipTotal {
res.Status = model.Won
} else {
res.Status = model.Lost
}
}
return res
}
type HandballP1Under struct {
id int64
notifyInTelegram bool
totalDiff float64
fixateGamePaceEveryNMinutes int
isDisabled bool
}
func NewHandballP1Under(opt HandballStrategyOptions) HandballP1Under {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := HandballP1Under{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.totalDiff = opt.TotalDiff
s.fixateGamePaceEveryNMinutes = opt.FixateGamePaceEveryNMinutes
s.isDisabled = opt.Disable
return s
}
func (s HandballP1Under) ID() int64 {
return s.id
}
func (s HandballP1Under) ShortName() string {
return fmt.Sprintf("#%d P1 Under (+%v, %d min)", s.id, s.totalDiff, s.fixateGamePaceEveryNMinutes)
}
func (s HandballP1Under) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s HandballP1Under) GetTelegramTipPattern() string {
return `Сигнал # %d.
Алгоритм %d. Тотал Меньше в 1м тайме.
%s. %s
%s - %s
Точка входа %.1f кф. %.3f
Рекомендация: тотал меньше %.1f и выше (%.1f; %.1f)`
}
func (s HandballP1Under) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
if liveMatch.CurrentPeriod == 1 {
return true
}
return false
}
func (s HandballP1Under) IsInteresting(match *TeamMatch) (notes interface{}, _ bool) {
if s.isDisabled {
return
}
eps := 0.01
// Проверки на то, что полные данные
if match.BasicTotal < 1 {
return nil, false
}
var tipTotal float64
/*
if match.H1BasicTotal < 1 {
tipTotal = (match.BasicTotal / 2) + s.totalDiff
} else {
tipTotal = match.H1BasicTotal + s.totalDiff
}
*/
if match.H1BasicTotal < 1 {
return
}
tipTotal = match.H1BasicTotal + s.totalDiff
// ВАЖНО!
// После деления тотал может быть 25.25
tipTotal = correctTotal(tipTotal, roundUp)
basicGoalsPerMinute := tipTotal / 30
if math.Abs(match.H2HGoalsPerMinute) > eps {
// Если команды между собой забивают больше чем дает бук (с учетом погрешности)
// - матч не интересен (ибо опасно)
if match.H2HGoalsPerMinute > basicGoalsPerMinute {
return
}
} else {
if math.Abs(match.HomeGoalsPerMinute) < eps {
return
}
if math.Abs(match.AwayGoalsPerMinute) < eps {
return
}
// Считаем средний темп с которым играют обе команды
avgGoalsPerMinute := (match.HomeGoalsPerMinute + match.AwayGoalsPerMinute) / 2
// Если в среднем команды забивают больше чем дает бук (с учетом погрешности)
// - матч не интересен (ибо опасно)
if avgGoalsPerMinute > basicGoalsPerMinute {
return
}
}
return nil, true
}
func (s HandballP1Under) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (comment string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 1800 {
return "в перерыве не ставим (багфикс)", nil, Waiting
}
if upd.CurrentTime < p1TimeFrame.Since {
return "еще рано", nil, Waiting
}
if upd.CurrentTime > p1TimeFrame.Until {
return "время вышло", nil, Unwatch
}
var tipTotal float64
if match.H1BasicTotal < 1 {
tipTotal = (match.BasicTotal / 2) + s.totalDiff
} else {
tipTotal = match.H1BasicTotal + s.totalDiff
}
// ВАЖНО!
// После деления тотал может быть 25.25
tipTotal = correctTotal(tipTotal, roundUp)
basicGoalsPerMinute := tipTotal / 30
tipTotalStr := fmt.Sprintf("%.1f", tipTotal)
if float64(upd.HomeGoals+upd.AwayGoals) > tipTotal {
return "Тотал пробит", nil, Unwatch
}
var isPassed bool
comment, isPassed = checkGamePace(basicGoalsPerMinute, match, upd, s.fixateGamePaceEveryNMinutes, Lte)
if !isPassed {
return comment, nil, Waiting
}
// Задача - найти максимальный тотал >= tipTotal, чтобы коэфф. >= 1.5
tipTotal = tipTotal - 0.01 // Для удобного сравнивания float
var (
maxTotalValue float64
bestOffer onexbet.ParamOffer // строковое представление тотала
)
for _, offer := range upd.H1.Total.Under {
if offer.Price < 1.5 {
continue
}
totalValue, err := strconv.ParseFloat(offer.Param, 64)
if err != nil {
continue
}
if totalValue < tipTotal {
continue
}
if bestOffer.Param == "" {
// инициализация
maxTotalValue = totalValue
bestOffer = offer
} else {
if totalValue > maxTotalValue {
maxTotalValue = totalValue
bestOffer = offer
}
}
}
if bestOffer.Param == "" {
comment = fmt.Sprintf("Тотал %s и больше с коэф. >= 1.5 не найден",
tipTotalStr)
return comment, nil, Waiting
}
return "", &BetDetails{
Market: MarketTotalH1,
Side: SideUnder,
Param: bestOffer.Param,
Price: bestOffer.Price,
}, Bet
}
func (s HandballP1Under) 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
//}
tipTotal, err := strconv.ParseFloat(tip.Param, 64)
if err != nil {
return
}
res := &TipResult{
Result: fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints),
}
goals := float64(h1.HomePoints + h1.AwayPoints)
// Сперва проверяем что тоталы не равны
dif := goals - tipTotal
eps := 0.0001
if math.Abs(dif) < eps {
// Равны - расход
res.Status = model.Void
} else {
if goals < tipTotal {
res.Status = model.Won
} else {
res.Status = model.Lost
}
}
return res
}