| Title: | Estimate Holding Cost Distributions for Chinese Equities |
| Version: | 0.2.1 |
| Description: | Estimates holding-cost distributions of mainland Chinese A-share equities from daily price, volume, amount and turnover data. Implements the linear turnover-based cost-distribution recurrence of Liu and Wang (2018) <doi:10.1016/j.physa.2017.11.154>, and adds a package-specific exponential-retention alternative and price-allocation choices. Provides optional adjustment factors and initial distributions, offline preparation of 'Tushare' tables, weighted cost summaries and diagnostics for reproducible sensitivity analysis. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Depends: | R (≥ 4.1.0) |
| Imports: | graphics |
| Suggests: | testthat (≥ 3.0.0), knitr, rmarkdown |
| VignetteBuilder: | knitr |
| Config/testthat/edition: | 3 |
| Language: | en-US |
| NeedsCompilation: | no |
| Packaged: | 2026-10-26 03:11:39 UTC; zhaoruihong |
| Author: | Ruihong Zhao [aut, cre] |
| Maintainer: | Ruihong Zhao <3142115360@qq.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-11-10 17:20:08 UTC |
Estimate A-Share Holding-Cost Distributions
Description
Offline preparation and estimation of daily holding-cost distributions for mainland Chinese A-share equities. Models expose their initialization, turnover replacement, price allocation and price-basis assumptions.
Details
The preparation function joins locally supplied tables. The estimator returns weights, statistics and diagnostics. A weighted-mean cost utility is also provided. Examples and the vignette use synthetic data and require no token. See the introduction vignette for a worked example and limitations.
Author(s)
Ruihong Zhao 3142115360@qq.com
See Also
Inspect and Plot a Chip Distribution
Description
Print a brief model summary, extract statistics or weights, or plot estimated cost on the vertical axis against estimated chip share on the horizontal axis.
Usage
## S3 method for class 'chip_distribution'
print(x, ...)
## S3 method for class 'chip_distribution'
summary(object, ...)
## S3 method for class 'chip_distribution'
as.data.frame(x, row.names = NULL, optional = FALSE, ...)
## S3 method for class 'chip_distribution'
plot(x, ..., col = "#4C78A8", close_col = "#D62728")
Arguments
x, object |
A |
... |
For |
row.names, optional |
Passed to |
col |
Colour for horizontal chip segments. |
close_col |
Colour for the final closing-price reference line. |
Value
summary returns the one-row summary data frame;
as.data.frame returns the distribution data frame.
print and plot return x invisibly.
plot draws on the current graphics device without opening an external
viewer or saving files.
See Also
Examples
daily <- data.frame(
trade_date = c("20260102", "20260105"),
high = c(10, 11), low = c(10, 11), close = c(10, 11),
vol = c(100, 100), amount = c(100, 110), turnover_rate = c(0, 20)
)
chips <- estimate_chip_distribution(daily)
print(chips)
summary(chips)
as.data.frame(chips)
plot(chips, main = "Synthetic example", xlab = "Share (%)")
Calculate a Share-Weighted Mean Cost
Description
Calculate the weighted mean of prices without R's vector recycling. Prices and share counts must correspond in the same order.
Usage
chip_mean_cost(price, shares)
Arguments
price |
Non-empty vector of positive, finite prices. Numeric text and factors containing numeric text are also accepted. |
shares |
Equally sized vector of non-negative, finite share counts or weights with a finite positive sum. |
Value
A numeric scalar equal to the sum of prices multiplied by normalized share counts. Prices retain the input monetary units.
Examples
chip_mean_cost(c(10, 11, 12), c(50, 20, 30)) # 10.8
chip_mean_cost(c(10, 11, 12), c(0.5, 0.2, 0.3))
Estimate a Holding-Cost Distribution from Daily Market Data
Description
Estimate the cost distribution of circulating shares using an explicitly specified daily turnover-replacement model. Returns auditable weights, descriptive cost statistics and a per-day diagnostic record.
Usage
estimate_chip_distribution(
data, date_col = "trade_date", high_col = "high", low_col = "low",
close_col = "close", turnover_col = "turnover_rate", volume_col = "vol",
amount_col = "amount", adjustment_col = NULL, price_step = 0.01,
price_method = c("vwap", "typical", "close", "uniform"),
turnover_transform = c("exponential", "linear"),
initial_distribution = NULL
)
Arguments
data |
A non-empty data frame containing one row per observed day and one
stock. A |
date_col |
Date column name. Accepts |
high_col, low_col, close_col |
Column names for positive finite daily high, low and closing prices in CNY per share. High must be at least low and close must lie in that range. Use unadjusted prices when supplying factors. |
turnover_col |
Column name for non-negative daily turnover in percentage
points: |
volume_col, amount_col |
Volume and amount column names, required for VWAP. Units are lots of 100 shares and thousand CNY, respectively, as in Tushare daily data. Zero values are permitted together only on rows on which no transaction price is needed. Negative values are rejected. |
adjustment_col |
Optional column name for strictly positive adjustment factors. Historical prices are multiplied by that day's factor divided by the last observed day's factor. Without factors, prices must already share a consistent basis. |
price_step |
Positive price-grid spacing in CNY. Smaller spacing increases resolution and computational work. Grids above one million bins are rejected. The spacing must exceed the numerical resolution of the input prices. |
price_method |
Placement of newly traded weight: |
turnover_transform |
|
initial_distribution |
Optional non-empty data frame with positive
|
Details
Let w_{t-1} be the previous grid distribution, g_t the normalized
distribution assigned to newly traded shares and s_t the retention
fraction. The recurrence is
w_t = s_t w_{t-1} + (1-s_t) g_t.
The exponential transformation assumes homogeneous random replacement; the linear alternative assumes a fraction of existing shares changes hands once. Neither transformation identifies actual sellers or individual accounts.
If initial_distribution is NULL, all initial weight is placed
using the first day's price rule. Its turnover is not used as a replacement
step. If an initial distribution is supplied, every row is a replacement step.
history_coverage describes the amount of initial weight replaced by
those steps, not coverage of actual shareholder records.
Point prices are rounded to the nearest grid centre using R's ties-to-even
rounding. Centres are spaced by price_step; the first centre is the
largest non-negative grid multiple not exceeding the minimum input price,
except that a zero centre is replaced by the minimum positive input price.
Uniform allocation uses overlap with cells extending half a step either side
of each centre. A zero-width high-low interval is allocated to one centre.
No arbitrary small-weight threshold is used; only zero numerical weights are
omitted. Very old weights may underflow in long histories.
VWAP values beyond the high-low range by more than
1e-6 * max(1, high) in original price units are rejected. Smaller
rounding discrepancies are clamped to the range and flagged. The optional
float_share column must be positive; a consecutive relative change
above 1e-6 produces a warning and a diagnostic flag. Issuances, unlocks,
repurchases, fees and taxes are not modelled. Dividend factors provide a
comparable price basis rather than literal historical cash acquisition costs.
Runtime is approximately proportional to the product of the number of days and grid bins. Memory usage is proportional to bins plus input rows.
Value
A chip_distribution object, a list with four components:
- distribution
Data frame with positive grid
price, normalizedweight(sum one, subject to numerical precision), andpercent = 100 * weight.- summary
One-row data frame, with these groups of fields:
Stock identifier:
ts_code.Date interval:
start_dateandend_date.Input count:
observations. Final price:last_close.Weighted mean:
average_cost. Weighted median:median_cost.Peak cost:
peak_price. Peak share:peak_percent. Tied peaks use the lowest price.Profitable share:
profit_ratioandprofit_percent; only costs strictly below the last close are included.Equal-price share:
breakeven_ratio. Higher-cost share:loss_ratio. Equality allows only numerical rounding error. The profit, break-even and loss ratios sum to one.Central interval:
cost_low_90andcost_high_90, the discrete 5th and 95th weighted quantiles.Range width:
concentration_90, the interval's width divided by the sum of its endpoints. Smaller values mean narrower cost ranges, not higher ownership concentration.Turnover sum:
cumulative_turnover_percent, over applied rows.Surviving prior:
initialization_weight, the product of retention fractions over applied update rows.Replaced prior:
history_coverage, one minus surviving prior.
- diagnostics
One row per sorted date with the following fields:
Date:
trade_date. Turnover:turnover_rate.Proposed replacement:
replacement_fraction.Used replacement:
applied_replacement_fraction; zero for an initialized first row.Old-weight retention:
retention.Final-basis price:
transaction_price.Original-basis price:
raw_transaction_price.Factor normalization:
adjustment_ratio.Minor VWAP corrections:
vwap_rounding_adjusted.Initialization flag:
initialized_here.Share-count change flag:
capital_changed.
vwap_outside_rangeis retained for backwards compatibility and is always false because material violations are errors. Uniform allocation's transaction price is a representative typical price, not the allocation itself.- settings
Column mappings, grid spacing and origin, price and turnover methods, initialization rule and
price_basis = "last_trading_day".
The estimates depend on modelling assumptions, observed history and grid spacing. They do not establish investor identity or future returns.
References
Liu, H. and Wang, L. (2018). “The price momentum of stock in distribution.” Physica A: Statistical Mechanics and its Applications, 492, 2336–2344. doi:10.1016/j.physa.2017.11.154.
Tushare daily prices and units: https://tushare.pro/document/2?doc_id=27. Turnover definitions: https://tushare.pro/document/2?doc_id=32. Adjustment factors: https://tushare.pro/document/2?doc_id=28.
See Also
prepare_tushare_data, chip_mean_cost,
plot.chip_distribution
Examples
# Entirely synthetic data; no network access or API token is required.
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)
summary(chips)
as.data.frame(chips)
initial <- data.frame(price = c(8, 12), weight = c(0.5, 0.5))
estimate_chip_distribution(daily, initial_distribution = initial)
Prepare Matching Tushare Tables without Network Access
Description
Validate and join locally supplied daily price, turnover and optional factor tables with complete one-to-one stock/date matches. Dates are normalized and sorted. No API requests, token storage or file writes are performed.
Usage
prepare_tushare_data(daily, daily_basic, adj_factor = NULL,
ts_code = NULL, as_of = NULL)
Arguments
daily |
Unadjusted daily prices, supplied as a non-empty data frame with
|
daily_basic |
Non-empty data frame with |
adj_factor |
Optional factor table with three columns:
|
ts_code |
Optional single stock code to select from all tables. Without it, each table must refer to one and the same stock. |
as_of |
Optional cutoff date. Accepts a |
Details
Duplicate selected stock/date keys, different stocks or a missing turnover
or factor row for any retained daily date are errors. Extra dates in the
indicator or factor table are ignored. Daily prices remain authoritative;
for example, daily_basic$close does not replace daily$close.
Columns from daily are preserved except reserved indicator/factor
columns, which are supplied by their authoritative tables. Arithmetic and
price-range validation is performed by the estimator.
Value
A data frame ready for estimate_chip_distribution(), with daily price
columns, matched turnover_rate, optional float_share and optional
adj_factor. If factors are included, explicitly specify
adjustment_col = "adj_factor" in the estimator.
See Also
Examples
daily <- data.frame(
ts_code = rep("000001.SZ", 2), trade_date = c("20260105", "20260102"),
high = c(11, 10), low = c(11, 10), close = c(11, 10),
vol = c(100, 100), amount = c(110, 100)
)
basic <- data.frame(
ts_code = rep("000001.SZ", 2), trade_date = c("20260102", "20260105"),
turnover_rate = c(0, 20)
)
input <- prepare_tushare_data(daily, basic)
estimate_chip_distribution(input)