hkjc 0.3.21__tar.gz → 0.3.23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hkjc-0.3.21 → hkjc-0.3.23}/PKG-INFO +1 -2
- {hkjc-0.3.21 → hkjc-0.3.23}/dashboard/app.py +45 -26
- hkjc-0.3.23/dashboard/static/script.js +997 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/dashboard/static/styles.css +109 -6
- {hkjc-0.3.21 → hkjc-0.3.23}/dashboard/templates/index.html +27 -8
- {hkjc-0.3.21 → hkjc-0.3.23}/pyproject.toml +1 -2
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/historical.py +31 -6
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/live.py +32 -22
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/utils.py +6 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/uv.lock +1 -74
- hkjc-0.3.21/dashboard/static/script.js +0 -707
- {hkjc-0.3.21 → hkjc-0.3.23}/.gitignore +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/.python-version +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/README.md +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/dashboard/templates/horse-info.html +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/__init__.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/features.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/harville_model.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/processing.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/py.typed +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/speedpro.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/strategy/place_only.py +0 -0
- {hkjc-0.3.21 → hkjc-0.3.23}/src/hkjc/strategy/qpbanker.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hkjc
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.23
|
4
4
|
Summary: Library for scrapping HKJC data and perform basic analysis
|
5
5
|
Requires-Python: >=3.11
|
6
6
|
Requires-Dist: beautifulsoup4>=4.14.2
|
@@ -12,5 +12,4 @@ Requires-Dist: numpy>=2.3.3
|
|
12
12
|
Requires-Dist: polars>=1.33.1
|
13
13
|
Requires-Dist: pyarrow>=21.0.0
|
14
14
|
Requires-Dist: requests>=2.32.5
|
15
|
-
Requires-Dist: scipy>=1.16.2
|
16
15
|
Requires-Dist: tqdm>=4.67.1
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from flask import Flask, jsonify, render_template, request
|
2
|
+
from flask_caching import Cache
|
2
3
|
|
3
4
|
import polars as pl
|
4
5
|
import numpy as np
|
@@ -26,27 +27,36 @@ def arr_to_dict(arr: np.ndarray, dtype=float):
|
|
26
27
|
|
27
28
|
|
28
29
|
app = Flask(__name__)
|
29
|
-
|
30
|
+
config = {
|
31
|
+
"CACHE_TYPE": "RedisCache",
|
32
|
+
"CACHE_REDIS_HOST": "localhost",
|
33
|
+
"CACHE_REDIS_PORT": "6379"
|
34
|
+
}
|
35
|
+
app.config.from_mapping(config)
|
36
|
+
cache = Cache(app)
|
30
37
|
|
31
38
|
@app.route("/")
|
32
39
|
def disp_race_info():
|
33
40
|
race_info = _fetch_live_races('', '')
|
34
41
|
|
35
|
-
|
36
|
-
|
37
|
-
for
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
42
|
+
try:
|
43
|
+
df_speedpro = speedpro_energy(race_info['Date'])
|
44
|
+
for race_num, race in race_info['Races'].items():
|
45
|
+
for i, runner in enumerate(race['Runners']):
|
46
|
+
df = (df_speedpro
|
47
|
+
.filter(pl.col('RaceNo') == race_num)
|
48
|
+
.filter(pl.col('RunnerNumber') == int(runner['No']))
|
49
|
+
)
|
50
|
+
race_info['Races'][race_num]['Runners'][i]['SPEnergy'] = df['SpeedPRO_Energy_Difference'].item(
|
51
|
+
0)
|
52
|
+
race_info['Races'][race_num]['Runners'][i]['Fitness'] = df['FitnessRatings'].item(
|
53
|
+
0)
|
54
|
+
except: # fill with dummy value if SpeedPro not available
|
55
|
+
for race_num, race in race_info['Races'].items():
|
56
|
+
for i, runner in enumerate(race['Runners']):
|
57
|
+
race_info['Races'][race_num]['Runners'][i]['SPEnergy'] = 0
|
58
|
+
race_info['Races'][race_num]['Runners'][i]['Fitness'] = 0
|
59
|
+
|
50
60
|
return render_template('index.html',
|
51
61
|
race_info=race_info)
|
52
62
|
|
@@ -70,6 +80,7 @@ going_dict = {'TURF': turf_going_dict, 'ALL WEATHER TRACK': aw_going_dict}
|
|
70
80
|
|
71
81
|
|
72
82
|
@app.route("/horse_info/<horse_no>", methods=['GET'])
|
83
|
+
@cache.cached(timeout=7200, query_string=True)
|
73
84
|
def disp_horse_info(horse_no):
|
74
85
|
# read optional filters
|
75
86
|
dist = request.args.get('dist', type=int)
|
@@ -84,16 +95,18 @@ def disp_horse_info(horse_no):
|
|
84
95
|
going = None
|
85
96
|
|
86
97
|
df = get_horse_data(horse_no)
|
87
|
-
if dist is not None:
|
88
|
-
df = df.filter(pl.col('Dist') == dist)
|
89
|
-
if track and track.upper() == 'TURF':
|
90
|
-
df = df.filter(pl.col('Track') == 'Turf')
|
91
|
-
elif track and track.upper() == 'ALL WEATHER TRACK':
|
92
|
-
df = df.filter(pl.col('Track') == 'AWT')
|
93
|
-
if going is not None:
|
94
|
-
df = df.filter(pl.col('G') == going)
|
95
98
|
|
96
|
-
|
99
|
+
if df.height > 0:
|
100
|
+
if dist is not None:
|
101
|
+
df = df.filter(pl.col('Dist') == dist)
|
102
|
+
if track and track.upper() == 'TURF':
|
103
|
+
df = df.filter(pl.col('Track') == 'Turf')
|
104
|
+
elif track and track.upper() == 'ALL WEATHER TRACK':
|
105
|
+
df = df.filter(pl.col('Track') == 'AWT')
|
106
|
+
if going is not None:
|
107
|
+
df = df.filter(pl.col('G') == going)
|
108
|
+
|
109
|
+
return render_template('horse-info.html', df=df.head(5))
|
97
110
|
|
98
111
|
|
99
112
|
@app.route('/live_odds/<race_no>')
|
@@ -111,4 +124,10 @@ def disp_live_odds(race_no=1):
|
|
111
124
|
def disp_speedmap(race_no=1):
|
112
125
|
return speedmap(int(race_no))
|
113
126
|
|
114
|
-
# TODO: trades
|
127
|
+
# TODO: trades API
|
128
|
+
|
129
|
+
# TODO: Single QP trade calculation: ?banker= & cover= , , , => return ev win-prob avgodds
|
130
|
+
|
131
|
+
# TODO: Recommend QP trade given banker ?exclude= , , , &maxC= => return list of pareto trades, cover, ev, win-prob, avgodds
|
132
|
+
|
133
|
+
# TODO: Recommend Place only trade ?exclude= , , , &maxC => return list of pareto trades, cover, ev, win-prob, avgodds
|