0. Process
This report explains how we reached the results. The methodology
proceeds in the following steps:
Identify data source. We use the Hong Kong
Exchanges and Clearing Limited (HKEX) Share Repurchase Reports,
published daily at https://www3.hkexnews.hk/reports/sharerepur/sbn.asp
(HKEX Share Repurchase Reports,
n.d.).
Fetch XLS reports. A Python script
(hkex_fetch.py) queries the HKEX calendar by year and month
via HTTP POST, extracts links to daily XLS files (format
SRRPT{YYYYMMDD}.xls), and downloads each file to a local
xls directory. The script skips future dates and
already-downloaded files.
Parse XLS and store in Parquet. Each XLS file is
read with pandas. Data starts at row 4; columns include Company, Stock
code, Security type, Trading date, Shares repurchased, High/Low price,
Aggregate paid, and Method (exchange venue). Values are normalised
(e.g. currency prefixes, date formats). Records are deduplicated and
appended to a single Parquet file.
Generate HTML report. The same script produces
an HTML table grouped by stock code, with each company’s repurchase
history in descending date order. This HTML serves as a human-readable
summary.
Statistical analysis. A second Python script
(analyze_repurchases.py) loads the Parquet (or parses the
HTML if Parquet is unavailable) and runs seven analysis dimensions:
overview, stock-level aggregates, temporal patterns, price analysis,
concentration, method breakdown, and recent activity.
Produce results. The analysis output is
displayed in this report. Full source code for both scripts is provided
in the appendices.
1. Overview
The data in the Overview table (and throughout this report) were
generated by fetching HKEX share repurchase reports with the following
command, run from the hkexsharerepurchase directory:
python hkex_fetch.py --years 2019,2020,2021,2022,2023,2024,2025,2026
This downloads XLS reports for the specified years, parses them into
Parquet and HTML, and produces the dataset summarised below.
| Dataset overview |
| Metric |
Value |
| Total records |
55,211 |
| Unique stocks |
699 |
| Total shares bought |
62,724,506,717 |
| Total aggregate (HKD) |
778,720,818,392 |
2. Top Stocks by Spend
| Top 15 stocks by aggregate spend |
| HKD amounts |
| Stock |
Company |
Aggregate (HKD) |
Shares |
Transactions |
| 0700 |
TENCENT |
285,348,521,537 |
739,215,700 |
556 |
| 0005 |
HSBC HOLDINGS |
117,928,596,926 |
3,421,421,365 |
3532 |
| 1299 |
AIA |
106,396,873,846 |
1,603,848,400 |
684 |
| 1810 |
XIAOMI-W |
30,671,724,844 |
1,615,066,800 |
342 |
| 3690 |
MEITUAN-W |
28,549,326,005 |
264,415,400 |
68 |
| 0300 |
MIDEA GROUP |
12,355,822,102 |
166,538,456 |
155 |
| 2318 |
PING AN |
10,993,766,541 |
172,599,415 |
35 |
| 2269 |
WUXI BIO |
10,899,370,490 |
339,780,000 |
78 |
| 0386 |
SINOPEC CORP |
10,754,925,442 |
2,536,133,671 |
293 |
| 1919 |
COSCO SHIP HOLD |
10,389,017,942 |
865,819,688 |
319 |
| 2333 |
GREATWALL MOTOR |
9,672,229,805 |
820,764,043 |
67 |
| 1024 |
KUAISHOU-W |
9,383,126,830 |
189,519,600 |
250 |
| 6690 |
HAIER SMARTHOME |
8,477,751,992 |
331,818,797 |
419 |
| 2888 |
STANCHART |
8,034,332,965 |
1,054,529,523 |
1830 |
| 0019 |
SWIRE PACIFIC A |
7,882,187,565 |
126,218,000 |
444 |
3. Temporal by Year
| Repurchase activity by year |
| Shares, aggregate HKD, transactions, unique stocks |
| Year |
Shares |
Aggregate (HKD) |
Txns |
Stocks |
| 2018 |
10,801,000 |
102,494,198 |
25 |
23 |
| 2019 |
9,133,145,085 |
31,497,854,192 |
4,586 |
204 |
| 2020 |
5,396,345,567 |
21,506,546,841 |
4,267 |
184 |
| 2021 |
5,550,351,038 |
48,202,987,331 |
4,829 |
194 |
| 2022 |
10,174,859,093 |
121,426,437,362 |
7,631 |
244 |
| 2023 |
8,969,381,985 |
138,699,779,333 |
8,351 |
219 |
| 2024 |
11,970,970,481 |
285,168,927,107 |
12,422 |
305 |
| 2025 |
10,573,067,066 |
222,972,489,400 |
11,285 |
300 |
| 2026 |
945,585,402 |
23,607,672,691 |
1,815 |
144 |
4. Price Analysis
| Price statistics |
| Metric |
Value |
| Average price (overall) |
18.31 |
| Median price |
4.61 |
| Min price |
0.01 |
| Max price |
676.25 |
5. Concentration
| Market concentration |
| Metric |
Value |
| Top 10 stocks (% of shares) |
18.7% |
| Top 10 stocks (% of aggregate spend) |
69.9% |
6. Method Breakdown
| Top 10 venues by aggregate spend |
| Exchange / method |
| Venue |
Transactions |
Shares |
Aggregate (HKD) |
| Exchange |
44453 |
48,412,789,369 |
758,728,761,344 |
| Shanghai Stock Exchange |
1402 |
3,401,705,102 |
42,528,922,692 |
| Shenzhen Stock Exchange |
704 |
1,602,854,352 |
35,971,266,390 |
| London Stock Exchange |
1977 |
2,287,083,720 |
23,208,763,644 |
| New York Stock Exchange |
2199 |
990,244,306 |
10,243,523,913 |
| By private arrangement |
43 |
1,205,660,567 |
7,474,956,905 |
| By general offer |
1 |
841,749,304 |
3,392,249,695 |
| CBOE Europe – CXE |
466 |
305,916,840 |
2,088,605,675 |
| Private Arrangement |
3 |
1,247,590,736 |
1,314,008,779 |
| CBOE BXE |
469 |
120,060,257 |
1,023,390,974 |
7. Recent Activity (90d)
| Recent activity (last 90 days) |
| Metric |
Value |
| Records |
2,585 |
| Total shares |
1,426,151,718 |
| Unique stocks |
172 |
Appendix 1: XLS Fetch Script
The following Python script fetches share repurchase XLS reports from
HKEX, parses them, stores data in Parquet, and generates the HTML
report.
#!/usr/bin/env python3
"""
HKEX Share Repurchase Report Fetcher
Fetches share repurchase reports from HKEX, downloads XLS files, parses repurchase
details for each stock, stores data in Parquet, and generates an HTML table.
Source: https://www3.hkexnews.hk/reports/sharerepur/sbn.asp
"""
import re
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urljoin
import pandas as pd
import requests
from bs4 import BeautifulSoup
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
BASE_URL = "https://www3.hkexnews.hk/reports/sharerepur"
INDEX_URL = f"{BASE_URL}/sbn.asp"
DOCUMENTS_BASE = f"{BASE_URL}/documents"
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
SCRIPT_DIR = Path(__file__).parent.resolve()
OUTPUT_DIR = SCRIPT_DIR / "hkexsharerepurchasefolder"
XLS_DIR = OUTPUT_DIR / "xls"
PARQUET_PATH = OUTPUT_DIR / "hkex_repurchases.parquet"
HTML_PATH = OUTPUT_DIR / "hkex_repurchases.html"
SESSION = requests.Session()
SESSION.headers.update({"User-Agent": USER_AGENT})
def fetch_report_links(years=None, skip_future=True):
"""Fetch all XLS report links from HKEX share repurchase calendar."""
if years is None:
years = list(range(2003, datetime.now().year + 1))
links = []
seen_dates = set()
for year, month in ((y, m) for y in years for m in range(1, 13)):
try:
resp = SESSION.post(INDEX_URL, data={"y": year, "m": month}, timeout=30)
resp.raise_for_status()
except Exception as e:
continue
for match in re.finditer(r"SRRPT(\d{8})\.xls", resp.text):
datestr = match.group(1)
if datestr in seen_dates:
continue
seen_dates.add(datestr)
if skip_future:
try:
if datetime.strptime(datestr, "%Y%m%d").date() > datetime.now().date():
continue
except ValueError:
pass
links.append((datestr, f"{DOCUMENTS_BASE}/SRRPT{datestr}.xls"))
time.sleep(0.2)
return sorted(links, key=lambda x: x[0], reverse=True)
def download_report(datestr, url):
"""Download an XLS report to the xls folder."""
out_path = XLS_DIR / f"SRRPT{datestr}.xls"
out_path.parent.mkdir(parents=True, exist_ok=True)
if out_path.exists():
return out_path, False
try:
resp = SESSION.get(url, timeout=30)
resp.raise_for_status()
if "html" in resp.headers.get("Content-Type", "").lower() or len(resp.content) < 500:
return None, False
out_path.write_bytes(resp.content)
return out_path, True
except Exception:
return None, False
def _parse_value(s):
"""Parse 'HKD 270,040.00' to (value, currency)."""
if s is None or (isinstance(s, float) and pd.isna(s)):
return None, None
s = str(s).strip()
for prefix in ["HKD", "USD", "GBP", "RMB"]:
if prefix.upper() in s.upper():
try:
return float(s.upper().replace(prefix, "").replace(",", "").strip()), prefix
except ValueError:
pass
try:
return float(str(s).replace(",", "").strip()), None
except ValueError:
return None, None
def _parse_shares(s):
if s is None or (isinstance(s, float) and pd.isna(s)):
return 0
try:
return int(float(str(s).replace(",", "").replace(" ", "").strip()))
except (ValueError, TypeError):
return 0
def parse_xls_report(path, report_date):
"""Parse an HKEX XLS report and extract repurchase rows."""
rows = []
try:
df = pd.read_excel(path, header=None)
except Exception:
return rows
for i in range(4, len(df)):
row = df.iloc[i]
company = str(row[0]).strip() if pd.notna(row[0]) else ""
code_raw = str(row[1]).strip() if pd.notna(row[1]) else ""
if not code_raw or code_raw == "nan":
continue
code = code_raw.replace(".", "").strip()
code_display = code.zfill(4) if code.isdigit() else code
trading_date = row[3]
if pd.isna(trading_date):
continue
shares = _parse_shares(row[4])
if shares <= 0:
continue
high_val, high_ccy = _parse_value(row[5])
low_val, low_ccy = _parse_value(row[6])
agg_val, agg_ccy = _parse_value(row[7])
method = str(row[8]).strip() if pd.notna(row[8]) else ""
avg = (high_val + low_val) / 2 if (high_val and low_val) else (agg_val / shares if agg_val and shares else None)
total = agg_val if agg_val else (shares * avg if avg else None)
currency = agg_ccy or high_ccy or low_ccy or "HKD"
if hasattr(trading_date, "strftime"):
date_str = trading_date.strftime("%Y-%m-%d")
else:
date_str = str(trading_date)
rows.append({
"company": company, "stock_code": code_display, "sec_type": str(row[2]).strip() if pd.notna(row[2]) else "",
"trading_date": date_str, "report_date": report_date, "shares_repurchased": shares,
"high_price": high_val, "low_price": low_val, "avg_price": round(avg, 4) if avg else None,
"aggregate_paid": round(total, 2) if total else None, "currency": currency, "method": method,
})
return rows
def append_and_save(new_rows):
"""Append new rows to parquet, deduplicate, and save."""
if not new_rows:
return pd.read_parquet(PARQUET_PATH) if PARQUET_PATH.exists() else pd.DataFrame()
new_df = pd.DataFrame(new_rows)
existing = pd.read_parquet(PARQUET_PATH) if PARQUET_PATH.exists() else pd.DataFrame()
combined = pd.concat([existing, new_df], ignore_index=True) if not existing.empty else new_df
combined = combined.drop_duplicates(subset=["stock_code", "trading_date", "report_date", "shares_repurchased"], keep="first")
combined = combined.sort_values(["stock_code", "trading_date"], ascending=[True, False])
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
combined.to_parquet(PARQUET_PATH, index=False)
return combined
def generate_html_report(df):
"""Generate HTML table with repurchase history by stock code."""
if df.empty:
HTML_PATH.write_text("<!DOCTYPE html><html><body><p>No data.</p></body></html>", encoding="utf-8")
return HTML_PATH
df_sorted = df.sort_values(["stock_code", "trading_date"], ascending=[True, False])
html_parts = ["<!DOCTYPE html>", "<html><head><meta charset='utf-8'><title>HKEX Share Repurchases</title></head><body>",
"<h1>HKEX Share Repurchase History</h1>", f"<p>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>"]
for stock_code, grp in df_sorted.groupby("stock_code"):
company = grp['company'].iloc[0]
html_parts.append(f"<div class='stock-section'><h2>Stock {stock_code} – {company}</h2><table><thead><tr>")
html_parts.append("<th>Trading Date</th><th>Report Date</th><th>Shares</th><th>High</th><th>Low</th><th>Avg</th><th>Aggregate</th><th>Currency</th><th>Method</th></tr></thead><tbody>")
for _, r in grp.iterrows():
html_parts.append(f"<tr><td>{r['trading_date']}</td><td>{r.get('report_date')}</td><td>{r.get('shares_repurchased'):,}</td><td>{r.get('high_price')}</td><td>{r.get('low_price')}</td><td>{r.get('avg_price')}</td><td>{r.get('aggregate_paid'):,}</td><td>{r.get('currency')}</td><td>{r.get('method')}</td></tr>")
html_parts.append("</tbody></table></div>")
html_parts.append("</body></html>")
HTML_PATH.write_text("\n".join(html_parts), encoding="utf-8")
return HTML_PATH
def main(years=None, limit=None, skip_download=False):
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
XLS_DIR.mkdir(parents=True, exist_ok=True)
if skip_download:
xls_files = sorted(XLS_DIR.glob("SRRPT*.xls"), reverse=True)[:limit or 9999]
report_links = [(m.group(1), str(p)) for p in xls_files for m in [re.match(r"SRRPT(\d{8})\.xls", p.name)] if m]
else:
report_links = fetch_report_links(years=years)
if limit:
report_links = report_links[:limit]
to_download = [(d, u) for d, u in report_links if not (XLS_DIR / f"SRRPT{d}.xls").exists()]
for datestr, url in to_download:
download_report(datestr, url)
time.sleep(0.3)
xls_files = sorted(XLS_DIR.glob("SRRPT*.xls"), reverse=True)[:limit or 9999]
all_rows = []
for path in xls_files:
m = re.match(r"SRRPT(\d{8})\.xls", path.name)
if m:
all_rows.extend(parse_xls_report(path, m.group(1)))
df = append_and_save(all_rows)
generate_html_report(df)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--years", type=str)
parser.add_argument("--limit", type=int)
parser.add_argument("--skip-download", action="store_true")
args = parser.parse_args()
years = [int(y) for y in args.years.split(",")] if args.years else None
main(years=years, limit=args.limit, skip_download=args.skip_download)
Appendix 2: Analysis Script
The following Python script loads the Parquet (or HTML) data and runs
the seven analysis dimensions.
#!/usr/bin/env python3
"""
HKEX Share Repurchase Data Analyzer
Analyzes the share repurchase HTML report (or Parquet) generated by hkex_fetch.py.
"""
import argparse
import re
from pathlib import Path
from typing import Optional
import pandas as pd
from bs4 import BeautifulSoup
SCRIPT_DIR = Path(__file__).parent.resolve()
DEFAULT_HTML = SCRIPT_DIR / "hkexsharerepurchasefolder" / "hkex_repurchases.html"
DEFAULT_PARQUET = SCRIPT_DIR / "hkexsharerepurchasefolder" / "hkex_repurchases.parquet"
def _parse_num(s):
if not s or not str(s).strip():
return None
try:
return float(str(s).replace(",", "").replace(" ", "").strip())
except (ValueError, TypeError):
return None
def load_from_html(html_path):
soup = BeautifulSoup(html_path.read_text(encoding="utf-8"), "lxml")
rows = []
for section in soup.find_all("div", class_="stock-section"):
h2 = section.find("h2")
if not h2:
continue
match = re.match(r"Stock\s+(\S+)\s*[–\-]\s*(.+)", h2.get_text(strip=True))
stock_code = match.group(1) if match else ""
company = match.group(2).strip() if match and match.lastindex >= 2 else ""
table = section.find("table")
if not table or not table.find("tbody"):
continue
for tr in table.find("tbody").find_all("tr"):
cells = [td.get_text(strip=True) for td in tr.find_all("td")]
if len(cells) < 9:
continue
shares = _parse_num(cells[2])
rows.append({
"stock_code": stock_code, "company": company, "trading_date": cells[0], "report_date": cells[1],
"shares_repurchased": int(shares) if shares else 0, "high_price": _parse_num(cells[3]),
"low_price": _parse_num(cells[4]), "avg_price": _parse_num(cells[5]),
"aggregate_paid": _parse_num(cells[6]), "currency": cells[7], "method": cells[8],
})
return pd.DataFrame(rows)
def load_data(html_path=None, parquet_path=None):
parquet_path = parquet_path or DEFAULT_PARQUET
html_path = html_path or DEFAULT_HTML
if parquet_path.exists():
return pd.read_parquet(parquet_path)
if html_path.exists():
return load_from_html(html_path)
raise FileNotFoundError("Neither Parquet nor HTML found.")
def run_overview(df):
df = df.copy()
df["trading_date"] = pd.to_datetime(df["trading_date"], errors="coerce")
valid = df["trading_date"].notna()
return {
"total_records": len(df), "unique_stocks": df["stock_code"].nunique(),
"date_range": (str(df.loc[valid, "trading_date"].min().date()), str(df.loc[valid, "trading_date"].max().date())),
"total_shares": int(df["shares_repurchased"].sum()),
"total_aggregate_hkd": float(df.loc[df["currency"] == "HKD", "aggregate_paid"].sum()),
"currencies": df["currency"].value_counts().to_dict(), "methods": df["method"].value_counts().to_dict(),
}
def run_stock_level(df):
return df.groupby(["stock_code", "company"]).agg(
total_shares=("shares_repurchased", "sum"), total_aggregate=("aggregate_paid", "sum"),
transaction_count=("trading_date", "count"), date_first=("trading_date", "min"), date_last=("trading_date", "max"),
).reset_index().sort_values("total_aggregate", ascending=False)
def run_temporal(df):
df = df.copy()
df["trading_date"] = pd.to_datetime(df["trading_date"], errors="coerce")
df = df.dropna(subset=["trading_date"])
df["year"] = df["trading_date"].dt.year
return df.groupby("year").agg(
shares=("shares_repurchased", "sum"), aggregate=("aggregate_paid", "sum"),
transactions=("trading_date", "count"), stocks=("stock_code", "nunique"),
).reset_index()
def run_price_analysis(df):
df = df[df["avg_price"].notna() & (df["avg_price"] > 0)]
if df.empty:
return {"message": "No valid price data"}
return {
"avg_price_overall": float(df["avg_price"].mean()), "median_price": float(df["avg_price"].median()),
"price_range": (float(df["avg_price"].min()), float(df["avg_price"].max())),
"avg_spread_pct": float(((df["high_price"] - df["low_price"]) / df["avg_price"] * 100).mean()) if "high_price" in df.columns else None,
}
def run_concentration(df):
total_shares = df["shares_repurchased"].sum()
total_agg = df["aggregate_paid"].sum()
by_stock = df.groupby("stock_code").agg(shares=("shares_repurchased", "sum"), aggregate=("aggregate_paid", "sum")).reset_index()
by_stock["pct_shares"] = (by_stock["shares"] / total_shares * 100).round(2)
by_stock["pct_aggregate"] = (by_stock["aggregate"] / total_agg * 100).round(2)
by_stock = by_stock.sort_values("aggregate", ascending=False)
return {
"top10_stocks_pct_shares": float(by_stock.head(10)["pct_shares"].sum()),
"top10_stocks_pct_aggregate": float(by_stock.head(10)["pct_aggregate"].sum()),
}
def run_method_breakdown(df):
return df.groupby("method").agg(count=("trading_date", "count"), shares=("shares_repurchased", "sum"), aggregate=("aggregate_paid", "sum")).reset_index()
def run_recent_activity(df, days=90):
df = df.copy()
df["trading_date"] = pd.to_datetime(df["trading_date"], errors="coerce")
cutoff = pd.Timestamp.now() - pd.Timedelta(days=days)
return df[df["trading_date"] >= cutoff]
def print_report(overview, stock_df, temporal_df, price_info, concentration, method_df, recent_df):
print("\n" + "=" * 70)
print("HKEX SHARE REPURCHASE DATA – STRUCTURED INSIGHT REPORT")
print("=" * 70)
print("\n--- 1. OVERVIEW ---")
print(f" Total records: {overview['total_records']:,}, Unique stocks: {overview['unique_stocks']:,}")
print(f" Date range: {overview['date_range'][0]} → {overview['date_range'][1]}")
print(f" Total shares: {overview['total_shares']:,}, Aggregate HKD: {overview['total_aggregate_hkd']:,.0f}")
print("\n--- 2. TOP STOCKS ---")
for _, r in stock_df.head(15).iterrows():
print(f" {r['stock_code']:>6} {r['company'][:40]:<40} {r['total_aggregate']:>15,.0f} {r['total_shares']:>12,} shares")
print("\n--- 3. TEMPORAL ---")
for _, r in temporal_df.tail(10).iterrows():
print(f" {int(r['year'])}: {r['shares']:>15,} shares {r['aggregate']:>15,.0f} HKD {r['transactions']:>6} txns")
print("\n--- 4. PRICE ---")
if "message" not in price_info:
print(f" Avg: {price_info['avg_price_overall']:.2f}, Median: {price_info['median_price']:.2f}")
print("\n--- 5. CONCENTRATION ---")
print(f" Top 10: {concentration['top10_stocks_pct_shares']:.1f}% shares, {concentration['top10_stocks_pct_aggregate']:.1f}% aggregate")
print("\n--- 6. METHOD ---")
for _, r in method_df.iterrows():
print(f" {str(r['method'])[:20]:<20} {r['count']:>8} txns {r['shares']:>15,} shares")
print("\n--- 7. RECENT (90d) ---")
print(f" Records: {len(recent_df):,}, Shares: {recent_df['shares_repurchased'].sum():,}, Stocks: {recent_df['stock_code'].nunique()}")
print("=" * 70)
def main(html_path=None, parquet_path=None, output_dir=None, save_csv=True):
df = load_data(html_path=html_path, parquet_path=parquet_path)
if df.empty:
return
overview = run_overview(df)
stock_df = run_stock_level(df)
temporal_df = run_temporal(df)
price_info = run_price_analysis(df)
concentration = run_concentration(df)
method_df = run_method_breakdown(df)
recent_df = run_recent_activity(df, days=90)
print_report(overview, stock_df, temporal_df, price_info, concentration, method_df, recent_df)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--html", type=Path)
parser.add_argument("--parquet", type=Path)
parser.add_argument("--output", type=Path)
parser.add_argument("--no-csv", action="store_true")
args = parser.parse_args()
main(html_path=args.html, parquet_path=args.parquet, output_dir=args.output, save_csv=not args.no_csv)
Appendix 3: Top 100 Stocks
| Top 100 stocks by aggregate spend |
| HKD amounts |
| Stock |
Company |
Aggregate (HKD) |
Shares |
Transactions |
| 0700 |
TENCENT |
285,348,521,537 |
739,215,700 |
556 |
| 0005 |
HSBC HOLDINGS |
117,928,596,926 |
3,421,421,365 |
3532 |
| 1299 |
AIA |
106,396,873,846 |
1,603,848,400 |
684 |
| 1810 |
XIAOMI-W |
30,671,724,844 |
1,615,066,800 |
342 |
| 3690 |
MEITUAN-W |
28,549,326,005 |
264,415,400 |
68 |
| 0300 |
MIDEA GROUP |
12,355,822,102 |
166,538,456 |
155 |
| 2318 |
PING AN |
10,993,766,541 |
172,599,415 |
35 |
| 2269 |
WUXI BIO |
10,899,370,490 |
339,780,000 |
78 |
| 0386 |
SINOPEC CORP |
10,754,925,442 |
2,536,133,671 |
293 |
| 1919 |
COSCO SHIP HOLD |
10,389,017,942 |
865,819,688 |
319 |
| 2333 |
GREATWALL MOTOR |
9,672,229,805 |
820,764,043 |
67 |
| 1024 |
KUAISHOU-W |
9,383,126,830 |
189,519,600 |
250 |
| 6690 |
HAIER SMARTHOME |
8,477,751,992 |
331,818,797 |
419 |
| 2888 |
STANCHART |
8,034,332,965 |
1,054,529,523 |
1830 |
| 0019 |
SWIRE PACIFIC A |
7,882,187,565 |
126,218,000 |
444 |
| 9987 |
YUM CHINA |
6,415,775,744 |
70,873,492 |
1306 |
| 1378 |
CHINAHONGQIAO |
6,191,566,097 |
410,864,500 |
84 |
| 1113 |
CK ASSET |
6,148,507,143 |
146,955,500 |
147 |
| 3333 |
EVERGRANDE |
5,185,965,770 |
322,249,000 |
50 |
| 1157 |
ZOOMLION |
4,962,098,046 |
852,033,899 |
121 |
| 0011 |
HANG SENG BANK |
4,074,116,441 |
38,905,200 |
149 |
| 9988 |
BABA-W |
3,994,654,953 |
341,487,472 |
198 |
| 1821 |
ESR |
3,919,103,942 |
290,950,200 |
381 |
| 2057 |
ZTO EXPRESS-W |
3,887,050,422 |
47,216,846 |
78 |
| 0656 |
FOSUN INTL |
3,843,397,063 |
520,702,000 |
325 |
| 0189 |
DONGYUE GROUP |
3,731,992,946 |
523,977,818 |
6 |
| 0151 |
WANT WANT CHINA |
3,495,940,144 |
646,216,000 |
262 |
| 3323 |
CNBM |
3,392,249,695 |
841,749,304 |
1 |
| 0384 |
CHINA GAS HOLD |
3,045,876,893 |
176,415,400 |
94 |
| 2020 |
ANTA SPORTS |
2,961,123,608 |
35,970,200 |
28 |
| 3750 |
CATL |
2,834,694,717 |
9,349,796 |
8 |
| 1044 |
HENGAN INT'L |
2,741,780,303 |
33,856,000 |
64 |
| 2423 |
BEKE-W |
2,639,454,764 |
493,937,658 |
564 |
| 1093 |
CSPC PHARMA |
2,591,840,200 |
476,022,000 |
76 |
| 6936 |
SF HOLDING |
2,500,704,805 |
64,308,189 |
39 |
| 1513 |
LIVZON PHARMA |
2,397,540,432 |
70,435,307 |
249 |
| 1211 |
BYD COMPANY |
2,209,695,766 |
7,388,024 |
6 |
| 6821 |
ASYMCHEM |
2,061,734,696 |
19,603,945 |
47 |
| 0017 |
NEW WORLD DEV |
2,025,424,570 |
114,764,000 |
65 |
| 0175 |
GEELY AUTO |
1,875,637,334 |
112,782,000 |
47 |
| 2611 |
GTJA |
1,839,789,555 |
107,701,790 |
30 |
| 2338 |
WEICHAI POWER |
1,819,041,458 |
141,498,000 |
43 |
| 2378 |
PRU |
1,781,955,242 |
226,315,168 |
425 |
| 3898 |
TIMES ELECTRIC |
1,766,368,027 |
58,572,400 |
72 |
| 0354 |
CHINASOFT INT'L |
1,755,218,941 |
364,828,000 |
123 |
| 6186 |
CHINA FEIHE |
1,719,254,743 |
315,866,000 |
94 |
| 1929 |
CHOW TAI FOOK |
1,707,069,474 |
135,055,400 |
15 |
| 2018 |
AAC TECH |
1,690,943,268 |
49,029,500 |
140 |
| 6886 |
HTSC |
1,690,738,771 |
92,849,054 |
57 |
| 2319 |
MENGNIU DAIRY |
1,659,242,623 |
83,259,000 |
234 |
| 6098 |
CG SERVICES |
1,656,153,149 |
133,602,000 |
128 |
| 1910 |
SAMSONITE |
1,629,582,061 |
83,106,000 |
87 |
| 9992 |
POP MART |
1,539,805,160 |
60,894,400 |
111 |
| 3888 |
KINGSOFT |
1,508,062,645 |
61,083,800 |
107 |
| 2899 |
ZIJIN MINING |
1,499,677,507 |
106,516,000 |
10 |
| 0754 |
HOPSON DEV HOLD |
1,498,249,860 |
67,595,600 |
81 |
| 1972 |
SWIREPROPERTIES |
1,456,942,911 |
92,515,200 |
163 |
| 0001 |
CKH HOLDINGS |
1,431,074,200 |
26,196,000 |
65 |
| 0883 |
CNOOC |
1,419,907,270 |
107,502,000 |
15 |
| 3993 |
CMOC |
1,377,788,549 |
253,443,694 |
8 |
| 3347 |
TIGERMED |
1,373,919,904 |
17,438,080 |
52 |
| 0751 |
SKYWORTH GROUP |
1,351,245,625 |
388,462,000 |
142 |
| 0087 |
SWIRE PACIFIC B |
1,312,457,000 |
132,937,500 |
482 |
| 2202 |
CHINA VANKE |
1,291,541,933 |
72,955,992 |
18 |
| 2096 |
SIMCERE PHARMA |
1,272,386,964 |
200,199,000 |
175 |
| 0489 |
DONGFENG GROUP |
1,267,882,158 |
371,829,000 |
93 |
| 0881 |
ZHONGSHENG HLDG |
1,262,366,128 |
54,590,500 |
91 |
| 1530 |
3SBIO |
1,254,766,400 |
178,037,087 |
53 |
| 1038 |
CKI HOLDINGS |
1,158,328,020 |
131,065,097 |
1 |
| 2039 |
CIMC |
1,154,221,728 |
146,179,540 |
99 |
| 0868 |
XINYI GLASS |
1,153,314,900 |
54,472,000 |
19 |
| 2382 |
SUNNY OPTICAL |
1,097,443,390 |
17,884,900 |
21 |
| 0941 |
CHINA MOBILE |
1,071,071,012 |
18,529,000 |
16 |
| 2331 |
LI NING |
1,053,850,613 |
51,667,500 |
13 |
| 6618 |
JD HEALTH |
1,001,993,906 |
22,702,000 |
45 |
| 2238 |
GAC GROUP |
997,069,007 |
307,919,698 |
49 |
| 0598 |
SINOTRANS |
996,891,213 |
269,341,070 |
150 |
| 2282 |
MGM CHINA |
973,490,182 |
67,752,300 |
97 |
| 6608 |
BAIRONG-W |
961,895,088 |
90,970,000 |
306 |
| 9959 |
LINKLOGIS-W |
951,169,545 |
338,257,000 |
224 |
| 0772 |
CHINA LIT |
938,005,615 |
38,173,600 |
187 |
| 6826 |
HAOHAI BIOTEC |
935,387,090 |
20,525,775 |
284 |
| 2611 |
GTHT |
930,942,641 |
51,375,898 |
27 |
| 9896 |
MNSO |
906,370,011 |
37,780,368 |
347 |
| 0268 |
KINGDEE INT'L |
886,827,270 |
108,435,800 |
76 |
| 0799 |
IGG |
863,549,970 |
151,192,000 |
275 |
| 2866 |
COSCO SHIP DEV |
847,265,407 |
518,843,803 |
221 |
| 2048 |
E-HOUSE ENT |
844,272,936 |
85,377,500 |
67 |
| 0182 |
CONCORD NE |
844,078,000 |
1,472,090,000 |
303 |
| 0023 |
BANK OF E ASIA |
842,230,578 |
86,287,800 |
320 |
| 1999 |
MAN WAH HLDGS |
832,158,657 |
123,690,800 |
52 |
| 0697 |
SHOUCHENG |
801,402,073 |
425,142,000 |
227 |
| 9626 |
BILIBILI-W |
799,250,094 |
6,427,307 |
8 |
| 1337 |
RAZER |
797,885,245 |
410,276,000 |
122 |
| 2273 |
GUSHENGTANG |
794,613,016 |
24,298,400 |
155 |
| 0909 |
MING YUAN CLOUD |
789,480,535 |
176,719,000 |
170 |
| 1276 |
HENGRUI PHARMA |
765,908,156 |
11,947,440 |
32 |
| 1833 |
PA GOODDOCTOR |
727,993,935 |
28,481,300 |
37 |
| 6837 |
HAITONG SEC |
717,361,719 |
77,074,467 |
33 |
| 0669 |
TECHTRONIC IND |
683,218,225 |
7,850,000 |
27 |
Appendix 4: Swire Pacific by Month
| Swire Pacific – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
0 |
0 |
0 |
0 |
797,500 |
8,390,500 |
4,821,500 |
0 |
| February |
0 |
0 |
0 |
0 |
0 |
0 |
7,279,000 |
8,453,500 |
0 |
| March |
0 |
0 |
0 |
0 |
0 |
10,928,000 |
10,597,500 |
12,052,000 |
0 |
| April |
0 |
0 |
0 |
0 |
0 |
9,700,500 |
9,372,500 |
10,776,000 |
0 |
| May |
0 |
0 |
0 |
0 |
0 |
2,680,000 |
11,876,000 |
4,418,500 |
0 |
| June |
0 |
0 |
0 |
0 |
0 |
0 |
7,977,000 |
0 |
0 |
| July |
0 |
0 |
0 |
0 |
0 |
0 |
6,658,000 |
0 |
0 |
| August |
0 |
0 |
0 |
0 |
12,905,000 |
0 |
9,657,000 |
0 |
0 |
| September |
0 |
0 |
0 |
0 |
27,352,000 |
0 |
7,967,500 |
0 |
0 |
| October |
0 |
0 |
0 |
0 |
21,677,500 |
0 |
12,569,500 |
0 |
0 |
| November |
0 |
0 |
0 |
0 |
13,682,000 |
0 |
6,196,500 |
0 |
0 |
| December |
0 |
0 |
0 |
0 |
8,191,500 |
6,081,500 |
6,097,500 |
0 |
0 |
| Total |
0 |
0 |
0 |
0 |
83,808,000 |
30,187,500 |
104,638,500 |
40,521,500 |
0 |
Appendix 5: Hysan Development (0014) by Month
| Hysan Development – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| February |
0 |
0 |
0 |
0 |
1,050,000 |
0 |
0 |
0 |
0 |
| March |
0 |
0 |
1,700,000 |
0 |
950,000 |
0 |
0 |
0 |
0 |
| April |
0 |
0 |
0 |
0 |
1,500,000 |
0 |
0 |
0 |
0 |
| May |
0 |
0 |
0 |
0 |
500,000 |
0 |
0 |
0 |
0 |
| June |
0 |
0 |
0 |
100,000 |
1,400,000 |
0 |
0 |
0 |
0 |
| July |
0 |
0 |
0 |
0 |
150,000 |
0 |
0 |
0 |
0 |
| August |
0 |
50,000 |
0 |
1,600,000 |
500,000 |
0 |
0 |
0 |
0 |
| September |
0 |
250,000 |
0 |
1,700,000 |
600,000 |
0 |
0 |
0 |
0 |
| October |
0 |
1,550,000 |
2,200,000 |
1,500,000 |
850,000 |
0 |
0 |
0 |
0 |
| November |
0 |
400,000 |
0 |
600,000 |
0 |
0 |
0 |
0 |
0 |
| December |
0 |
750,000 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| Total |
0 |
3,000,000 |
3,900,000 |
5,500,000 |
7,500,000 |
0 |
0 |
0 |
0 |
Appendix 6: Sino Land (0083) by Month
## [1] "No Sino Land data found in the dataset."
Appendix 7: Sun Hung Kai Properties (0016) by Month
## [1] "No Sun Hung Kai Properties (0016) data found in the dataset."
Appendix 8: CKH Holdings (0001) by Month
| CKH Holdings (0001) – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| February |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| March |
0 |
0 |
0 |
200,000 |
50,000 |
0 |
0 |
0 |
0 |
| April |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| May |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| June |
0 |
0 |
0 |
7,630,500 |
0 |
0 |
0 |
0 |
0 |
| July |
0 |
0 |
0 |
117,000 |
0 |
0 |
0 |
0 |
0 |
| August |
0 |
0 |
0 |
7,871,000 |
0 |
0 |
0 |
0 |
0 |
| September |
0 |
0 |
0 |
3,894,000 |
0 |
0 |
0 |
0 |
0 |
| October |
0 |
0 |
0 |
267,500 |
0 |
0 |
0 |
0 |
0 |
| November |
0 |
0 |
0 |
543,500 |
1,990,000 |
0 |
0 |
0 |
0 |
| December |
0 |
0 |
0 |
1,182,500 |
2,450,000 |
0 |
0 |
0 |
0 |
| Total |
0 |
0 |
0 |
21,706,000 |
4,490,000 |
0 |
0 |
0 |
0 |
Appendix 9: Henderson Land (0012) by Month
## [1] "No Henderson Land (0012) data found in the dataset."
Appendix 10: New World Development (0017) by Month
| New World Development (0017) – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
1,308,000 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| February |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| March |
0 |
0 |
42,767,000 |
0 |
0 |
0 |
0 |
0 |
0 |
| April |
0 |
0 |
25,322,000 |
0 |
0 |
0 |
0 |
0 |
0 |
| May |
0 |
2,754,000 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| June |
0 |
2,240,000 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| July |
0 |
0 |
0 |
11,955,000 |
0 |
0 |
0 |
0 |
0 |
| August |
0 |
0 |
0 |
14,061,000 |
0 |
0 |
0 |
0 |
0 |
| September |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| October |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| November |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| December |
0 |
4,000,000 |
10,357,000 |
0 |
0 |
0 |
0 |
0 |
0 |
| Total |
0 |
10,302,000 |
78,446,000 |
26,016,000 |
0 |
0 |
0 |
0 |
0 |
Appendix 11: Tencent (0700) by Month
| Tencent (0700) – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
0 |
0 |
0 |
4,831,400 |
7,853,100 |
34,110,000 |
37,060,000 |
10,205,000 |
| February |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| March |
0 |
0 |
0 |
0 |
4,033,000 |
4,620,000 |
16,930,000 |
5,924,000 |
0 |
| April |
0 |
0 |
0 |
0 |
7,240,000 |
4,640,000 |
25,860,000 |
8,430,000 |
0 |
| May |
0 |
0 |
0 |
0 |
0 |
7,740,000 |
27,550,000 |
9,784,000 |
0 |
| June |
0 |
0 |
0 |
0 |
2,500,000 |
23,493,400 |
50,280,000 |
20,669,000 |
0 |
| July |
0 |
0 |
0 |
0 |
7,870,000 |
10,859,200 |
23,700,000 |
7,017,000 |
0 |
| August |
0 |
362,200 |
0 |
1,332,600 |
9,950,000 |
12,380,000 |
29,320,000 |
9,191,000 |
0 |
| September |
0 |
2,294,500 |
0 |
4,249,200 |
25,020,000 |
24,280,000 |
41,877,400 |
19,152,000 |
0 |
| October |
0 |
830,000 |
0 |
0 |
15,050,000 |
11,800,000 |
5,051,100 |
4,916,000 |
0 |
| November |
0 |
0 |
0 |
0 |
11,032,800 |
11,170,000 |
19,200,000 |
9,232,000 |
0 |
| December |
0 |
0 |
0 |
0 |
19,555,800 |
33,370,000 |
33,360,000 |
22,040,000 |
0 |
| Total |
0 |
3,486,700 |
0 |
5,581,800 |
107,083,000 |
152,205,700 |
307,238,500 |
153,415,000 |
10,205,000 |
Appendix 12: HSBC (0005) by Month
| HSBC (0005) – Share buyback volume by month and year |
| Shares repurchased (trading date) |
| Month |
2018 |
2019 |
2020 |
2021 |
2022 |
2023 |
2024 |
2025 |
2026 |
| January |
0 |
0 |
0 |
0 |
27,960,594 |
0 |
122,552,689 |
82,867,710 |
0 |
| February |
0 |
0 |
0 |
0 |
24,906,232 |
0 |
51,302,690 |
50,758,214 |
0 |
| March |
0 |
0 |
0 |
0 |
65,050,466 |
0 |
122,158,417 |
103,862,170 |
0 |
| April |
0 |
0 |
0 |
0 |
69,274,784 |
0 |
102,867,356 |
43,632,215 |
0 |
| May |
0 |
0 |
0 |
0 |
27,976,092 |
73,690,430 |
112,174,638 |
117,191,165 |
0 |
| June |
0 |
0 |
0 |
0 |
61,278,424 |
102,885,810 |
141,350,214 |
80,760,711 |
0 |
| July |
0 |
0 |
0 |
0 |
62,618,641 |
81,199,523 |
89,396,738 |
54,800,474 |
0 |
| August |
0 |
93,613,105 |
0 |
0 |
0 |
90,995,526 |
115,575,881 |
80,549,311 |
0 |
| September |
0 |
42,163,889 |
0 |
0 |
0 |
99,677,790 |
143,114,902 |
80,101,981 |
0 |
| October |
0 |
0 |
0 |
10,920,644 |
0 |
66,400,674 |
93,936,740 |
66,690,676 |
0 |
| November |
0 |
0 |
0 |
73,006,934 |
0 |
129,728,329 |
144,446,030 |
0 |
0 |
| December |
0 |
0 |
0 |
40,713,153 |
0 |
114,193,735 |
63,075,668 |
0 |
0 |
| Total |
0 |
135,776,994 |
0 |
124,640,731 |
339,065,233 |
758,771,817 |
1,301,951,963 |
761,214,627 |
0 |