0. Process

This report explains how we reached the results. The methodology proceeds in the following steps:

  1. 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.).

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

References

HKEX share repurchase reports. (n.d.). https://www3.hkexnews.hk/reports/sharerepur/sbn.asp