hkjc_race_results
Hong Kong horse racing results and historical form guide
Download a file
CSV · Excel · Parquet · JSON. Filtered exports.
See pricingQuery with SQL
DBeaver, DuckDB, MotherDuck, or HTTPS. Sub-second latency.
Open connection detailsConnect to AI
MCP for Claude Desktop, Cursor, ChatGPT, Goose. 1-line install.
See setupComplete historical record of every Hong Kong horse race from the Jockey Club. Track individual horse performances across thousands of races with detailed finishing positions, winning margins, race times, and running styles. Perfect for form analysis, identifying patterns, and researching historical performance trends. Includes 60+ data points per race entry covering horse details, jockey and trainer information, race conditions, and performance metrics. Download ready-to-use Excel or CSV files for your own analysis.
Evaluate it in full below: the evidence and a working sample are open to everyone. Buy or subscribe to unlock the complete dataset — your account is created automatically at checkout.
The evidence
Rows over time
yearSchema & column quality (91 columns)
Scroll table sideways to see more columns →
| Column | Type | Description | Populated | Distinct |
|---|---|---|---|---|
| horse_id | String | Unique horse identifier (e.g., 'HK_2020_E403') | — | — |
| race_date | DateTime | Date of the race (YYYY-MM-DD) | 100.0% | 3,586 |
| race_number | Int64 | Race number within the meeting (1-11) | 100.0% | 11 |
| race_index | Nullable(String) | Original HKJC race identifier within the meeting (1-10). Use this for matching with external HKJC data sources. NULL for modern data (Sept 2025+). | — | — |
| race_id | Nullable(String) | Unique race identifier (format: ST_20250928_R01) | — | — |
| horse_name | String | Horse's name in English | — | — |
| horse_number | Nullable(Int64) | Horse's saddle/barrier number for the race | — | — |
| finishing_position | Int64 | Final finishing position (1, 2, 3, etc.). 0 for non-starters — see non_starter. | — | — |
| non_starter | Nullable(Bool) | TRUE when the horse was entered but did not start the race (scratched, withdrawn, or a standby/reserve that never ran). For these rows finishing_position is not meaningful (0). Filter on non_starter = false for finishing-position calculations; keep them for withdrawal and pre-race analysis. | — | — |
| result_status | Nullable(String) | Result classification. 'PLACED' = a completed run with a numeric finishing_position (any position, not the racing sense of "placed in the money"). Codes present in the data: PU = pulled up (started, retired mid-race, no placing); WV = withdrawn on veterinary advice; WXNR = declared a non-runner. WV and WXNR are non-runners (the horse did not start), so they are always flagged non_starter = true. non_starter = false therefore cleanly means the horse ran; for a set restricted to placed runners (excluding a PU or a future DISQ that ran but did not place) filter result_status = 'PLACED'. DISQ (disqualified) is decoded but does not yet appear: HKJC publishes disqualifications weeks after the race, after our re-scrape window closes, so the provisional placing stays frozen. When a DISQ is ingested the runner did start and HKJC renumbers the field, so finishing_position reflects the official result. See result_status_label for decoded wording. NULL for non-starters with no recorded code and entries with no outcome. | — | — |
| result_status_label | Nullable(String) | Human-readable form of result_status (Finished, Pulled Up, Withdrawn (Vet), Non-runner; Disqualified once ingested). Unmapped codes pass through verbatim. | — | — |
| finishing_time_seconds | Nullable(Float64) | Finishing time converted to total seconds | — | — |
| finishing_time | Nullable(String) | Finishing time (MM:SS.ss format) | — | — |
| margin | Nullable(String) | Margin from winner (lengths, head, neck, short head, etc.) | — | — |
| margin_lengths | Nullable(Float64) | Margin converted to numeric lengths | — | — |
| draw | Nullable(Int64) | Starting gate/barrier position (same as horse_number in HK flat racing) | — | — |
| odds | Nullable(Float64) | Win odds as a decimal number (e.g. 4.5 = 9/2) | — | — |
| position_1 | Nullable(String) | Running position at first checkpoint | — | — |
| position_2 | Nullable(String) | Running position at second checkpoint | — | — |
| position_3 | Nullable(String) | Running position at third checkpoint | — | — |
| position_4 | Nullable(String) | Running position at fourth checkpoint | — | — |
| position_5 | Nullable(String) | Running position at fifth checkpoint | — | — |
| position_6 | Nullable(String) | Running position at sixth checkpoint (longer races only) | — | — |
| section_1_time | Nullable(String) | Time for first section in seconds (e.g., '14.77') | — | — |
| section_2_time | Nullable(String) | Time for second section in seconds | — | — |
| section_3_time | Nullable(String) | Time for third section in seconds | — | — |
| section_4_time | Nullable(String) | Time for fourth section in seconds | — | — |
| section_5_time | Nullable(String) | Time for fifth section in seconds | — | — |
| section_6_time | Nullable(String) | Time for sixth section in seconds (longer races only) | — | — |
| lbw_1 | Nullable(String) | Lengths behind leader at first checkpoint (e.g., '2-1/4') | — | — |
| lbw_2 | Nullable(String) | Lengths behind leader at second checkpoint | — | — |
| lbw_3 | Nullable(String) | Lengths behind leader at third checkpoint | — | — |
| lbw_4 | Nullable(String) | Lengths behind leader at fourth checkpoint | — | — |
| lbw_5 | Nullable(String) | Lengths behind leader at fifth checkpoint | — | — |
| lbw_6 | Nullable(String) | Lengths behind leader at sixth checkpoint | — | — |
| split_800m_600m_time | Nullable(Float64) | Time in seconds for the 200m run between 800m-to-go and 600m-to-go (second-to-last 400m section, first half). Available from 12 November 2022. Blank for older races or those without sectional data. | — | — |
| split_600m_400m_time | Nullable(Float64) | Time in seconds for the 200m run between 600m-to-go and 400m-to-go (second-to-last 400m section, second half). | — | — |
| split_400m_200m_time | Nullable(Float64) | Time in seconds for the 200m run between 400m-to-go and 200m-to-go (final 400m section, first half). | — | — |
| split_final_200m_time | Nullable(Float64) | Time in seconds for the final 200m (finishing sprint). The single most-analyzed sectional metric in racing; lower is faster. Available from 12 November 2022 (when HKJC began publishing 200m sub-splits); also from April 2024 via the V2 spider. | — | — |
| split_first_200m_time | Nullable(Float64) | Time in seconds for the opening 200m from the gates. Four of the eight standard HK race distances (1000m / 1400m / 1800m / 2200m) open with a 200m section; the four multiples of 400m (1200 / 1600 / 2000 / 2400) open with 400m and have no opening 200m timing point. When the distance has no opening 200m, this column is NULL by design, not missing data — use has_opening_200m_segment to filter. Coverage back to 2013 for distances that have the timing point; this timing point has always been part of HKJC's published sectional data, only the column surfacing is new. | — | — |
| has_opening_200m_segment | Nullable(Bool) | True when the race distance has an opening 200m timing point (1000m / 1400m / 1800m / 2200m). False for 400m-aligned distances (1200 / 1600 / 2000 / 2400) and Happy Valley 1650m, where split_first_200m_time is NULL by design. Use this to distinguish "no data possible at this distance" from "data missing for this race" when filtering or charting opening-pace metrics. | — | — |
| section_1_vs_pace | Nullable(Float64) | Difference between horse's section 1 time and race benchmark. Negative = faster than field average. Available for races with V2 sectional data (Jan 2026+). | — | — |
| section_2_vs_pace | Nullable(Float64) | Difference between horse's section 2 time and race benchmark. Negative = faster than field. | — | — |
| section_3_vs_pace | Nullable(Float64) | Difference between horse's section 3 time and race benchmark. Negative = faster than field. | — | — |
| section_4_vs_pace | Nullable(Float64) | Difference between horse's section 4 time and race benchmark. Negative = faster than field. | — | — |
| section_5_vs_pace | Nullable(Float64) | Difference between horse's section 5 time and race benchmark. Negative = faster than field. | — | — |
| section_6_vs_pace | Nullable(Float64) | Difference between horse's section 6 time and race benchmark. Negative = faster than field (longer races only). | — | — |
| venue_code | String | Venue code (ST/HV) | 100.0% | 2 |
| venue | Nullable(String) | Race venue (Sha Tin / Happy Valley) | — | — |
| surface_type | Nullable(String) | Track surface type: 'Turf' or 'AWT' (All Weather Track) | — | — |
| course_config | Nullable(String) | Course configuration (A, B, C, C+3, etc.) - different track layouts at same venue | — | — |
| distance | Nullable(Int64) | Race distance in meters (1000-2400m typical) | — | — |
| distance_category | String | Distance category (Sprint/Mile/Middle/Long) | — | — |
| race_name | Nullable(String) | Name of the race (e.g., 'FAT CHOI HANDICAP') | — | — |
| race_class | Nullable(String) | Race class (Class 1-5, Group 1/2/3, Griffin, etc.) | — | — |
| track_condition | Nullable(String) | Track going condition (Good/Good to Firm/Yielding/etc.) | — | — |
| field_size | Int64 | Number of horses in the race | — | — |
| jockey | String | Jockey name | — | — |
| jockey_allowance | Nullable(Int64) | Weight allowance for apprentice jockeys (0/2/5 lbs). NULL for historical races. | — | — |
| trainer | String | Trainer name | — | — |
| owner | String | Owner name | — | — |
| horse_colour | Nullable(String) | Horse's coat colour (Bay, Chestnut, Grey, etc.) | — | — |
| horse_sex | Nullable(String) | Horse's sex (G=Gelding, C=Colt, F=Filly, M=Mare, H=Horse) | — | — |
| horse_age | Nullable(Int64) | Horse's age in years | — | — |
| horse_origin | String | Country of origin (AUS, NZ, IRE, GB, etc.) | — | — |
| import_type | String | Import classification (PP, PPG, ISG, etc.) | — | — |
| import_date | Nullable(DateTime) | Date imported to Hong Kong | — | — |
| declared_horse_weight | Nullable(Int32) | Horse's declared body weight before racing (in lbs, typically 1000-1200). Useful for tracking fitness/condition changes. | — | — |
| sire | String | Sire (father) | — | — |
| dam | String | Dam (mother) | — | — |
| dam_sire | String | Dam's sire (maternal grandfather) | — | — |
| is_winner | Nullable(UInt8) | True if horse won the race | — | — |
| is_top_three | Nullable(UInt8) | True if horse finished in top 3 (excludes scratched entries) | — | — |
| dead_heat | Nullable(Bool) | True if horse tied for position. FALSE by default for historical races. | — | — |
| weight | Nullable(Int32) | Weight carried by the horse, in pounds (e.g. 126) | — | — |
| gear | Nullable(String) | Equipment used (B=Blinkers, TT=Tongue Tie, H=Hood, SR=Shadow Roll, etc.). '--' means no gear. See gear_label for the decoded form. | — | — |
| gear_label | Nullable(String) | Equipment decoded to words, e.g. 'Visor (removed) / Blinkers (2nd time) / Tongue Tie'. 'No gear' where the raw code is '--'. Codes are HKJC's own (Equipment Register, racing.hkjc.com/en-us/local/page/reg-approved-gear); a trailing 1/2 means first/second time worn and a trailing '-' means removed this start. Unrecognised codes pass through unchanged. | — | — |
| entry_rating | Nullable(Int64) | Horse's official HKJC rating at the time this race was run (1-144 scale). Resolved from race card and horse profile sources. Single authoritative rating per entry. | — | — |
| rating_type | String | Rating band for the race (H=Handicap, I=International) | — | — |
| total_stakes | Nullable(Float64) | Total career prize money (HKD) | — | — |
| season_stakes | Nullable(Float64) | Current season prize money (HKD) | — | — |
| no_of_starts | Nullable(String) | Career record (format: wins-seconds-thirds-total) | — | — |
| career_wins | Nullable(Int32) | Career wins in Hong Kong | — | — |
| career_seconds | Nullable(Int32) | Career second places | — | — |
| career_thirds | Nullable(Int32) | Career third places | — | — |
| career_total_starts | Nullable(Int32) | Total career starts | — | — |
| career_win_rate | Nullable(Float64) | Career win percentage (0-1) | — | — |
| race_url | String | Source URL for the race page | — | — |
| video_replay_url | String | Direct URL to watch race video replay for this specific horse | — | — |
| updated_at | Nullable(DateTime) | Timestamp when record was last updated | — | — |
| id | String | Stable unique identifier for this row — the last column of every export. Use it to deduplicate, to key an incremental load, or to reference one specific row. It is additive: the entity and join keys you already use (race_id, horse_id, and the like) are unchanged. | — | — |
Last 12 months of race results across every meeting, full schema, no row cap. Includes both Starter and Pro tier columns so buyers can see the full surface area.
Choose your access
A Pass gives you freshness within a rolling window. The Full Archive gives you the full history we hold for the dataset, frozen at purchase. They solve different problems, and they work together.
One-off purchase
Full Archive
The full history we hold, frozen at purchase. Yours to keep.
Full details →- What you get
- The full history we hold, to your purchase date
- History depth
- May 1977 – present, in full
- Updates
- None. Frozen at purchase
- Delivery
- Gzipped CSV, opens in Excel; link valid 7 days
- API access
- Not included
- Access window
- Most recent 24 months, rolling
- Updates
- Continuous, within hours of the source
- Delivery
- API, SQL and downloads
- Exports
- 1,000,000 rows/month included
Included Datasets
- Access window
- Most recent 24 months, rolling
- Updates
- Continuous, within hours of the source
- Delivery
- API, SQL and downloads
- Exports
- Uncapped
Included Datasets
Get notified when this dataset is updated.
Questions about this data?
Our data experts are ready to help clarify any aspects of this dataset.
Contact Our Team