A distribution assigns a share of circulating stock to each modelled holding cost. Average cost is weighted by shares, rather than by the number of distinct price levels:
chip_mean_cost(c(10, 11, 12), c(50, 20, 30))
#> [1] 10.8Actual holders’ acquisition costs are not available in ordinary daily market data. This package instead specifies the assumptions used to carry old weights forward and allocate new weights.
The following data are entirely synthetic, not a real stock history. Each day has a single transaction price. Dates are deliberately out of order.
daily <- data.frame(
trade_date = c("20260106", "20260102", "20260105"),
high = c(12, 10, 11), low = c(12, 10, 11), close = c(12, 10, 11),
vol = c(100, 100, 100), amount = c(120, 100, 110),
turnover_rate = c(30, 0, 20)
)
chips <- estimate_chip_distribution(daily)
as.data.frame(chips)
#> price weight percent
#> 1 10 0.6065307 60.65307
#> 2 11 0.1342876 13.42876
#> 3 12 0.2591818 25.91818
summary(chips)
#> ts_code start_date end_date observations last_close average_cost median_cost
#> 1 <NA> 20260102 20260106 3 12 10.65265 10
#> peak_price peak_percent profit_ratio profit_percent breakeven_ratio
#> 1 10 60.65307 0.7408182 74.08182 0.2591818
#> loss_ratio cost_low_90 cost_high_90 concentration_90
#> 1 0 10 12 0.09090909
#> cumulative_turnover_percent history_coverage initialization_weight
#> 1 50 0.3934693 0.6065307Tushare reports volume in lots of 100 shares and amount in thousand CNY, so VWAP is amount * 10 / vol. See the daily-data documentation and turnover definitions.
The first row initializes the distribution at 10 CNY. After turnover of 20 and 30 percentage points on the next two days, the analytical weights are:
expected <- c(
"10 CNY" = exp(-0.5),
"11 CNY" = (1 - exp(-0.2)) * exp(-0.3),
"12 CNY" = 1 - exp(-0.3)
)
expected
#> 10 CNY 11 CNY 12 CNY
#> 0.6065307 0.1342876 0.2591818
stopifnot(isTRUE(all.equal(unname(expected), chips$distribution$weight)))At a final close of 12, the 12-CNY weight is break-even; it is not counted as profitable. The three profit/break-even/loss ratios sum to one.
plot(chips, main = "Synthetic daily cost distribution")The exponential rule retains exp(-turnover_rate / 100) of old weight each day. It represents homogeneous random replacement. Linear replacement uses turnover directly, is easier to interpret as a single transfer, and requires rates at most 100 percentage points. They agree approximately for very small turnover.
The linear update builds on the turnover-based cost-distribution recurrence of Liu and Wang (2018). The exponential retention transform and each daily price-allocation rule are explicit package-specific modelling choices.
linear <- estimate_chip_distribution(daily, turnover_transform = "linear")
as.data.frame(linear)
#> price weight percent
#> 1 10 0.56 56
#> 2 11 0.14 14
#> 3 12 0.30 30The default initialization is a strong assumption. A short interval with little turnover can retain most of the first day’s assigned weight. The diagnostics distinguish proposed turnover from the replacement actually applied to that initialized first row:
chips$diagnostics[c("trade_date", "replacement_fraction",
"applied_replacement_fraction", "initialized_here")]
#> trade_date replacement_fraction applied_replacement_fraction initialized_here
#> 1 20260102 0.0000000 0.0000000 TRUE
#> 2 20260105 0.1812692 0.1812692 FALSE
#> 3 20260106 0.2591818 0.2591818 FALSE
chips$summary[c("initialization_weight", "history_coverage")]
#> initialization_weight history_coverage
#> 1 0.6065307 0.3934693If a distribution immediately before the first date is available, supply it in the final observation’s price basis. Every row then applies turnover:
initial <- data.frame(price = c(8, 12), weight = c(0.5, 0.5))
with_initial <- estimate_chip_distribution(daily, initial_distribution = initial)
summary(with_initial)
#> ts_code start_date end_date observations last_close average_cost median_cost
#> 1 <NA> 20260102 20260106 3 12 10.65265 12
#> peak_price peak_percent profit_ratio profit_percent breakeven_ratio
#> 1 12 56.24471 0.4375529 43.75529 0.5624471
#> loss_ratio cost_low_90 cost_high_90 concentration_90
#> 1 0 8 12 0.2
#> cumulative_turnover_percent history_coverage initialization_weight
#> 1 50 0.3934693 0.6065307The VWAP method places new weight at a single daily average price. Typical-price and close methods offer alternatives when volume and amount are unavailable. The uniform rule allocates weight by overlaps of grid cells with the daily low-high range. Daily data do not prove that trades were uniformly distributed.
range_data <- data.frame(
trade_date = "20260102", high = 12, low = 10, close = 11, turnover_rate = 0
)
uniform <- estimate_chip_distribution(range_data, price_method = "uniform", price_step = 0.25)
plot(uniform, main = "Uniform allocation is a modelling assumption")Smaller price_step reduces rounding error but increases computation. Cost peaks and threshold-based profit shares can change with resolution. The nominal central 90-percent interval consists of discrete 5th and 95th weighted quantiles; tied mass at endpoints can make its contained weight exceed 90 percent.
The model cannot identify which cost layers were sold, intraday trade-price patterns, locked holdings, investor identities, or the costs of new shares. Issuance, unlocks and repurchases are not modelled. If a float_share column is present, consecutive changes are flagged and produce a warning. Fees and taxes are excluded. Estimates and sensitivity comparisons describe model behaviour; they do not predict future returns.
For field definitions and validation rules, see ?estimate_chip_distribution. Chinese guidance is available at:
system.file("README.zh-CN.md", package = "asharechips")
#> [1] "/private/var/folders/4m/4fmpnlcn1vxg6btldtgs420m0000gn/T/Rtmp81fJNB/Rinst9382034e8a9/asharechips/README.zh-CN.md"