Backfill first HKEX IPO document batch
Request:
Start progressively filling detailed information for recent HK IPO targets.
Changes:
- Add scripts/archive_hkex_documents.py to map tickers to HKEXnews stock IDs, select official prospectus and allotment-results PDFs, archive them under data/raw/{ticker}, parse high-confidence T0/T1 facts, export snapshots, and refresh sync state.
- Document the small-batch HKEX document backfill workflow in README.md and the archivist skill.
- Archive prospectus and allotment-results PDFs for 00901, 01081, 01779, 02290, 02553, and 03388.
- Fill T0 details including application dates, expected allotment date, board lot, minimum subscription amount, and offer-share counts for the six tickers.
- Fill T1 allotment-demand details including valid/successful applications, public subscription level, international placees, international subscription level, and final offer-share allocations.
- Refresh source_refs, ipo_master, offering_terms, ipo_demand, ticker_sync_state, and sync_tasks snapshots.
Verification:
- Ran archive_hkex_documents.py in a first small batch and re-ran corrected tickers after parser hardening.
- Parsed project Python scripts with ast.parse.
- Checked SQLite integrity and DB-to-snapshot row counts.
- Verified source_refs paths are repo-relative, source files exist, and SHA-256 hashes match.
- Confirmed batch field completeness for the six processed tickers.
- Ran git diff --check and git diff --cached --check.
- Checked for Python cache and SQLite transient files.
Next useful context:
- This batch added about 55MB of official HKEXnews PDFs.
- Sync state now has 16 complete stages, 1993 pending_due stages, and 42 pending_not_due stages.
- Continue with small --limit batches because HKEXnews title search can include historical or postponed offering documents for the same stock code.
This commit is contained in:
@@ -115,6 +115,18 @@ The script discovers HKEXnews annual new listing report XLSX files, archives the
|
||||
|
||||
By default, exclude report rows without a numeric IPO offer price because transfers, introductions, and de-SPAC transactions are not ordinary public subscription targets.
|
||||
|
||||
## HKEX Document Backfill
|
||||
|
||||
Use the document archiver to fill detailed T0/T1 facts from official HKEXnews PDFs in small batches:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/archive_hkex_documents.py --limit 5 --as-of YYYY-MM-DDTHH:MM:SSZ
|
||||
```
|
||||
|
||||
The script resolves HKEXnews stock IDs, archives prospectus and allotment-results PDFs under `data/raw/{ticker}/`, updates `source_refs`, parses high-confidence fields into `ipo_master`, `offering_terms`, and `ipo_demand`, exports snapshots, and refreshes sync state.
|
||||
|
||||
Prefer small batches. After each batch, inspect a few tickers for date/source mismatches before continuing, because HKEXnews title search can include historical, postponed, or supplemental offering documents for the same stock code.
|
||||
|
||||
## Quality Checks
|
||||
|
||||
Before finishing, confirm:
|
||||
|
||||
@@ -109,6 +109,18 @@ The updater archives the HKEXnews XLSX reports under `data/raw/hkex_new_listing_
|
||||
|
||||
Rows without an IPO offer price, such as transfers of listing, introductions, or de-SPAC transactions, are skipped by default because they are not ordinary public subscription targets.
|
||||
|
||||
## HKEX Document Backfill
|
||||
|
||||
Use the HKEX document archiver to progressively fill detailed T0/T1 facts for open sync tasks:
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/archive_hkex_documents.py --limit 5 --as-of 2026-06-15T08:30:00Z
|
||||
```
|
||||
|
||||
The archiver maps stock codes to HKEXnews title-search stock IDs, downloads the selected prospectus and allotment-results PDFs under `data/raw/{ticker}/`, records `source_refs`, parses high-confidence T0/T1 fields into `ipo_master`, `offering_terms`, and `ipo_demand`, exports snapshots, and refreshes `sync_tasks`.
|
||||
|
||||
Run in small batches because prospectus PDFs are large and title-search results can contain historical or postponed-offering documents.
|
||||
|
||||
## Incremental Archive Sync
|
||||
|
||||
The archivist keeps a per-ticker sync ledger so repeated updates can focus on missing stages:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,8 @@
|
||||
demand_id,ticker,source_id,stage_date,valid_applications,successful_applications,public_oversubscription_times,international_placees,international_oversubscription_times,final_hk_offer_shares,final_international_offer_shares,data_as_of,notes
|
||||
00901_allotment_2026_05_27_2026052700001,00901,00901_allotment_results_2026_05_27_2026052700001,2026-05-27,177196,17058,1971.99,215,2.23,1920800,17286500,2026-06-15T08:35:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
01081_allotment_2026_06_04_2026060402919,01081,01081_allotment_results_2026_06_04_2026060402919,2026-06-04,122627,28788,134.39,125,10.68,8696600,91314000,2026-06-15T08:35:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
01779_allotment_2026_06_04_2026060402923,01779,01779_allotment_results_2026_06_04_2026060402923,2026-06-04,266377,28057,4762.58,80,10.94,1419350,12773800,2026-06-15T08:35:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
02290_allotment_2026_06_04_2026060402521,02290,02290_allotment_results_2026_06_04_2026060402521,2026-06-04,133189,16359,664.92,78,3.18,12500000,112500000,2026-06-15T08:35:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
02553_allotment_2026_06_02_2026060202644,02553,02553_allotment_results_2026_06_02_2026060202644,2026-06-02,109125,21872,1421.54,97,0.95,6000000,34000000,2026-06-15T08:50:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
03388_allotment_2026_05_28_2026052802543,03388,03388_allotment_results_2026_05_28_2026052802543,2026-05-28,251375,44336,3829.42,183,26.8,7342800,66084750,2026-06-15T08:35:00Z,Parsed from HKEXnews allotment results announcement.
|
||||
06658_allotment_2026_06_12,06658,06658_allotment_results_2026_06_12,2026-06-12,180507,11465,6586.73,64,2.64,1146500,10317600,2026-06-15T06:15:00Z,Claw-back shown as N/A in the HKEXnews allotment results.
|
||||
|
||||
|
@@ -12,11 +12,11 @@ ticker,company_name_en,company_name_zh,stock_short_name,exchange,board,status,li
|
||||
00699,NINGBO JOYSON ELECTRONIC CORP.- H shares,,,HKEX,Main Board,listed,2025-11-06,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
00800,WeRide Inc. - W,,,HKEX,Main Board,listed,2025-11-06,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
00805,New Gonow Recreational Vehicles Inc.,,,HKEX,Main Board,listed,2025-01-13,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
00901,"Shenzhen SDMC Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-27,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
00901,"Shenzhen SDMC Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-27,2026-05-18,2026-05-21,2026-05-26,,2026-06-15T08:35:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
00917,Qunabox Group Limited,,,HKEX,Main Board,listed,2024-05-27,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
00999,Xiaocaiyuan International Holding Ltd.,,,HKEX,Main Board,listed,2024-12-20,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
01021,"Guangdong Huayan Robotics Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-03-30,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01081,"Dajin Heavy Industry Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-06-05,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01081,"Dajin Heavy Industry Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-06-05,2026-05-28,2026-06-02,2026-06-04,,2026-06-15T08:35:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01111,Huashi Group Holdings Limited,,,HKEX,Main Board,listed,2023-11-10,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2023.
|
||||
01187,"Cofoe Medical Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-06,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01236,"SHENZHEN LDROBOT CO., LTD - H Shares",,,HKEX,Main Board,listed,2026-05-11,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
@@ -38,7 +38,7 @@ ticker,company_name_en,company_name_zh,stock_short_name,exchange,board,status,li
|
||||
01609,"Star Sports Medicine Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-05,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01641,"Hongxing Coldchain (Hunan) Co., Ltd.- H shares",,,HKEX,Main Board,listed,2026-01-13,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01768,"BUSY MING GROUP CO., LTD.- H shares",,,HKEX,Main Board,listed,2026-01-28,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01779,"LongBio Pharma (Suzhou) Co., Ltd. - B - H Shares",,,HKEX,Main Board,listed,2026-06-05,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01779,"LongBio Pharma (Suzhou) Co., Ltd. - B - H Shares",,,HKEX,Main Board,listed,2026-06-05,2026-05-28,2026-06-02,2026-06-04,,2026-06-15T08:35:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01828,FWD Group Holdings Limited,,,HKEX,Main Board,listed,2025-07-07,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
01879,"Shanghai Xizhi Technology Co., Ltd. - P - H Shares",,,HKEX,Main Board,listed,2026-04-28,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
01973,"Tian Tu Capital Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2023-10-06,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2023.
|
||||
@@ -52,7 +52,7 @@ ticker,company_name_en,company_name_zh,stock_short_name,exchange,board,status,li
|
||||
02259,Zijin Gold International Company Limited,,,HKEX,Main Board,listed,2025-09-30,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
02268,WuXi XDC Cayman Inc.,,,HKEX,Main Board,listed,2023-11-17,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2023.
|
||||
02271,Zhong An Intelligent Living Service Limited,,,HKEX,Main Board,listed,2023-07-18,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2023.
|
||||
02290,Lung Fung Group Holdings Limited,,,HKEX,Main Board,listed,2026-06-05,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
02290,Lung Fung Group Holdings Limited,,,HKEX,Main Board,listed,2026-06-05,2026-05-28,2026-06-02,2026-06-04,,2026-06-15T08:35:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
02396,B&K CORPORATION LIMITED - B - H shares,,,HKEX,Main Board,listed,2025-12-22,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
02408,"Guangzhou Xiao Noodles Catering Management Co., Ltd.- H shares",,,HKEX,Main Board,listed,2025-12-05,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
02410,"TYK Medicines, Inc - B - H Shares",,,HKEX,Main Board,listed,2024-08-20,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
@@ -115,7 +115,7 @@ ticker,company_name_en,company_name_zh,stock_short_name,exchange,board,status,li
|
||||
02549,Carote Ltd,,,HKEX,Main Board,listed,2024-10-02,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
02550,Easou Technology Holdings Limited,,,HKEX,Main Board,listed,2024-06-07,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
02551,"APT Electronics Co., Ltd. - H shares",,,HKEX,Main Board,listed,2024-11-08,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
02553,"Beijing Shougang LanzaTech Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-06-03,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
02553,"Beijing Shougang LanzaTech Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-06-03,2026-05-26,2026-05-29,2026-06-02,,2026-06-15T08:50:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
02555,"Sichuan Baicha Baidao Industrial Co., Ltd. - H shares",,,HKEX,Main Board,listed,2024-04-23,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
02556,Marketingforce Management Ltd,,,HKEX,Main Board,listed,2024-05-16,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
02559,Dida Inc.,,,HKEX,Main Board,listed,2024-06-28,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2024.
|
||||
@@ -213,7 +213,7 @@ ticker,company_name_en,company_name_zh,stock_short_name,exchange,board,status,li
|
||||
03317,"Shenzhen Xunce Technology Co., Ltd.- H shares",,,HKEX,Main Board,listed,2025-12-30,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
03355,FS.COM Ltd. - H Shares,,,HKEX,Main Board,listed,2026-03-23,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
03378,"Hanx Biopharmaceuticals (Wuhan) Co., Ltd. - B - H shares",,,HKEX,Main Board,listed,2025-12-23,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2025.
|
||||
03388,"Shenzhen Creality 3D Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-29,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
03388,"Shenzhen Creality 3D Technology Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-05-29,2026-05-20,2026-05-26,2026-05-28,,2026-06-15T08:35:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
03625,"Shanghai FourSemi Semiconductor Co., Ltd. - H Shares",,,HKEX,Main Board,listed,2026-03-31,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
03636,"Yunnan Jinxun Resources Co., Ltd. - H shares",,,HKEX,Main Board,listed,2026-01-09,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2026.
|
||||
03650,Keep Inc.,,,HKEX,Main Board,listed,2023-07-12,,,,,2026-06-15T07:30:00Z,Seeded from HKEXnews Main Board New Listing Report 2023.
|
||||
|
||||
|
@@ -12,11 +12,11 @@ ticker,source_id,prospectus_date,offer_price_hkd,board_lot,min_subscription_amou
|
||||
00699,00699_new_listing_report_main_2025,2025-10-28,22.0,,,,,,,,,,3412.2,,,2026-06-15T07:30:00Z
|
||||
00800,00800_new_listing_report_main_2025,2025-10-28,27.1,,,,,,,,,,2391.575,,,2026-06-15T07:30:00Z
|
||||
00805,00805_new_listing_report_main_2025,2024-12-31,1.27,,,,,,,,,,304.8,,,2026-06-15T07:30:00Z
|
||||
00901,00901_new_listing_report_main_2026,2026-05-18,32.8,,,,,,,,,,629.99944,,,2026-06-15T07:30:00Z
|
||||
00901,00901_prospectus_2026_05_18_2026051800027,2026-05-18,32.8,100,3313.08,19207300,1920800,17286500,0.1,2881095,,,629.99944,,209540070,2026-06-15T08:35:00Z
|
||||
00917,00917_new_listing_report_main_2024,2024-05-17,25.0,,,,,,,,,,492.6,,,2026-06-15T07:30:00Z
|
||||
00999,00999_new_listing_report_main_2024,2024-12-12,8.5,,,,,,,,,,860.0368,,,2026-06-15T07:30:00Z
|
||||
01021,01021_new_listing_report_main_2026,2026-03-20,17.0,,,,,,,,,,1808.3172,,,2026-06-15T07:30:00Z
|
||||
01081,01081_new_listing_report_main_2026,2026-05-28,66.4,,,,,,,,,,6640.70384,,,2026-06-15T07:30:00Z
|
||||
01081,01081_prospectus_2026_05_28_2026052800019,2026-05-28,66.4,100,6706.97,86965800,8696600,78269200,0.1,13044870,,,6640.70384,6465.48,737759949,2026-06-15T08:35:00Z
|
||||
01111,01111_new_listing_report_main_2023,2023-10-31,1.04,,,,,,,,,,130.0,,,2026-06-15T07:30:00Z
|
||||
01187,01187_new_listing_report_main_2026,2026-04-27,39.33,,,,,,,,,,1061.91,,,2026-06-15T07:30:00Z
|
||||
01236,01236_new_listing_report_main_2026,2026-04-30,26.36,,,,,,,,,,878.668424,,,2026-06-15T07:30:00Z
|
||||
@@ -38,7 +38,7 @@ ticker,source_id,prospectus_date,offer_price_hkd,board_lot,min_subscription_amou
|
||||
01609,01609_new_listing_report_main_2026,2026-04-24,98.5,,,,,,,,,,829.552225,,,2026-06-15T07:30:00Z
|
||||
01641,01641_new_listing_report_main_2026,2025-12-31,12.26,,,,,,,,,,285.20438,,,2026-06-15T07:30:00Z
|
||||
01768,01768_new_listing_report_main_2026,2026-01-20,236.6,,,,,,,,,,4220.42348,,,2026-06-15T07:30:00Z
|
||||
01779,01779_new_listing_report_main_2026,2026-05-28,96.06,,,,,,,,,,1363.393989,,,2026-06-15T07:30:00Z
|
||||
01779,01779_prospectus_2026_05_28_2026052800023,2026-05-28,96.06,50,4851.44,14193150,1419350,12773800,0.1,2128972,,,1363.393989,1254.9,74193150,2026-06-15T08:35:00Z
|
||||
01828,01828_new_listing_report_main_2025,2025-06-26,38.0,,,,,,,,,,3661.053,,,2026-06-15T07:30:00Z
|
||||
01879,01879_new_listing_report_main_2026,2026-04-20,183.2,,,,,,,,,,2906.375484,,,2026-06-15T07:30:00Z
|
||||
01973,01973_new_listing_report_main_2023,2023-09-25,6.5,,,,,,,,,,1126.177,,,2026-06-15T07:30:00Z
|
||||
@@ -52,7 +52,7 @@ ticker,source_id,prospectus_date,offer_price_hkd,board_lot,min_subscription_amou
|
||||
02259,02259_new_listing_report_main_2025,2025-09-19,71.59,,,,,,,,,,28731.880487,,,2026-06-15T07:30:00Z
|
||||
02268,02268_new_listing_report_main_2023,2023-11-07,20.6,,,,,,,,,,4070.6527,,,2026-06-15T07:30:00Z
|
||||
02271,02271_new_listing_report_main_2023,2023-06-30,1.18,,,,,,,,,,162.14852,,,2026-06-15T07:30:00Z
|
||||
02290,02290_new_listing_report_main_2026,2026-05-28,5.18,,,,,,,,,,647.5,,,2026-06-15T07:30:00Z
|
||||
02290,02290_prospectus_2026_05_28_2026052800031,2026-05-28,5.18,500,3222.17,125000000,12500000,112500000,0.1,18750000,,,647.5,,500000000,2026-06-15T08:35:00Z
|
||||
02396,02396_new_listing_report_main_2025,2025-12-12,38.2,,,,,,,,,,674.18416,,,2026-06-15T07:30:00Z
|
||||
02408,02408_new_listing_report_main_2025,2025-11-27,7.04,,,,,,,,,,685.44608,,,2026-06-15T07:30:00Z
|
||||
02410,02410_new_listing_report_main_2024,2024-08-12,12.1,,,,,,,,,,579.348,,,2026-06-15T07:30:00Z
|
||||
@@ -115,7 +115,7 @@ ticker,source_id,prospectus_date,offer_price_hkd,board_lot,min_subscription_amou
|
||||
02549,02549_new_listing_report_main_2024,2024-09-23,5.78,,,,,,,,,,863.20832,,,2026-06-15T07:30:00Z
|
||||
02550,02550_new_listing_report_main_2024,2024-05-30,5.8,,,,,,,,,,85.8545,,,2026-06-15T07:30:00Z
|
||||
02551,02551_new_listing_report_main_2024,2024-10-31,3.61,,,,,,,,,,139.4904,,,2026-06-15T07:30:00Z
|
||||
02553,02553_new_listing_report_main_2026,2026-05-26,14.6,,,,,,,,,,584.0,,,2026-06-15T07:30:00Z
|
||||
02553,02553_prospectus_2026_05_26_2026052600047,2026-05-26,14.6,200,3454.49,40000000,4000000,36000000,0.1,6000000,,,584.0,486.46,400000000,2026-06-15T08:50:00Z
|
||||
02555,02555_new_listing_report_main_2024,2024-04-15,17.5,,,,,,,,,,2585.8595,,,2026-06-15T07:30:00Z
|
||||
02556,02556_new_listing_report_main_2024,2024-05-07,43.6,,,,,,,,,,259.40692,,,2026-06-15T07:30:00Z
|
||||
02559,02559_new_listing_report_main_2024,2024-06-20,6.0,,,,,,,,,,234.546,,,2026-06-15T07:30:00Z
|
||||
@@ -213,7 +213,7 @@ ticker,source_id,prospectus_date,offer_price_hkd,board_lot,min_subscription_amou
|
||||
03317,03317_new_listing_report_main_2025,2025-12-18,48.0,,,,,,,,,,1089.3312,,,2026-06-15T07:30:00Z
|
||||
03355,03355_new_listing_report_main_2026,2026-03-13,41.6,,,,,,,,,,1900.10912,,,2026-06-15T07:30:00Z
|
||||
03378,03378_new_listing_report_main_2025,2025-12-15,32.0,,,,,,,,,,586.272,,,2026-06-15T07:30:00Z
|
||||
03388,03388_new_listing_report_main_2026,2026-05-20,18.8,,,,,,,,,,1380.43794,,,2026-06-15T07:30:00Z
|
||||
03388,03388_prospectus_2026_05_20_2026052000023,2026-05-20,18.8,150,2848.44,73427550,7342800,66084750,0.1,11014132,,,1380.43794,1272.32,466840101,2026-06-15T08:35:00Z
|
||||
03625,03625_new_listing_report_main_2026,2026-03-23,40.0,,,,,,,,,,480.0,,,2026-06-15T07:30:00Z
|
||||
03636,03636_new_listing_report_main_2026,2025-12-31,30.0,,,,,,,,,,1268.412,,,2026-06-15T07:30:00Z
|
||||
03650,03650_new_listing_report_main_2023,2023-06-30,28.92,,,,,,,,,,313.452312,,,2026-06-15T07:30:00Z
|
||||
|
||||
|
@@ -12,11 +12,15 @@ source_id,ticker,source_type,title,path_base,local_path,url,file_sha256,source_d
|
||||
00699_new_listing_report_main_2025,00699,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
00800_new_listing_report_main_2025,00800,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
00805_new_listing_report_main_2025,00805,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
00901_allotment_results_2026_05_27_2026052700001,00901,allotment_results,ANNOUNCEMENT OF ALLOTMENT RESULTS,repo_root,data/raw/00901/allotment_results_2026-05-27_2026052700001.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0527/2026052700001.pdf,c2201438364d513cb92aa7f96be2ec10bc91296ad31720514562c4b76d2ec48d,2026-05-27,2026-06-15T08:35:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
00901_new_listing_report_main_2026,00901,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
00901_prospectus_2026_05_18_2026051800027,00901,prospectus,GLOBAL OFFERING,repo_root,data/raw/00901/prospectus_2026-05-18_2026051800027.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0518/2026051800027.pdf,d4cb0a3be8a638791d62ab9262d9bfec1e8df110323dcd93fc45ae6ce672ecdb,2026-05-18,2026-06-15T08:35:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
00917_new_listing_report_main_2024,00917,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
00999_new_listing_report_main_2024,00999,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01021_new_listing_report_main_2026,01021,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01081_allotment_results_2026_06_04_2026060402919,01081,allotment_results,Announcement of Allotment Results,repo_root,data/raw/01081/allotment_results_2026-06-04_2026060402919.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0604/2026060402919.pdf,d73aeb70ca4282c5c5c50a4a11f26a3f191a6d5bba4b7a865d091ebc85b614b2,2026-06-04,2026-06-15T08:35:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
01081_new_listing_report_main_2026,01081,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01081_prospectus_2026_05_28_2026052800019,01081,prospectus,GLOBAL OFFERING,repo_root,data/raw/01081/prospectus_2026-05-28_2026052800019.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0528/2026052800019.pdf,d09984648ea2e8b7c10788d585dd6281ad64e5a21eaad9a387f2c5c2966b37f3,2026-05-28,2026-06-15T08:35:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
01111_new_listing_report_main_2023,01111,new_listing_report,HKEXnews Main Board New Listing Report 2023,repo_root,data/raw/hkex_new_listing_reports/main/NLR2023_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2023_Eng.xlsx,1ec5da84e5062a829907253c2458a9af2a028964aa0baf4c0a5c40ac4db43e57,2023-12-29,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01187_new_listing_report_main_2026,01187,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01236_new_listing_report_main_2026,01236,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
@@ -38,7 +42,9 @@ source_id,ticker,source_type,title,path_base,local_path,url,file_sha256,source_d
|
||||
01609_new_listing_report_main_2026,01609,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01641_new_listing_report_main_2026,01641,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01768_new_listing_report_main_2026,01768,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01779_allotment_results_2026_06_04_2026060402923,01779,allotment_results,Announcement of Allotment Results,repo_root,data/raw/01779/allotment_results_2026-06-04_2026060402923.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0604/2026060402923.pdf,7af48176c182598c438b84b2c41684297bb6e10978a2f742d2c07b8cc7319648,2026-06-04,2026-06-15T08:35:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
01779_new_listing_report_main_2026,01779,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01779_prospectus_2026_05_28_2026052800023,01779,prospectus,GLOBAL OFFERING,repo_root,data/raw/01779/prospectus_2026-05-28_2026052800023.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0528/2026052800023.pdf,7687422547c9aff6ec0301bf77cf83a2af5b53a13604bc557ae9c32ce5dc48da,2026-05-28,2026-06-15T08:35:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
01828_new_listing_report_main_2025,01828,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01879_new_listing_report_main_2026,01879,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
01973_new_listing_report_main_2023,01973,new_listing_report,HKEXnews Main Board New Listing Report 2023,repo_root,data/raw/hkex_new_listing_reports/main/NLR2023_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2023_Eng.xlsx,1ec5da84e5062a829907253c2458a9af2a028964aa0baf4c0a5c40ac4db43e57,2023-12-29,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
@@ -52,7 +58,9 @@ source_id,ticker,source_type,title,path_base,local_path,url,file_sha256,source_d
|
||||
02259_new_listing_report_main_2025,02259,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02268_new_listing_report_main_2023,02268,new_listing_report,HKEXnews Main Board New Listing Report 2023,repo_root,data/raw/hkex_new_listing_reports/main/NLR2023_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2023_Eng.xlsx,1ec5da84e5062a829907253c2458a9af2a028964aa0baf4c0a5c40ac4db43e57,2023-12-29,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02271_new_listing_report_main_2023,02271,new_listing_report,HKEXnews Main Board New Listing Report 2023,repo_root,data/raw/hkex_new_listing_reports/main/NLR2023_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2023_Eng.xlsx,1ec5da84e5062a829907253c2458a9af2a028964aa0baf4c0a5c40ac4db43e57,2023-12-29,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02290_allotment_results_2026_06_04_2026060402521,02290,allotment_results,ANNOUNCEMENT OF FINAL OFFER PRICE AND ALLOTMENT RESULTS,repo_root,data/raw/02290/allotment_results_2026-06-04_2026060402521.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0604/2026060402521.pdf,85190f97977be8dd38dfbdd252cb2145fdcc1eab907f19bb48fcd841caaa226f,2026-06-04,2026-06-15T08:35:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
02290_new_listing_report_main_2026,02290,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02290_prospectus_2026_05_28_2026052800031,02290,prospectus,GLOBAL OFFERING,repo_root,data/raw/02290/prospectus_2026-05-28_2026052800031.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0528/2026052800031.pdf,f84e9f37a1b3f66714a30aca66a3ea4868e306d159bfc4eafc2c0b0e76d5f936,2026-05-28,2026-06-15T08:35:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
02396_new_listing_report_main_2025,02396,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02408_new_listing_report_main_2025,02408,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02410_new_listing_report_main_2024,02410,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
@@ -115,7 +123,9 @@ source_id,ticker,source_type,title,path_base,local_path,url,file_sha256,source_d
|
||||
02549_new_listing_report_main_2024,02549,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02550_new_listing_report_main_2024,02550,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02551_new_listing_report_main_2024,02551,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02553_allotment_results_2026_06_02_2026060202644,02553,allotment_results,ANNOUNCEMENT OF FINAL OFFER PRICE AND ALLOTMENT RESULTS,repo_root,data/raw/02553/allotment_results_2026-06-02_2026060202644.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0602/2026060202644.pdf,1176c14ac0a19de15fe09e6b8738b1a1777699b2ef31577cad9783507fbc8232,2026-06-02,2026-06-15T08:50:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
02553_new_listing_report_main_2026,02553,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02553_prospectus_2026_05_26_2026052600047,02553,prospectus,GLOBAL OFFERING,repo_root,data/raw/02553/prospectus_2026-05-26_2026052600047.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0526/2026052600047.pdf,0725adb1457af7bd6c5cd038c3b10ce176043f8c64a9fee9ebd86c42d9d01861,2026-05-26,2026-06-15T08:50:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
02555_new_listing_report_main_2024,02555,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02556_new_listing_report_main_2024,02556,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
02559_new_listing_report_main_2024,02559,new_listing_report,HKEXnews Main Board New Listing Report 2024,repo_root,data/raw/hkex_new_listing_reports/main/NLR2024_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2024_Eng.xlsx,8c456f5e8cc5326f68fe0d4da85006f018ccfe7a5282c4e3d6f7fed67090f60d,2024-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
@@ -213,7 +223,9 @@ source_id,ticker,source_type,title,path_base,local_path,url,file_sha256,source_d
|
||||
03317_new_listing_report_main_2025,03317,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03355_new_listing_report_main_2026,03355,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03378_new_listing_report_main_2025,03378,new_listing_report,HKEXnews Main Board New Listing Report 2025,repo_root,data/raw/hkex_new_listing_reports/main/NLR2025_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2025_Eng.xlsx,a9ac4c349715eaeaf64666b6ef0a142b49895fb15342cd189514d66263a3a066,2025-12-30,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03388_allotment_results_2026_05_28_2026052802543,03388,allotment_results,ANNOUNCEMENT OF ALLOTMENT RESULTS,repo_root,data/raw/03388/allotment_results_2026-05-28_2026052802543.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0528/2026052802543.pdf,9cb7df8ccd704f13591bf4c9a783bf7490da3fc7fed0183c9e4d431ce4d405b6,2026-05-28,2026-06-15T08:35:00Z,HKEXnews Announcements and Notices - [Allotment Results].
|
||||
03388_new_listing_report_main_2026,03388,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03388_prospectus_2026_05_20_2026052000023,03388,prospectus,GLOBAL OFFERING,repo_root,data/raw/03388/prospectus_2026-05-20_2026052000023.pdf,https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0520/2026052000023.pdf,e9c03a103d92851176522aea43b2b5effbd2a249152577639b9ce42cb718d417,2026-05-20,2026-06-15T08:35:00Z,HKEXnews Listing Documents - [Offer for Subscription].
|
||||
03625_new_listing_report_main_2026,03625,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03636_new_listing_report_main_2026,03636,new_listing_report,HKEXnews Main Board New Listing Report 2026,repo_root,data/raw/hkex_new_listing_reports/main/NLR2026_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2026_Eng.xlsx,72688c7edf7f28379a1b22cc4133cf201be69b19bef558d4b95c8da859cc4330,2026-06-09,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
03650_new_listing_report_main_2023,03650,new_listing_report,HKEXnews Main Board New Listing Report 2023,repo_root,data/raw/hkex_new_listing_reports/main/NLR2023_Eng.xlsx,https://www2.hkexnews.hk/-/media/HKEXnews/Homepage/New-Listings/New-Listing-Information/New-Listing-Report/Main/NLR2023_Eng.xlsx,1ec5da84e5062a829907253c2458a9af2a028964aa0baf4c0a5c40ac4db43e57,2023-12-29,2026-06-15T07:30:00Z,Annual HKEXnews new listing report used to seed recent IPO target coverage.
|
||||
|
||||
|
@@ -1,3 +1,10 @@
|
||||
sync_run_id,mode,as_of,started_at,finished_at,status,notes
|
||||
sync_state_20260615T073000Z,recent_ipo_list_refresh,2026-06-15T07:30:00Z,2026-06-15T07:30:00Z,2026-06-15T07:30:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T081500Z,hkex_document_archive,2026-06-15T08:15:00Z,2026-06-15T08:15:00Z,2026-06-15T08:15:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T082000Z,hkex_document_archive,2026-06-15T08:20:00Z,2026-06-15T08:20:00Z,2026-06-15T08:20:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T082500Z,hkex_document_archive,2026-06-15T08:25:00Z,2026-06-15T08:25:00Z,2026-06-15T08:25:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T083000Z,hkex_document_archive,2026-06-15T08:30:00Z,2026-06-15T08:30:00Z,2026-06-15T08:30:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T083500Z,hkex_document_archive,2026-06-15T08:35:00Z,2026-06-15T08:35:00Z,2026-06-15T08:35:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T084500Z,hkex_document_archive,2026-06-15T08:45:00Z,2026-06-15T08:45:00Z,2026-06-15T08:45:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_20260615T085000Z,hkex_document_archive,2026-06-15T08:50:00Z,2026-06-15T08:50:00Z,2026-06-15T08:50:00Z,complete,Derived ticker sync state refreshed.
|
||||
sync_state_seed_2026_06_15,bootstrap_state_refresh,2026-06-15T06:30:00Z,2026-06-15T06:30:00Z,2026-06-15T06:30:00Z,complete,Derived ticker sync state refreshed.
|
||||
|
||||
|
+2035
-2047
File diff suppressed because it is too large
Load Diff
+2051
-2051
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,761 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Archive HKEXnews prospectus and allotment-result documents for open sync tasks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from pypdf import PdfReader
|
||||
|
||||
|
||||
BASE_URL = "https://www1.hkexnews.hk"
|
||||
ACTIVE_STOCK_URL = f"{BASE_URL}/ncms/script/eds/activestock_sehk_e.json"
|
||||
INACTIVE_STOCK_URL = f"{BASE_URL}/ncms/script/eds/inactivestock_sehk_e.json"
|
||||
TITLE_SEARCH_URL = f"{BASE_URL}/search/titlesearch.xhtml"
|
||||
DB_PATH = Path("data/hk_ipo.sqlite")
|
||||
SCHEMA_PATH = Path("schema/hk_ipo.schema.sql")
|
||||
SNAPSHOT_DIR = Path("data/snapshots")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DocumentRow:
|
||||
release_time: str
|
||||
release_date: str
|
||||
headline: str
|
||||
title: str
|
||||
href: str
|
||||
url: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ArchivedSource:
|
||||
source_id: str
|
||||
ticker: str
|
||||
source_type: str
|
||||
title: str
|
||||
local_path: str
|
||||
url: str
|
||||
file_sha256: str
|
||||
source_date: str
|
||||
notes: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProspectusFacts:
|
||||
application_start_date: str | None = None
|
||||
application_end_date: str | None = None
|
||||
allotment_results_expected_date: str | None = None
|
||||
listing_date: str | None = None
|
||||
board_lot: int | None = None
|
||||
min_subscription_amount_hkd: float | None = None
|
||||
global_offer_shares: int | None = None
|
||||
hk_offer_shares_initial: int | None = None
|
||||
international_offer_shares_initial: int | None = None
|
||||
public_offer_pct_initial: float | None = None
|
||||
over_allotment_offer_shares: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AllotmentFacts:
|
||||
final_offer_price_hkd: float | None = None
|
||||
gross_proceeds_hkd_m: float | None = None
|
||||
net_proceeds_hkd_m: float | None = None
|
||||
issued_shares_upon_listing: int | None = None
|
||||
valid_applications: int | None = None
|
||||
successful_applications: int | None = None
|
||||
public_oversubscription_times: float | None = None
|
||||
international_placees: int | None = None
|
||||
international_oversubscription_times: float | None = None
|
||||
final_hk_offer_shares: int | None = None
|
||||
final_international_offer_shares: int | None = None
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--db", default=str(DB_PATH), help="Repo-relative SQLite database path.")
|
||||
parser.add_argument("--schema", default=str(SCHEMA_PATH), help="Repo-relative schema path.")
|
||||
parser.add_argument("--as-of", help="Archive timestamp. Defaults to current UTC time.")
|
||||
parser.add_argument("--limit", type=int, default=5, help="Maximum tickers to process.")
|
||||
parser.add_argument("--tickers", help="Comma-separated tickers to process instead of selecting from sync_tasks.")
|
||||
parser.add_argument("--skip-sync-state", action="store_true", help="Do not refresh sync state after updating facts.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def fetch_bytes(url: str) -> bytes:
|
||||
request = Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
with urlopen(request, timeout=60) as response:
|
||||
return response.read()
|
||||
|
||||
|
||||
def parse_as_of(value: str | None) -> str:
|
||||
if value:
|
||||
return datetime.fromisoformat(value.replace("Z", "+00:00")).isoformat().replace("+00:00", "Z")
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
def load_stock_ids() -> dict[str, int]:
|
||||
stock_ids: dict[str, int] = {}
|
||||
for url in [ACTIVE_STOCK_URL, INACTIVE_STOCK_URL]:
|
||||
payload = fetch_bytes(url).decode("utf-8-sig")
|
||||
for item in json.loads(payload):
|
||||
code = item.get("c")
|
||||
stock_id = item.get("i")
|
||||
if code and stock_id:
|
||||
stock_ids.setdefault(code, int(stock_id))
|
||||
return stock_ids
|
||||
|
||||
|
||||
def clean_html(value: str) -> str:
|
||||
text = re.sub(r"<.*?>", " ", value, flags=re.S)
|
||||
return " ".join(html.unescape(text).split())
|
||||
|
||||
|
||||
def parse_release_date(value: str) -> str:
|
||||
return datetime.strptime(value.split()[0], "%d/%m/%Y").date().isoformat()
|
||||
|
||||
|
||||
def title_search_rows(stock_id: int) -> list[DocumentRow]:
|
||||
url = f"{TITLE_SEARCH_URL}?category=0&market=SEHK&stockId={stock_id}"
|
||||
page = fetch_bytes(url).decode("utf-8", "replace")
|
||||
rows: list[DocumentRow] = []
|
||||
for row in re.findall(r"<tr>(.*?)</tr>", page, flags=re.S):
|
||||
release_match = re.search(r"Release Time: </span>(.*?)</td>", row, flags=re.S)
|
||||
headline_match = re.search(r'<div class="headline">(.*?)</div>', row, flags=re.S)
|
||||
link_match = re.search(r'<a href="([^"]+)"[^>]*>(.*?)</a>', row, flags=re.S)
|
||||
if not release_match or not link_match:
|
||||
continue
|
||||
release_time = " ".join(release_match.group(1).split())
|
||||
href = html.unescape(link_match.group(1))
|
||||
rows.append(
|
||||
DocumentRow(
|
||||
release_time=release_time,
|
||||
release_date=parse_release_date(release_time),
|
||||
headline=clean_html(headline_match.group(1)) if headline_match else "",
|
||||
title=clean_html(link_match.group(2)),
|
||||
href=href,
|
||||
url=urljoin(BASE_URL, href),
|
||||
)
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def parse_iso_date(value: str | None) -> date | None:
|
||||
if not value:
|
||||
return None
|
||||
return date.fromisoformat(value)
|
||||
|
||||
|
||||
def date_distance(left: str, right: str) -> int:
|
||||
return abs((date.fromisoformat(left) - date.fromisoformat(right)).days)
|
||||
|
||||
|
||||
def choose_prospectus(rows: list[DocumentRow], prospectus_date: str | None, listing_date: str | None) -> DocumentRow | None:
|
||||
candidates = []
|
||||
for row in rows:
|
||||
headline = row.headline.lower()
|
||||
title = row.title.lower()
|
||||
if not row.href.lower().endswith(".pdf"):
|
||||
continue
|
||||
if "listing documents" not in headline:
|
||||
continue
|
||||
if "global offering" in title or "prospectus" in title:
|
||||
candidates.append(row)
|
||||
if not candidates:
|
||||
return None
|
||||
if prospectus_date:
|
||||
return sorted(candidates, key=lambda row: (date_distance(row.release_date, prospectus_date), row.release_date))[0]
|
||||
listed = parse_iso_date(listing_date)
|
||||
if listed:
|
||||
windowed = [
|
||||
row
|
||||
for row in candidates
|
||||
if 0 <= (listed - date.fromisoformat(row.release_date)).days <= 60
|
||||
]
|
||||
if windowed:
|
||||
candidates = windowed
|
||||
return sorted(candidates, key=lambda row: row.release_date)[-1]
|
||||
|
||||
|
||||
def choose_allotment(rows: list[DocumentRow], listing_date: str | None) -> DocumentRow | None:
|
||||
candidates = [
|
||||
row
|
||||
for row in rows
|
||||
if row.href.lower().endswith(".pdf")
|
||||
and ("allotment results" in row.headline.lower() or "allotment results" in row.title.lower())
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
listed = parse_iso_date(listing_date)
|
||||
if listed:
|
||||
windowed = [
|
||||
row
|
||||
for row in candidates
|
||||
if -5 <= (listed - date.fromisoformat(row.release_date)).days <= 10
|
||||
]
|
||||
if windowed:
|
||||
candidates = windowed
|
||||
return sorted(candidates, key=lambda row: row.release_date)[-1]
|
||||
|
||||
|
||||
def sha256_bytes(data: bytes) -> str:
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def download_document(ticker: str, source_type: str, row: DocumentRow) -> ArchivedSource:
|
||||
data = fetch_bytes(row.url)
|
||||
doc_id = Path(row.href).stem
|
||||
suffix = Path(row.href).suffix.lower() or ".pdf"
|
||||
local_path = Path("data/raw") / ticker / f"{source_type}_{row.release_date}_{doc_id}{suffix}"
|
||||
local_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not local_path.exists() or local_path.read_bytes() != data:
|
||||
local_path.write_bytes(data)
|
||||
return ArchivedSource(
|
||||
source_id=f"{ticker}_{source_type}_{row.release_date.replace('-', '_')}_{doc_id}",
|
||||
ticker=ticker,
|
||||
source_type=source_type,
|
||||
title=row.title,
|
||||
local_path=local_path.as_posix(),
|
||||
url=row.url,
|
||||
file_sha256=sha256_bytes(data),
|
||||
source_date=row.release_date,
|
||||
notes=f"HKEXnews {row.headline}.",
|
||||
)
|
||||
|
||||
|
||||
def first_pdf_text(local_path: str, max_pages: int) -> str:
|
||||
reader = PdfReader(local_path)
|
||||
chunks = []
|
||||
for page in reader.pages[: min(max_pages, len(reader.pages))]:
|
||||
chunks.append(page.extract_text() or "")
|
||||
return " ".join(" ".join(chunks).split())
|
||||
|
||||
|
||||
def normalize_pdf_text(text: str) -> str:
|
||||
replacements = {
|
||||
"H o n g K o n g P u b l i c O f f e r i n g c o m m e n c e s": "Hong Kong Public Offering commences",
|
||||
"a t o r b e f o r e": "at or before",
|
||||
"n o l a t e r": "no later",
|
||||
"o n o r b e f o r e": "on or before",
|
||||
}
|
||||
for source, target in replacements.items():
|
||||
text = text.replace(source, target)
|
||||
text = re.sub(r"\bo\s+n\b", "on", text)
|
||||
text = re.sub(r"\bf\s+r\s+o\s+m\b", "from", text)
|
||||
return text
|
||||
|
||||
|
||||
def integer_after(pattern: str, text: str) -> int | None:
|
||||
match = re.search(pattern, text, flags=re.I)
|
||||
if not match:
|
||||
return None
|
||||
cleaned = match.group(1).replace(",", "").replace(" ", "")
|
||||
if not cleaned:
|
||||
return None
|
||||
return int(cleaned)
|
||||
|
||||
|
||||
def float_after(pattern: str, text: str) -> float | None:
|
||||
match = re.search(pattern, text, flags=re.I)
|
||||
if not match:
|
||||
return None
|
||||
return float(match.group(1).replace(",", ""))
|
||||
|
||||
|
||||
def money_m_after(pattern: str, text: str) -> float | None:
|
||||
match = re.search(pattern, text, flags=re.I)
|
||||
if not match:
|
||||
return None
|
||||
amount = float(match.group(1).replace(",", ""))
|
||||
unit = (match.group(2) or "").lower()
|
||||
if unit.startswith("b"):
|
||||
return amount * 1000
|
||||
return amount
|
||||
|
||||
|
||||
def date_after(label_pattern: str, text: str) -> str | None:
|
||||
match = re.search(
|
||||
label_pattern
|
||||
+ r".{0,600}?(?:on|from|at or before)\s+(?:[A-Z][a-z]+,\s+)?"
|
||||
+ r"([A-Z][a-z]+ \d{1,2}, \d{4}|\d{1,2} [A-Z][a-z]+ \d{4})",
|
||||
text,
|
||||
flags=re.I,
|
||||
)
|
||||
if not match:
|
||||
return None
|
||||
value = match.group(1)
|
||||
for date_format in ["%B %d, %Y", "%d %B %Y"]:
|
||||
try:
|
||||
return datetime.strptime(value, date_format).date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def parse_prospectus_facts(local_path: str) -> ProspectusFacts:
|
||||
text = normalize_pdf_text(first_pdf_text(local_path, 8))
|
||||
board_lot = integer_after(r"minimum\s*of\s*([\d][\d,\s]*)\s*Hong\s*Kong\s*Offer\s*Shares", text)
|
||||
min_amount = None
|
||||
if board_lot:
|
||||
pattern = rf"\b{board_lot:,}\b\s+([\d,]+\.\d{{2}})"
|
||||
min_amount = float_after(pattern, text)
|
||||
if min_amount is None:
|
||||
pattern = rf"\b{board_lot}\b\s+([\d,]+\.\d{{2}})"
|
||||
min_amount = float_after(pattern, text)
|
||||
global_shares = integer_after(r"Number of Offer Shares (?:under|in) the Global Offering\s*:?\s+([\d][\d,\s]*)", text)
|
||||
if global_shares is None:
|
||||
global_shares = integer_after(r"Number of Offer Shares\s*:?\s+([\d][\d,\s]*)\s+(?:H\s+)?Shares", text)
|
||||
hk_shares = integer_after(r"Number of Hong Kong Offer Shares\s*:?\s+([\d][\d,\s]*)", text)
|
||||
intl_shares = integer_after(r"Number of International Offer Shares\s*:?\s+([\d][\d,\s]*)", text)
|
||||
over_allotment = None
|
||||
if global_shares:
|
||||
over_allotment = round(global_shares * 0.15)
|
||||
public_pct = round(hk_shares / global_shares, 4) if global_shares and hk_shares else None
|
||||
allotment_date = (
|
||||
date_after(r"Announcement of.*?Offer Price", text)
|
||||
or date_after(r"Announcement of", text)
|
||||
or date_after(r"The results of allocations", text)
|
||||
)
|
||||
return ProspectusFacts(
|
||||
application_start_date=date_after(r"Hong Kong Public Offering commences", text),
|
||||
application_end_date=date_after(r"Application lists.*?close", text),
|
||||
allotment_results_expected_date=allotment_date,
|
||||
listing_date=date_after(r"Dealings in the (?:H\s+)?Shares.*?expected to commence", text),
|
||||
board_lot=board_lot,
|
||||
min_subscription_amount_hkd=min_amount,
|
||||
global_offer_shares=global_shares,
|
||||
hk_offer_shares_initial=hk_shares,
|
||||
international_offer_shares_initial=intl_shares,
|
||||
public_offer_pct_initial=public_pct,
|
||||
over_allotment_offer_shares=over_allotment,
|
||||
)
|
||||
|
||||
|
||||
def section_between(text: str, start: str, end: str | None, use_last_start: bool = False) -> str:
|
||||
start_matches = list(re.finditer(start, text, flags=re.I))
|
||||
if not start_matches:
|
||||
return ""
|
||||
start_match = start_matches[-1] if use_last_start else start_matches[0]
|
||||
section_start = start_match.end()
|
||||
if not end:
|
||||
return text[section_start:]
|
||||
end_match = re.search(end, text[section_start:], flags=re.I)
|
||||
section_end = section_start + end_match.start() if end_match else len(text)
|
||||
return text[section_start:section_end]
|
||||
|
||||
|
||||
def allotment_detail_sections(text: str) -> tuple[str, str]:
|
||||
hk_match = re.search(
|
||||
r"HONG KONG PUBLIC OFFERING\s+No\. of valid applications(.*?)INTERNATIONAL OFFERING\s+No\. of placees",
|
||||
text,
|
||||
flags=re.I,
|
||||
)
|
||||
intl_match = re.search(
|
||||
r"INTERNATIONAL OFFERING\s+No\. of placees(.*?)(?:The Directors|LOCK-UP|Allottees with|$)",
|
||||
text,
|
||||
flags=re.I,
|
||||
)
|
||||
hk_section = "No. of valid applications" + hk_match.group(1) if hk_match else ""
|
||||
intl_section = "No. of placees" + intl_match.group(1) if intl_match else ""
|
||||
return hk_section, intl_section
|
||||
|
||||
|
||||
def parse_allotment_facts(local_path: str) -> AllotmentFacts:
|
||||
text = first_pdf_text(local_path, 8)
|
||||
hk_section, intl_section = allotment_detail_sections(text)
|
||||
return AllotmentFacts(
|
||||
final_offer_price_hkd=float_after(r"Final Offer Price\s+HK\$([\d,.]+)", text),
|
||||
gross_proceeds_hkd_m=money_m_after(r"Gross proceeds.*?HK\$([\d,.]+)\s*(million|billion)?", text),
|
||||
net_proceeds_hkd_m=money_m_after(r"Net proceeds\s+HK\$([\d,.]+)\s*(million|billion)?", text),
|
||||
issued_shares_upon_listing=integer_after(r"Number of issued shares upon Listing.*?([\d,]+)", text),
|
||||
valid_applications=integer_after(r"No\. of valid applications\s+([\d,]+)", hk_section),
|
||||
successful_applications=integer_after(r"No\. of successful applications\s+([\d,]+)", hk_section),
|
||||
public_oversubscription_times=float_after(r"Subscription level\s+([\d,.]+)\s+times", hk_section),
|
||||
international_placees=integer_after(r"No\. of placees\s+([\d,]+)", intl_section),
|
||||
international_oversubscription_times=float_after(r"Subscription level.*?([\d,.]+)\s+times", intl_section),
|
||||
final_hk_offer_shares=integer_after(
|
||||
r"Final no\. of Offer Shares under the Hong Kong Public Offering.*?([\d][\d,\s]*)",
|
||||
hk_section,
|
||||
),
|
||||
final_international_offer_shares=integer_after(
|
||||
r"Final no\. of Offer Shares under the International Offering.*?([\d][\d,\s]*)",
|
||||
intl_section,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def select_tickers(conn: sqlite3.Connection, limit: int, tickers: str | None) -> list[str]:
|
||||
if tickers:
|
||||
return [ticker.strip().zfill(5) for ticker in tickers.split(",") if ticker.strip()]
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT DISTINCT m.ticker
|
||||
FROM sync_tasks t
|
||||
JOIN ipo_master m ON m.ticker = t.ticker
|
||||
WHERE t.task_status = 'open'
|
||||
AND t.stage IN ('T0_prospectus', 'T1_allotment')
|
||||
ORDER BY m.listing_date DESC, m.ticker
|
||||
LIMIT ?
|
||||
""",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
return [row[0] for row in rows]
|
||||
|
||||
|
||||
def ticker_dates(conn: sqlite3.Connection, ticker: str) -> tuple[str | None, str | None]:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT m.listing_date, r.prospectus_date
|
||||
FROM ipo_master m
|
||||
LEFT JOIN new_listing_report_entries r ON r.ticker = m.ticker
|
||||
WHERE m.ticker = ?
|
||||
ORDER BY r.report_year DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(ticker,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return None, None
|
||||
return row[0], row[1]
|
||||
|
||||
|
||||
def upsert_source_refs(conn: sqlite3.Connection, sources: list[ArchivedSource], as_of: str) -> None:
|
||||
conn.executemany(
|
||||
"""
|
||||
INSERT INTO source_refs (
|
||||
source_id, ticker, source_type, title, path_base, local_path, url,
|
||||
file_sha256, source_date, archived_at, notes
|
||||
)
|
||||
VALUES (?, ?, ?, ?, 'repo_root', ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(source_id) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
local_path = excluded.local_path,
|
||||
url = excluded.url,
|
||||
file_sha256 = excluded.file_sha256,
|
||||
source_date = excluded.source_date,
|
||||
archived_at = excluded.archived_at,
|
||||
notes = excluded.notes
|
||||
""",
|
||||
[
|
||||
(
|
||||
source.source_id,
|
||||
source.ticker,
|
||||
source.source_type,
|
||||
source.title,
|
||||
source.local_path,
|
||||
source.url,
|
||||
source.file_sha256,
|
||||
source.source_date,
|
||||
as_of,
|
||||
source.notes,
|
||||
)
|
||||
for source in sources
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def update_master_from_prospectus(conn: sqlite3.Connection, ticker: str, facts: ProspectusFacts, as_of: str) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE ipo_master
|
||||
SET application_start_date = COALESCE(?, application_start_date),
|
||||
application_end_date = COALESCE(?, application_end_date),
|
||||
allotment_results_expected_date = COALESCE(?, allotment_results_expected_date),
|
||||
listing_date = COALESCE(listing_date, ?),
|
||||
data_as_of = ?
|
||||
WHERE ticker = ?
|
||||
""",
|
||||
(
|
||||
facts.application_start_date,
|
||||
facts.application_end_date,
|
||||
facts.allotment_results_expected_date,
|
||||
facts.listing_date,
|
||||
as_of,
|
||||
ticker,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def update_terms_from_prospectus(
|
||||
conn: sqlite3.Connection,
|
||||
ticker: str,
|
||||
source_id: str,
|
||||
source_date: str,
|
||||
facts: ProspectusFacts,
|
||||
as_of: str,
|
||||
) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO offering_terms (
|
||||
ticker, source_id, prospectus_date, board_lot, min_subscription_amount_hkd,
|
||||
global_offer_shares, hk_offer_shares_initial, international_offer_shares_initial,
|
||||
public_offer_pct_initial, over_allotment_offer_shares, data_as_of
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(ticker) DO UPDATE SET
|
||||
source_id = CASE
|
||||
WHEN offering_terms.source_id LIKE '%_new_listing_report_%'
|
||||
OR offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.source_id
|
||||
ELSE offering_terms.source_id
|
||||
END,
|
||||
prospectus_date = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.prospectus_date
|
||||
ELSE COALESCE(offering_terms.prospectus_date, excluded.prospectus_date)
|
||||
END,
|
||||
board_lot = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.board_lot
|
||||
ELSE COALESCE(offering_terms.board_lot, excluded.board_lot)
|
||||
END,
|
||||
min_subscription_amount_hkd = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.min_subscription_amount_hkd
|
||||
ELSE COALESCE(offering_terms.min_subscription_amount_hkd, excluded.min_subscription_amount_hkd)
|
||||
END,
|
||||
global_offer_shares = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.global_offer_shares
|
||||
ELSE COALESCE(offering_terms.global_offer_shares, excluded.global_offer_shares)
|
||||
END,
|
||||
hk_offer_shares_initial = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.hk_offer_shares_initial
|
||||
ELSE COALESCE(offering_terms.hk_offer_shares_initial, excluded.hk_offer_shares_initial)
|
||||
END,
|
||||
international_offer_shares_initial = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.international_offer_shares_initial
|
||||
ELSE COALESCE(
|
||||
offering_terms.international_offer_shares_initial,
|
||||
excluded.international_offer_shares_initial
|
||||
)
|
||||
END,
|
||||
public_offer_pct_initial = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.public_offer_pct_initial
|
||||
ELSE COALESCE(offering_terms.public_offer_pct_initial, excluded.public_offer_pct_initial)
|
||||
END,
|
||||
over_allotment_offer_shares = CASE
|
||||
WHEN offering_terms.source_id = excluded.source_id
|
||||
OR offering_terms.source_id LIKE offering_terms.ticker || '_prospectus_%'
|
||||
THEN excluded.over_allotment_offer_shares
|
||||
ELSE COALESCE(offering_terms.over_allotment_offer_shares, excluded.over_allotment_offer_shares)
|
||||
END,
|
||||
data_as_of = excluded.data_as_of
|
||||
""",
|
||||
(
|
||||
ticker,
|
||||
source_id,
|
||||
source_date,
|
||||
facts.board_lot,
|
||||
facts.min_subscription_amount_hkd,
|
||||
facts.global_offer_shares,
|
||||
facts.hk_offer_shares_initial,
|
||||
facts.international_offer_shares_initial,
|
||||
facts.public_offer_pct_initial,
|
||||
facts.over_allotment_offer_shares,
|
||||
as_of,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def update_terms_from_allotment(conn: sqlite3.Connection, ticker: str, facts: AllotmentFacts, as_of: str) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE offering_terms
|
||||
SET offer_price_hkd = COALESCE(offer_price_hkd, ?),
|
||||
gross_proceeds_hkd_m = COALESCE(gross_proceeds_hkd_m, ?),
|
||||
net_proceeds_hkd_m = COALESCE(net_proceeds_hkd_m, ?),
|
||||
issued_shares_upon_listing = COALESCE(issued_shares_upon_listing, ?),
|
||||
data_as_of = ?
|
||||
WHERE ticker = ?
|
||||
""",
|
||||
(
|
||||
facts.final_offer_price_hkd,
|
||||
facts.gross_proceeds_hkd_m,
|
||||
facts.net_proceeds_hkd_m,
|
||||
facts.issued_shares_upon_listing,
|
||||
as_of,
|
||||
ticker,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def upsert_demand(conn: sqlite3.Connection, ticker: str, source_id: str, source_date: str, facts: AllotmentFacts, as_of: str) -> None:
|
||||
if not any(
|
||||
[
|
||||
facts.valid_applications,
|
||||
facts.successful_applications,
|
||||
facts.public_oversubscription_times,
|
||||
facts.international_placees,
|
||||
facts.international_oversubscription_times,
|
||||
]
|
||||
):
|
||||
return
|
||||
demand_id = source_id.replace("_allotment_results_", "_allotment_")
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO ipo_demand (
|
||||
demand_id, ticker, source_id, stage_date, valid_applications, successful_applications,
|
||||
public_oversubscription_times, international_placees, international_oversubscription_times,
|
||||
final_hk_offer_shares, final_international_offer_shares, data_as_of, notes
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(demand_id) DO UPDATE SET
|
||||
source_id = excluded.source_id,
|
||||
stage_date = excluded.stage_date,
|
||||
valid_applications = excluded.valid_applications,
|
||||
successful_applications = excluded.successful_applications,
|
||||
public_oversubscription_times = excluded.public_oversubscription_times,
|
||||
international_placees = excluded.international_placees,
|
||||
international_oversubscription_times = excluded.international_oversubscription_times,
|
||||
final_hk_offer_shares = excluded.final_hk_offer_shares,
|
||||
final_international_offer_shares = excluded.final_international_offer_shares,
|
||||
data_as_of = excluded.data_as_of,
|
||||
notes = excluded.notes
|
||||
""",
|
||||
(
|
||||
demand_id,
|
||||
ticker,
|
||||
source_id,
|
||||
source_date,
|
||||
facts.valid_applications,
|
||||
facts.successful_applications,
|
||||
facts.public_oversubscription_times,
|
||||
facts.international_placees,
|
||||
facts.international_oversubscription_times,
|
||||
facts.final_hk_offer_shares,
|
||||
facts.final_international_offer_shares,
|
||||
as_of,
|
||||
"Parsed from HKEXnews allotment results announcement.",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def export_snapshot(conn: sqlite3.Connection, table: str, order_by: str = "1") -> None:
|
||||
SNAPSHOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
cursor = conn.execute(f"SELECT * FROM {table} ORDER BY {order_by}")
|
||||
columns = [description[0] for description in cursor.description]
|
||||
with (SNAPSHOT_DIR / f"{table}.csv").open("w", newline="", encoding="utf-8") as handle:
|
||||
writer = csv.writer(handle, lineterminator="\n")
|
||||
writer.writerow(columns)
|
||||
writer.writerows(cursor.fetchall())
|
||||
|
||||
|
||||
def refresh_sync_state(db_path: str, schema_path: str, as_of: str) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"scripts/update_sync_state.py",
|
||||
"--db",
|
||||
db_path,
|
||||
"--schema",
|
||||
schema_path,
|
||||
"--as-of",
|
||||
as_of,
|
||||
"--mode",
|
||||
"hkex_document_archive",
|
||||
"--summary-limit",
|
||||
"25",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
as_of = parse_as_of(args.as_of)
|
||||
stock_ids = load_stock_ids()
|
||||
archived_sources: list[ArchivedSource] = []
|
||||
processed = 0
|
||||
missing_stock_ids: list[str] = []
|
||||
missing_docs: list[str] = []
|
||||
|
||||
with sqlite3.connect(args.db) as conn:
|
||||
conn.executescript(Path(args.schema).read_text(encoding="utf-8"))
|
||||
tickers = select_tickers(conn, args.limit, args.tickers)
|
||||
for ticker in tickers:
|
||||
stock_id = stock_ids.get(ticker)
|
||||
if stock_id is None:
|
||||
missing_stock_ids.append(ticker)
|
||||
continue
|
||||
rows = title_search_rows(stock_id)
|
||||
listing_date, prospectus_date = ticker_dates(conn, ticker)
|
||||
prospectus_row = choose_prospectus(rows, prospectus_date, listing_date)
|
||||
allotment_row = choose_allotment(rows, listing_date)
|
||||
if not prospectus_row and not allotment_row:
|
||||
missing_docs.append(ticker)
|
||||
continue
|
||||
|
||||
sources_for_ticker: list[ArchivedSource] = []
|
||||
if prospectus_row:
|
||||
prospectus_source = download_document(ticker, "prospectus", prospectus_row)
|
||||
sources_for_ticker.append(prospectus_source)
|
||||
prospectus_facts = parse_prospectus_facts(prospectus_source.local_path)
|
||||
update_master_from_prospectus(conn, ticker, prospectus_facts, as_of)
|
||||
update_terms_from_prospectus(
|
||||
conn,
|
||||
ticker,
|
||||
prospectus_source.source_id,
|
||||
prospectus_source.source_date,
|
||||
prospectus_facts,
|
||||
as_of,
|
||||
)
|
||||
if allotment_row:
|
||||
allotment_source = download_document(ticker, "allotment_results", allotment_row)
|
||||
sources_for_ticker.append(allotment_source)
|
||||
allotment_facts = parse_allotment_facts(allotment_source.local_path)
|
||||
update_terms_from_allotment(conn, ticker, allotment_facts, as_of)
|
||||
upsert_demand(conn, ticker, allotment_source.source_id, allotment_source.source_date, allotment_facts, as_of)
|
||||
|
||||
upsert_source_refs(conn, sources_for_ticker, as_of)
|
||||
archived_sources.extend(sources_for_ticker)
|
||||
processed += 1
|
||||
|
||||
for table in [
|
||||
"ipo_master",
|
||||
"offering_terms",
|
||||
"ipo_demand",
|
||||
"source_refs",
|
||||
"data_gaps",
|
||||
]:
|
||||
export_snapshot(conn, table)
|
||||
|
||||
if not args.skip_sync_state:
|
||||
refresh_sync_state(args.db, args.schema, as_of)
|
||||
|
||||
print("hkex documents archived")
|
||||
print(f"tickers selected: {len(tickers)}")
|
||||
print(f"tickers processed: {processed}")
|
||||
print(f"sources archived: {len(archived_sources)}")
|
||||
if missing_stock_ids:
|
||||
print("missing stock ids: " + ", ".join(missing_stock_ids))
|
||||
if missing_docs:
|
||||
print("missing target docs: " + ", ".join(missing_docs))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user