Files

470 lines
10 KiB
Go
Raw Permalink Normal View History

2026-07-21 05:29:37 +03:00
package model
import (
"fmt"
"math"
"sort"
"strings"
"gordenko.dev/dima/qx"
)
func New(qxdb *qx.Db) *Model {
if qxdb == nil {
panic("Param is required")
}
s := new(Model)
s.qx = qxdb
return s
}
type Model struct {
qx *qx.Db
}
func (s *Model) AddTip(tip Tip) (tipID int64, err error) {
/*
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
*/
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
tipID, err = tx.Insert(tip)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) GetTip(tipID int64) (_ *Tip, err error) {
tip := Tip{
TipID: tipID,
}
found, err := s.qx.One(&tip)
if err != nil {
return
}
if found {
return &tip, nil
}
return
}
func (s *Model) HasTip(matchID string, strategyID int64) (has bool, err error) {
has, err = s.qx.Has("tips", "WHERE matchID=? AND strategyID=?", matchID, strategyID)
return
}
func (s *Model) ListSportTips(sportID int) (tips []Tip, err error) {
err = s.qx.ListBy(&tips, "WHERE sportID=? ORDER BY tipTime DESC", sportID)
return
}
func (s *Model) ListTips() (tips []Tip, err error) {
err = s.qx.ListBy(&tips, "ORDER BY tipTime DESC")
return
}
func (s *Model) ListWaitingTips() (tips []Tip, err error) {
err = s.qx.ListBy(&tips, "WHERE status=? ORDER BY tipTime DESC", Waiting)
return
}
func (s *Model) ListStrategyTips(strategyID int) (tips []Tip, err error) {
err = s.qx.ListBy(&tips, "WHERE strategyID=? ORDER BY tipTime DESC", strategyID)
return
}
// ListMatchTips - возвращает прогнозы с неизвестным результатом
func (s *Model) ListMatchTips(matchID string) (tips []Tip, err error) {
err = s.qx.ListBy(&tips, "WHERE matchID=? AND status=0", matchID)
return
}
func (s *Model) SetTipResult(req SetTipResultReq) (err error) {
switch req.Status {
case Waiting, Won, Lost, Void:
// pass
default:
err = fix.Field(WrongValue, "status")
return
}
req.Result = strings.TrimSpace(req.Result)
if req.Result == "" {
err = fix.Field(EmptyValue, "result")
return
}
err = s.qx.Exec(`UPDATE tips SET result=?, status=?, notes=? WHERE tipID=?`,
req.Result, req.Status, req.Notes, req.TipID)
return
}
type StrategyPoint struct {
Date int64 `json:"date"`
Profit float64 `json:"profit"`
}
func (s *Model) GetStrategyHistory(strategyID int64) (list []StrategyPoint, err error) {
var (
tips []Tip
profit float64
)
err = s.qx.ListBy(&tips, "WHERE strategyID=? AND status IN (1, 2) ORDER BY tipTime ASC",
strategyID)
if err != nil {
return
}
for _, tip := range tips {
if tip.Status == Won {
profit += (tip.Price - 1) * 10
} else {
profit -= 10
}
list = append(list, StrategyPoint{
Date: tip.TipTime,
Profit: profit,
})
}
return
}
func (s *Model) GetStrategyRatedHistory(strategyID int64) (list []StrategyPoint, err error) {
var (
tips []Tip
profit float64
)
err = s.qx.ListBy(&tips, "WHERE strategyID=? AND status IN (1, 2) AND isRated=true ORDER BY tipTime ASC",
strategyID)
if err != nil {
return
}
for _, tip := range tips {
if tip.Status == Won {
profit += (tip.Price - 1) * 10
} else {
profit -= 10
}
list = append(list, StrategyPoint{
Date: tip.TipTime,
Profit: profit,
})
}
return
}
func (s *Model) ListStrategiesReports(sportID int) (list []StrategyReport, err error) {
var (
tips []Tip
reports = make(map[int64]*StrategyReport)
)
err = s.qx.ListBy(&tips, "WHERE sportID=? AND status IN (1, 2) ORDER BY tipTime ASC", sportID)
if err != nil {
return
}
for _, tip := range tips {
report, ok := reports[tip.StrategyID]
if !ok {
report = &StrategyReport{
StrategyID: tip.StrategyID,
}
reports[tip.StrategyID] = report
}
report.Turnover += 10
if tip.Status == Won {
report.Profit += (tip.Price - 1) * 10
report.WonTips++
} else {
report.Profit -= 10
report.LostTips++
}
if report.Profit < report.Drawdown {
report.Drawdown = report.Profit
}
}
var strategies []Strategy
err = s.qx.ListQuery(&strategies,
"SELECT strategyID, name FROM strategies WHERE sportID=?", sportID)
if err != nil {
return
}
for _, strategy := range strategies {
report, ok := reports[strategy.StrategyID]
if ok {
report.Strategy = strategy.Name
}
}
for _, report := range reports {
// turnover = 100%
// profit = x (ROI)
report.ROI = (report.Profit * 100) / report.Turnover
// won + lost = 100%
// won = x (Accuracy)
report.Accuracy = (float64(report.WonTips) * 100) / float64(report.WonTips+report.LostTips)
list = append(list, *report)
}
sort.Slice(list, func(i, j int) bool {
return list[i].StrategyID < list[j].StrategyID
})
return
}
func (s *Model) GetSportRatedReport(sportID int64) (report SportReport, err error) {
var tips []Tip
report.SportID = sportID
err = s.qx.ListBy(&tips, "WHERE sportID=? AND status IN (1, 2) AND isRated=true ORDER BY tipTime ASC", sportID)
if err != nil {
return
}
for _, tip := range tips {
report.Turnover += 10
if tip.Status == Won {
report.Profit += (tip.Price - 1) * 10
report.WonTips++
} else {
report.Profit -= 10
report.LostTips++
}
if report.Profit < report.Drawdown {
report.Drawdown = report.Profit
}
}
// turnover = 100%
// profit = x (ROI)
report.ROI = (report.Profit * 100) / report.Turnover
// won + lost = 100%
// won = x (Accuracy)
report.Accuracy = (float64(report.WonTips) * 100) / float64(report.WonTips+report.LostTips)
return
}
func getAccuracy(won, lost int) (accuracy float64) {
// won + lost = 100%
// won = x%
// x = (won*100) / (won+lost)
total := won + lost
if total == 0 {
return
}
return float64(won*100) / float64(total)
}
type StrategyChampStats struct {
//StrategyID int64 `json:"strategyID"`
ZoneID string `json:"zoneID" qx:"table=tips"`
ZoneName string `json:"zoneName"`
ChampID string `json:"champID"`
ChampName string `json:"champName"`
Won int `json:"won"`
Lost int `json:"lost"`
AvgPrice float64 `json:"avgPrice"`
}
// Статистика по лигам
func (s *Model) ListStrategyChampStats(strategyID int64) (list []StrategyChampStats, err error) {
err = s.qx.ListQuery(&list, `
SELECT z.zoneID, z.name, t.champID, c.name, COUNT(IF(t.status=1,1,NULL)) as won,
COUNT(IF(t.status=2,1,NULL)) as lost, AVG(t.price)
FROM tips t
INNER JOIN teamChamps c USING(champID)
INNER JOIN teamZones z USING(zoneID)
WHERE t.strategyID=? AND t.status IN (1,2)
GROUP BY t.champID
ORDER BY c.name ASC`, strategyID)
if err != nil {
return
}
eps := 0.001
sort.Slice(list, func(i, j int) bool {
// Сортируем по точности прогнозов
a := getAccuracy(list[i].Won, list[i].Lost)
b := getAccuracy(list[j].Won, list[j].Lost)
dif := math.Abs(a - b)
if dif < eps {
if list[i].Won == list[j].Won {
if list[i].ZoneName == list[j].ZoneName {
return list[i].ChampName < list[j].ChampName
}
return list[i].ZoneName < list[j].ZoneName
}
return list[i].Won > list[j].Won
}
return a > b
})
return
}
// Статистика по лигам
func (s *Model) ListStrategyRatedChampStats(strategyID int64) (list []StrategyChampStats, err error) {
err = s.qx.ListQuery(&list, `
SELECT z.zoneID, z.name, t.champID, c.name, COUNT(IF(t.status=1,1,NULL)) as won,
COUNT(IF(t.status=2,1,NULL)) as lost, AVG(t.price)
FROM tips t
INNER JOIN teamChamps c USING(champID)
INNER JOIN teamZones z USING(zoneID)
WHERE t.strategyID=? AND t.status IN (1,2) AND t.isRated=true
GROUP BY t.champID
ORDER BY c.name ASC`, strategyID)
if err != nil {
return
}
eps := 0.001
sort.Slice(list, func(i, j int) bool {
// Сортируем по точности прогнозов
a := getAccuracy(list[i].Won, list[i].Lost)
b := getAccuracy(list[j].Won, list[j].Lost)
dif := math.Abs(a - b)
if dif < eps {
if list[i].Won == list[j].Won {
if list[i].ZoneName == list[j].ZoneName {
return list[i].ChampName < list[j].ChampName
}
return list[i].ZoneName < list[j].ZoneName
}
return list[i].Won > list[j].Won
}
return a > b
})
return
}
type Strategy struct {
StrategyID int64 `json:"strategyID" qx:"table=strategies; pk"`
Name string `json:"name"`
}
// Статистика по лигам
func (s *Model) ListStrategies() (list []Strategy, err error) {
tmp := []struct {
StrategyID int64
Name string
SportID int64
SportName string
}{}
err = s.qx.ListQuery(&tmp, `
SELECT s.strategyID, s.name, x.sportID, x.name
FROM strategies s
INNER JOIN sports x USING(sportID)
ORDER BY x.sportID ASC, s.strategyID ASC`)
if err != nil {
return
}
for _, x := range tmp {
list = append(list, Strategy{
StrategyID: x.StrategyID,
Name: fmt.Sprintf("#%d %s. %s", x.StrategyID, x.SportName, x.Name),
})
}
return
/*
return []Strategy{
{
StrategyID: 1,
Name: "#1 Алгоритм 1 (Футбол, гол в 1м тайме)",
},
{
StrategyID: 2,
Name: "#2 Алгоритм 2 (Футбол, гол в матче)",
},
{
StrategyID: 3,
Name: "#3 Алгоритм 3 (Футбол, 2й гол в матче)",
},
{
StrategyID: 4,
Name: "#4 Алгоритм ТБ (-9) (Гандбол, ТБ в матче)",
},
{
StrategyID: 5,
Name: "#5 Алгоритм ТМ (+8) (Гандбол, ТМ в матче)",
},
{
StrategyID: 6,
Name: "#6 Алгоритм 1 модифицированный (Футбол, гол в 1м тайме)",
},
{
StrategyID: 7,
Name: "#7 Алгоритм 2 модифицированный (Футбол, гол в матче)",
},
{
StrategyID: 8,
Name: "#8 Алгоритм 3 модифицированный (Футбол, 2й гол в матче)",
},
{
StrategyID: 11,
Name: "#11 Алгоритм ТБ (-6) (Гандбол, ТБ в 1м тайме)",
},
{
StrategyID: 12,
Name: "#12 Алгоритм ТМ (+5) (Гандбол, ТМ в 1м тайме)",
},
}, nil
*/
}