1166 lines
29 KiB
Go
1166 lines
29 KiB
Go
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
|
||
}
|