# Load data from star schema
etf_daily <- open_dataset(DAILY_PARQUET_DIR)

# Filter for stock_code 2800
etf_2800_all <- etf_daily %>%
  filter(stock_code == STOCK_CODE) %>%
  collect()

if (nrow(etf_2800_all) == 0) {
  stop("No data found for stock_code ", STOCK_CODE)
}

# Ensure trade_date is Date type
etf_2800_all$trade_date <- as.Date(etf_2800_all$trade_date)

# Sort by date (most recent first)
etf_2800_all <- etf_2800_all %>%
  arrange(desc(trade_date))

# Get last 5 days and last 30 days
etf_2800_recent <- etf_2800_all %>% slice_head(n = DAYS_TO_SHOW)
etf_2800_last30 <- etf_2800_all %>% slice_head(n = 30)

# Key variables
key_vars <- c("trade_date", "volume_cleaned", "turnover_cleaned", "aum_cleaned", 
              "closing_price", "nav", "day_high", "day_low", 
              "outstanding_units_cleaned", "premium_discount_percent")

# Calculate historical statistics (needed early for comparisons)
stats <- etf_2800_all %>%
  summarise(
    volume_median = median(volume_cleaned, na.rm = TRUE),
    volume_mean = mean(volume_cleaned, na.rm = TRUE),
    volume_min = min(volume_cleaned, na.rm = TRUE),
    volume_max = max(volume_cleaned, na.rm = TRUE),
    turnover_median = median(turnover_cleaned, na.rm = TRUE),
    turnover_mean = mean(turnover_cleaned, na.rm = TRUE),
    turnover_min = min(turnover_cleaned, na.rm = TRUE),
    turnover_max = max(turnover_cleaned, na.rm = TRUE),
    total_days = n()
  )

# Prepare comparison data
comparison <- etf_2800_recent %>%
  select(trade_date, volume_cleaned, turnover_cleaned) %>%
  mutate(
    volume_vs_median = ifelse(is.na(volume_cleaned), NA, volume_cleaned / stats$volume_median),
    volume_vs_mean = ifelse(is.na(volume_cleaned), NA, volume_cleaned / stats$volume_mean),
    turnover_vs_median = ifelse(is.na(turnover_cleaned), NA, turnover_cleaned / stats$turnover_median),
    turnover_vs_mean = ifelse(is.na(turnover_cleaned), NA, turnover_cleaned / stats$turnover_mean),
    volume_pct_rank = NA_real_,
    turnover_pct_rank = NA_real_
  )

# Calculate percentile ranks
for (i in 1:nrow(comparison)) {
  if (!is.na(comparison$volume_cleaned[i])) {
    comparison$volume_pct_rank[i] <- mean(etf_2800_all$volume_cleaned <= comparison$volume_cleaned[i], na.rm = TRUE) * 100
  }
  if (!is.na(comparison$turnover_cleaned[i])) {
    comparison$turnover_pct_rank[i] <- mean(etf_2800_all$turnover_cleaned <= comparison$turnover_cleaned[i], na.rm = TRUE) * 100
  }
}

Data Overview

cat("**Stock Code:**", STOCK_CODE, "\n\n")
## **Stock Code:** 3033
cat("**Total Records:**", nrow(etf_2800_all), "\n\n")
## **Total Records:** 65
cat("**Date Range:**", format(min(etf_2800_all$trade_date)), "to", format(max(etf_2800_all$trade_date)), "\n\n")
## **Date Range:** 2025-09-17 to 2025-12-19
cat("**Last Updated:**", format(Sys.Date()))
## **Last Updated:** 2025-12-20

Closing Price Trend

# Prepare data for chart - order by date ascending for line chart
chart_data <- etf_2800_all %>%
  select(trade_date, closing_price) %>%
  arrange(trade_date) %>%
  mutate(
    is_latest = trade_date == max(trade_date, na.rm = TRUE)
  )

# Get latest date for labeling
latest_date <- max(chart_data$trade_date, na.rm = TRUE)
latest_price <- chart_data$closing_price[chart_data$trade_date == latest_date][1]

ggplot(chart_data, aes(x = trade_date, y = closing_price)) +
  geom_line(color = "#007bff", size = 0.8) +
  geom_point(data = chart_data %>% filter(is_latest), 
             color = "red", size = 3, shape = 19) +
  geom_point(data = chart_data %>% filter(!is_latest), 
             color = "#007bff", size = 0.5, alpha = 0.6) +
  labs(
    title = paste("Closing Price Trend - ETF", STOCK_CODE),
    subtitle = paste("Latest:", format(latest_date), "- Price:", ifelse(is.na(latest_price), "NA", formatC(latest_price, format = "f", digits = 4))),
    x = "Date",
    y = "Closing Price (HKD)",
    caption = "Red dot indicates latest data point"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    plot.subtitle = element_text(size = 11, color = "gray50"),
    axis.title = element_text(size = 11),
    axis.text = element_text(size = 9),
    plot.caption = element_text(size = 9, color = "gray60", hjust = 0),
    panel.grid.minor = element_blank()
  ) +
  scale_x_date(date_labels = "%Y-%m-%d", date_breaks = "1 month") +
  scale_y_continuous(labels = scales::number_format(accuracy = 0.01))


Latest Date Verification

latest_date <- max(etf_2800_all$trade_date)
dec_16 <- etf_2800_recent %>%
  filter(trade_date == latest_date)

if (nrow(dec_16) > 0) {
  # Create a summary table
  latest_summary <- data.frame(
    Metric = c("Date", "Volume", "Turnover", "AUM", "Closing Price", "NAV", "Suspension Flag"),
    Value = c(
      format(latest_date),
      ifelse(is.na(dec_16$volume_cleaned[1]), "NA (missing/suspended)", 
             formatC(dec_16$volume_cleaned[1], format = "d", big.mark = ",")),
      ifelse(is.na(dec_16$turnover_cleaned[1]), "NA (missing/suspended)",
             formatC(dec_16$turnover_cleaned[1], format = "f", digits = 2, big.mark = ",")),
      ifelse(is.na(dec_16$aum_cleaned[1]), "NA",
             formatC(dec_16$aum_cleaned[1], format = "f", digits = 0, big.mark = ",")),
      ifelse(is.na(dec_16$closing_price[1]), "NA",
             formatC(dec_16$closing_price[1], format = "f", digits = 4)),
      ifelse(is.na(dec_16$nav[1]), "NA",
             formatC(dec_16$nav[1], format = "f", digits = 4)),
      ifelse(is.na(dec_16$suspension_flag[1]), "NA", dec_16$suspension_flag[1])
    ),
    stringsAsFactors = FALSE
  )
  
  # Add comparison metrics if available
  if (!is.na(dec_16$volume_cleaned[1]) && !is.na(dec_16$turnover_cleaned[1])) {
    vol_idx <- which(comparison$trade_date == latest_date)
    comparison_metrics <- data.frame(
      Metric = c("Volume vs Median", "Volume vs Mean", 
                 "Turnover vs Median", "Turnover vs Mean"),
      Value = c(
        sprintf("%.2fx (%s percentile)", 
                dec_16$volume_cleaned[1] / stats$volume_median,
                sprintf("%.1f%%", comparison$volume_pct_rank[vol_idx])),
        sprintf("%.2fx", dec_16$volume_cleaned[1] / stats$volume_mean),
        sprintf("%.2fx (%s percentile)",
                dec_16$turnover_cleaned[1] / stats$turnover_median,
                sprintf("%.1f%%", comparison$turnover_pct_rank[vol_idx])),
        sprintf("%.2fx", dec_16$turnover_cleaned[1] / stats$turnover_mean)
      ),
      stringsAsFactors = FALSE
    )
    latest_summary <- rbind(latest_summary, comparison_metrics)
  }
  
  latest_summary %>%
    gt() %>%
    tab_header(
      title = "Latest Date Data Summary",
      subtitle = paste("ETF", STOCK_CODE, "-", format(latest_date))
    ) %>%
    cols_label(
      Metric = "Metric",
      Value = "Value"
    ) %>%
    tab_style(
      style = cell_text(weight = "bold"),
      locations = cells_column_labels()
    ) %>%
    tab_style(
      style = cell_text(align = "left"),
      locations = cells_body(columns = Metric)
    ) %>%
    tab_style(
      style = cell_text(align = "right"),
      locations = cells_body(columns = Value)
    ) %>%
    opt_table_font(font = "Arial") %>%
    tab_options(
      table.width = pct(60),
      column_labels.background.color = "#f8f9fa",
      table_body.hlines.color = "#e9ecef",
      heading.border.bottom.color = "#dee2e6"
    )
} else {
  cat("⚠ Warning: No data found for latest date\n")
}
Latest Date Data Summary
ETF 3033 - 2025-12-19
Metric Value
Date 2025-12-19
Volume 892,442,349
Turnover 4,787,090,098.00
AUM NA
Closing Price 5.3600
NAV NA
Suspension Flag No
Volume vs Median 0.68x (21.5% percentile)
Volume vs Mean 0.63x
Turnover vs Median 0.65x (13.8% percentile)
Turnover vs Mean 0.58x

Comparison: Recent vs Historical

Percentile Explanation: Percentile rank shows what percentage of historical values are less than or equal to the current value. For example, a 75th percentile means the current value is higher than 75% of all historical values (higher is better for volume/turnover). A 25th percentile means it’s higher than only 25% of historical values (lower activity).

comparison_display <- comparison %>%
  select(trade_date, volume_cleaned, volume_vs_median, volume_vs_mean, volume_pct_rank,
         turnover_cleaned, turnover_vs_median, turnover_vs_mean, turnover_pct_rank)

comparison_display %>%
  gt() %>%
  tab_header(
    title = "Recent Performance vs Historical Statistics",
    subtitle = paste("ETF", STOCK_CODE, "- Comparison of last 5 days")
  ) %>%
  fmt_date(columns = trade_date, date_style = "yMd") %>%
  fmt_number(columns = volume_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = volume_vs_median, decimals = 2, pattern = "{x}x") %>%
  fmt_number(columns = volume_vs_mean, decimals = 2, pattern = "{x}x") %>%
  fmt_number(columns = volume_pct_rank, decimals = 1, pattern = "{x}%") %>%
  fmt_number(columns = turnover_cleaned, decimals = 2, use_seps = TRUE) %>%
  fmt_number(columns = turnover_vs_median, decimals = 2, pattern = "{x}x") %>%
  fmt_number(columns = turnover_vs_mean, decimals = 2, pattern = "{x}x") %>%
  fmt_number(columns = turnover_pct_rank, decimals = 1, pattern = "{x}%") %>%
  sub_missing(columns = everything(), missing_text = "NA") %>%
  cols_label(
    trade_date = "Date",
    volume_cleaned = "Volume",
    volume_vs_median = "vs Median",
    volume_vs_mean = "vs Mean",
    volume_pct_rank = "Percentile",
    turnover_cleaned = "Turnover",
    turnover_vs_median = "vs Median",
    turnover_vs_mean = "vs Mean",
    turnover_pct_rank = "Percentile"
  ) %>%
  tab_spanner(
    label = "Volume",
    columns = c(volume_cleaned, volume_vs_median, volume_vs_mean, volume_pct_rank)
  ) %>%
  tab_spanner(
    label = "Turnover",
    columns = c(turnover_cleaned, turnover_vs_median, turnover_vs_mean, turnover_pct_rank)
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_text(align = "right"),
    locations = cells_body()
  ) %>%
  tab_style(
    style = cell_text(weight = "bold", align = "center"),
    locations = cells_column_spanners()
  ) %>%
  opt_table_font(font = "Arial") %>%
  tab_options(
    table.width = pct(100),
    column_labels.background.color = "#f8f9fa",
    table_body.hlines.color = "#e9ecef",
    heading.border.bottom.color = "#dee2e6"
  )
Recent Performance vs Historical Statistics
ETF 3033 - Comparison of last 5 days
Date
Volume
Turnover
Volume vs Median vs Mean Percentile Turnover vs Median vs Mean Percentile
12/19/2025 892,442,349 0.68x 0.63x 21.5% 4,787,090,098.00 0.65x 0.58x 13.8%
12/18/2025 1,139,429,792 0.87x 0.80x 40.0% 6,020,161,755.00 0.81x 0.72x 36.9%
12/17/2025 1,691,790,573 1.29x 1.19x 75.4% 8,988,359,290.00 1.21x 1.08x 64.6%
12/16/2025 858,662,184 0.65x 0.60x 15.4% 4,537,412,993.00 0.61x 0.55x 6.2%
12/15/2025 1,239,277,553 0.94x 0.87x 49.2% 6,724,758,896.00 0.91x 0.81x 43.1%

Recent 5 Days

display_data <- etf_2800_recent %>%
  select(any_of(key_vars))

display_data %>%
  gt() %>%
  tab_header(
    title = "Recent 5 Days - Key Metrics",
    subtitle = paste("ETF", STOCK_CODE)
  ) %>%
  fmt_date(columns = trade_date, date_style = "yMd") %>%
  fmt_number(columns = volume_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = turnover_cleaned, decimals = 2, use_seps = TRUE) %>%
  fmt_number(columns = aum_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = closing_price, decimals = 4) %>%
  fmt_number(columns = nav, decimals = 4) %>%
  fmt_number(columns = day_high, decimals = 4) %>%
  fmt_number(columns = day_low, decimals = 4) %>%
  fmt_number(columns = outstanding_units_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = premium_discount_percent, decimals = 2) %>%
  cols_label(
    trade_date = "Date",
    volume_cleaned = "Volume",
    turnover_cleaned = "Turnover",
    aum_cleaned = "AUM",
    closing_price = "Close Price",
    nav = "NAV",
    day_high = "Day High",
    day_low = "Day Low",
    outstanding_units_cleaned = "Outstanding Units",
    premium_discount_percent = "Premium/Discount %"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_text(align = "right"),
    locations = cells_body()
  ) %>%
  opt_table_font(font = "Arial") %>%
  tab_options(
    table.width = pct(100),
    column_labels.background.color = "#f8f9fa",
    table_body.hlines.color = "#e9ecef",
    heading.border.bottom.color = "#dee2e6"
  )
Recent 5 Days - Key Metrics
ETF 3033
Date Volume Turnover AUM Close Price NAV Day High Day Low Outstanding Units Premium/Discount %
12/19/2025 892,442,349 4,787,090,098.00 NA 5.3600 NA 5.3900 5.3200 13,642,400,200 NA
12/18/2025 1,139,429,792 6,020,161,755.00 72,420,000,000 5.3100 5.3100 5.3200 5.2600 13,642,400,200 0.02
12/17/2025 1,691,790,573 8,988,359,290.00 72,950,000,000 5.3500 5.3500 5.3700 5.2800 13,642,400,200 0.04
12/16/2025 858,662,184 4,537,412,993.00 72,750,000,000 5.2900 5.2900 5.3700 5.2400 13,743,400,200 −0.07
12/15/2025 1,239,277,553 6,724,758,896.00 75,140,000,000 5.3800 5.3900 5.4700 5.3800 13,947,400,200 −0.05

Recent 30 Days

table_30days <- etf_2800_last30 %>%
  select(any_of(key_vars)) %>%
  arrange(desc(trade_date))

table_30days %>%
  gt() %>%
  tab_header(
    title = "Recent 30 Days - Complete Data",
    subtitle = paste("ETF", STOCK_CODE, "- Most recent dates first")
  ) %>%
  fmt_date(columns = trade_date, date_style = "yMd") %>%
  fmt_number(columns = volume_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = turnover_cleaned, decimals = 2, use_seps = TRUE) %>%
  fmt_number(columns = aum_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = closing_price, decimals = 4) %>%
  fmt_number(columns = nav, decimals = 4) %>%
  fmt_number(columns = day_high, decimals = 4) %>%
  fmt_number(columns = day_low, decimals = 4) %>%
  fmt_number(columns = outstanding_units_cleaned, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = premium_discount_percent, decimals = 2) %>%
  sub_missing(columns = everything(), missing_text = "NA") %>%
  cols_label(
    trade_date = "Date",
    volume_cleaned = "Volume",
    turnover_cleaned = "Turnover",
    aum_cleaned = "AUM",
    closing_price = "Close Price",
    nav = "NAV",
    day_high = "Day High",
    day_low = "Day Low",
    outstanding_units_cleaned = "Outstanding Units",
    premium_discount_percent = "Premium/Discount %"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_text(align = "right"),
    locations = cells_body()
  ) %>%
  opt_table_font(font = "Arial") %>%
  tab_options(
    table.width = pct(100),
    column_labels.background.color = "#f8f9fa",
    table_body.hlines.color = "#e9ecef",
    heading.border.bottom.color = "#dee2e6",
    table.font.size = px(12)
  )
Recent 30 Days - Complete Data
ETF 3033 - Most recent dates first
Date Volume Turnover AUM Close Price NAV Day High Day Low Outstanding Units Premium/Discount %
12/19/2025 892,442,349 4,787,090,098.00 NA 5.3600 NA 5.3900 5.3200 13,642,400,200 NA
12/18/2025 1,139,429,792 6,020,161,755.00 72,420,000,000 5.3100 5.3100 5.3200 5.2600 13,642,400,200 0.02
12/17/2025 1,691,790,573 8,988,359,290.00 72,950,000,000 5.3500 5.3500 5.3700 5.2800 13,642,400,200 0.04
12/16/2025 858,662,184 4,537,412,993.00 72,750,000,000 5.2900 5.2900 5.3700 5.2400 13,743,400,200 −0.07
12/15/2025 1,239,277,553 6,724,758,896.00 75,140,000,000 5.3800 5.3900 5.4700 5.3800 13,947,400,200 −0.05
12/12/2025 972,331,101 5,349,672,951.00 78,160,000,000 5.5200 5.5200 5.5400 5.4300 14,147,900,200 −0.18
12/11/2025 864,808,203 4,703,813,108.00 76,730,000,000 5.4200 5.4200 5.5200 5.4000 14,147,900,200 0.03
12/10/2025 979,369,763 5,317,185,434.00 76,550,000,000 5.4600 5.4700 5.4700 5.3900 13,996,400,200 −0.26
12/9/2025 1,313,889,987 7,190,317,325.00 76,170,000,000 5.4400 5.4400 5.5500 5.4200 13,996,400,200 0.04
12/8/2025 664,780,210 3,696,484,973.00 77,490,000,000 5.5400 5.5500 5.6000 5.5400 13,967,400,200 −0.06
12/5/2025 1,043,813,555 5,752,290,779.00 77,510,000,000 5.5400 5.5500 5.5800 5.4400 13,967,400,200 −0.17
12/4/2025 902,847,524 4,932,497,746.00 77,060,000,000 5.5000 5.5000 5.5400 5.3900 14,001,400,200 −0.07
12/3/2025 699,980,187 3,809,071,589.00 76,200,000,000 5.4200 5.4200 5.5200 5.4100 14,050,900,200 −0.06
12/2/2025 619,644,746 3,425,344,789.00 76,630,000,000 5.5200 5.5100 5.5800 5.4800 13,905,900,200 0.17
12/1/2025 1,612,028,436 8,900,197,670.00 76,910,000,000 5.5200 5.5300 5.5600 5.4700 13,905,900,200 −0.20
11/28/2025 840,176,737 4,613,317,586.00 76,550,000,000 5.4900 5.4900 5.5300 5.4600 13,952,400,200 0.05
11/27/2025 1,222,467,487 6,739,034,971.00 77,380,000,000 5.4800 5.4900 5.5700 5.4700 14,106,400,200 −0.02
11/26/2025 951,646,982 5,257,031,164.00 79,970,000,000 5.5000 5.5100 5.5600 5.5000 14,523,900,200 −0.02
11/25/2025 1,569,684,609 8,653,403,170.00 77,860,000,000 5.5000 5.5000 5.5600 5.4500 14,157,900,200 0.00
11/24/2025 1,791,567,261 9,663,224,540.00 75,810,000,000 5.4200 5.4400 5.4600 5.3100 13,948,900,200 −0.19
11/21/2025 2,611,547,036 13,880,882,910.00 72,840,000,000 5.2700 5.2900 5.3700 5.2600 13,773,900,200 −0.35
11/20/2025 1,905,105,904 10,389,622,630.00 74,380,000,000 5.4700 5.4600 5.5600 5.4000 13,613,400,200 0.11
11/19/2025 1,855,234,737 10,226,882,860.00 73,390,000,000 5.5000 5.5000 5.5800 5.4700 13,353,900,200 −0.02
11/18/2025 1,383,768,691 7,681,487,586.00 73,720,000,000 5.5300 5.5300 5.6200 5.5000 13,320,900,200 −0.08
11/17/2025 901,677,839 5,101,257,206.00 74,060,000,000 5.6500 5.6400 5.7200 5.6200 13,124,900,200 0.12
11/14/2025 919,062,831 5,277,253,028.00 76,070,000,000 5.7000 5.7000 5.7900 5.6800 13,349,400,200 0.11
11/13/2025 1,166,473,139 6,772,321,736.00 78,280,000,000 5.8600 5.8600 5.9100 5.7600 13,349,400,200 −0.15
11/12/2025 1,604,332,817 9,308,318,670.00 77,420,000,000 5.8100 5.8200 5.8600 5.7600 13,308,400,200 −0.13
11/11/2025 834,860,520 4,834,475,518.00 77,800,000,000 5.8000 5.8100 5.8500 5.7400 13,394,900,200 −0.15
11/10/2025 868,099,374 4,997,647,223.00 77,510,000,000 5.7900 5.8000 5.8100 5.7000 13,365,400,200 −0.17

Historical Statistics

Volume Statistics

volume_stats_df <- data.frame(
  Metric = c("Median", "Mean", "Minimum", "Maximum", "Total Days"),
  Value = c(
    stats$volume_median,
    stats$volume_mean,
    stats$volume_min,
    stats$volume_max,
    stats$total_days
  )
)

volume_stats_df %>%
  gt() %>%
  fmt_number(columns = Value, rows = 1:4, decimals = 0, use_seps = TRUE) %>%
  fmt_number(columns = Value, rows = 5, decimals = 0, use_seps = FALSE) %>%
  cols_label(
    Metric = "Metric",
    Value = "Value"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_text(align = "right"),
    locations = cells_body(columns = Value)
  ) %>%
  opt_table_font(font = "Arial") %>%
  tab_options(
    table.width = pct(50),
    column_labels.background.color = "#f8f9fa",
    table_body.hlines.color = "#e9ecef"
  )
Metric Value
Median 1,313,889,987
Mean 1,425,819,881
Minimum 619,644,746
Maximum 3,737,847,066
Total Days 65

Turnover Statistics

turnover_stats_df <- data.frame(
  Metric = c("Median", "Mean", "Minimum", "Maximum"),
  Value = c(
    stats$turnover_median,
    stats$turnover_mean,
    stats$turnover_min,
    stats$turnover_max
  )
)

turnover_stats_df %>%
  gt() %>%
  fmt_number(columns = Value, decimals = 2, use_seps = TRUE) %>%
  cols_label(
    Metric = "Metric",
    Value = "Value"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_text(align = "right"),
    locations = cells_body(columns = Value)
  ) %>%
  opt_table_font(font = "Arial") %>%
  tab_options(
    table.width = pct(50),
    column_labels.background.color = "#f8f9fa",
    table_body.hlines.color = "#e9ecef"
  )
Metric Value
Median 7,416,426,523.00
Mean 8,304,287,494.49
Minimum 3,425,344,789.00
Maximum 22,304,783,010.00

Report generated: 2025-12-20 08:23:28.668204