Files
chinese-equity-quant/docs/portfolio_trading_cost_model.md
T
Yuxuan Yan 2c0ca53bd6 Document cost bps as one-way per-trade, not round-trip
The simulator charges (cost_bps + slippage_bps) on each fill, so a full
round trip is charged twice. Correct the cost-model doc, the reversal_5d
report, and the report generator to state the rate is one-way per-trade
(~20 bps round trip for 5+5), rather than mislabeling it round-trip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 21:46:41 +08:00

4.1 KiB

Portfolio Trading Cost Model

This document describes the trading cost model used by portfolio simulate. The current implementation is a simplified open-execution proportional cost model. It is intentionally small, explicit, and easy to audit.

Open-Execution Timeline

The simulator runs once per trading day:

  1. A constructed portfolio row provides the target book for an execution date. In the current file layout, a target dated t is executed at the next available market date d = next(t).
  2. Trades are executed at open[d].
  3. Realized positions are held during the trading day.
  4. Daily PnL is marked from open[d] to close[d] on the newly realized book, plus any overnight gap from the previous realized holdings.
  5. Trading cost is charged only on actually realized traded_shares, after all constraints have clipped the desired trade.

This means a fully blocked order has traded_shares = 0 and therefore zero trading cost.

Current Formula

For each symbol:

trade_value_i = abs(traded_shares_i * execution_price_i)
trade_cost_i  = trade_value_i * (cost_bps + slippage_bps) / 10000

where:

execution_price_i = open_price_i

cost_bps is the proportional explicit trading-cost rate in basis points. slippage_bps is modeled as an additional cash cost in basis points. The two rates are added linearly. The CLI options --cost-bps and --slippage-bps both default to 0.0.

Both rates are one-way, per-trade: the combined (cost_bps + slippage_bps) is charged on the traded notional of each fill, buy and sell alike. A full round trip (enter then exit a position) is therefore charged twice — e.g. 5 + 5 bps becomes ~20 bps over a complete round trip, not 10. Quote any round-trip figure by doubling, or convert a round-trip budget to a per-trade rate by halving before passing it in.

Example:

traded_shares = 1000
execution_price = 20 yuan
cost_bps = 10
slippage_bps = 5

abs(1000 * 20) * 15 / 10000 = 30 yuan

Slippage Convention

Slippage is not applied by changing the execution price. It is charged only as a cash cost through trade_cost.

Do not double-count slippage by doing both:

execution_price = open * (1 +/- slippage_bps / 10000)
trade_cost += trade_value * slippage_bps / 10000

The simulator should execute at the open price and subtract the slippage cash cost from PnL.

Relationship To The Simulator

ReferenceSimulator.fill() clips desired trades through constraints first, then passes the actual traded_shares to the cost model. The per-name result is stored in the fills parquet as trade_cost.

ReferenceSimulator.run() sums per-name trade_cost into the daily PnL row's cost column and subtracts that total from daily PnL:

pnl = overnight + intraday - cost_total

What This Model Does Not Cover

The current model intentionally does not model:

  • Minimum commissions.
  • Buy/sell asymmetric fees.
  • Sell-side stamp duty.
  • Exchange handling fees.
  • Regulatory fees.
  • Transfer fees.
  • Date-aware fee schedule changes.
  • Nonlinear price impact.
  • Auction liquidity / queue effects.
  • Partial fills caused by open auction depth.

These omissions are deliberate. The current model is the default reference model, not a detailed brokerage fee simulator.

Future Extension

The simulator is structured around a cost model abstraction:

class CostModel:
    def compute(
        self,
        traded_shares,
        execution_price,
        side,
        date,
        metadata,
    ):
        ...

The current implementation is SimpleProportionalCostModel.

A future AShareDetailedCostModel can add:

  • Commission, optionally subject to minimum commission.
  • Sell-side stamp duty.
  • Transfer fee.
  • Exchange handling fee.
  • Regulatory fee.
  • Date-aware fee rates.
  • Separate buy-side and sell-side rates.
  • Optional nonlinear slippage / market-impact model.

Any future model must preserve the same high-level simulator contract: costs are computed from realized trades after constraints, and slippage must not be counted both through execution-price adjustment and cash cost.