5.8 KiB
JoinQuant Cost Model Findings
Generated: 2026-07-06
This report summarizes the JoinQuant trading-cost behavior observed from the browser-automated real-data backtests and compares it with the current internal simulator model. The JoinQuant cost formula below is inferred from rendered transaction tables and strategy logs for this account. Treat it as an observed platform default, not as a guaranteed external contract.
Runs Used
Longer Comparison Run
- Portfolio:
jq_long_one_stock_long - Window:
2024-01-11to2024-02-29 - Booksize:
1,000,000 CNY - Instrument:
600000.XSHG - Target: buy and hold
1,000shares - JoinQuant rendered result: completed
- Local total PnL:
546.22 CNY - JoinQuant total PnL from positions tab:
575.00 CNY - Difference:
28.78 CNY, or0.002878percentage points on the book
The first JoinQuant transaction was:
| Date | Side | Shares | Price | Turnover | Fee |
|---|---|---|---|---|---|
2024-01-11 |
Buy | 1,000 |
6.57 |
6,570.00 |
5.00 |
That trade hit a minimum fee. The local simulator charged 6.1415 CNY because
the smoke runner used a flat 10 bps cash cost on adjusted-price turnover.
Cost Probe Run
- Portfolio:
jq_cost_probe_buy_sell - Window:
2024-01-11to2024-01-12 - Booksize:
1,000,000 CNY - Instrument:
600000.XSHG - Targets:
2024-01-11: buy100,000shares2024-01-12: sell to0shares
Observed JoinQuant transactions:
| Date | Side | Shares | Price | Turnover | Fee | Implied Fee |
|---|---|---|---|---|---|---|
2024-01-11 |
Buy | 100,000 |
6.57 |
657,000.00 |
197.10 |
3.0000 bps |
2024-01-12 |
Sell | -100,000 |
6.51 |
651,000.00 |
846.30 |
13.0000 bps |
The transaction-table numbers match this formula exactly:
buy fee = max(5 CNY, turnover * 0.0003)
sell fee = max(5 CNY, turnover * 0.0003) + turnover * 0.001
In basis points:
- Buy commission:
3 bps - Sell commission:
3 bps - Sell stamp tax:
10 bps - Minimum commission:
5 CNY
No separate transfer fee was visible in this probe. If a separate transfer fee was present as an additional charge, the observed fees would not match the formula above exactly. It may still be folded into JoinQuant's displayed commission field, so this finding should be read as "not separately observable" rather than "impossible".
No slippage was visible. The wrapper submitted open-time market orders with
run_daily(..., time="open"), and JoinQuant filled them at the displayed open
prices.
Evidence From Strategy Logs
The JoinQuant order log for the cost probe showed:
2024-01-11 ... trade price: 6.57, amount:100000, commission: 197.1
2024-01-12 ... trade price: 6.51, amount:100000, commission: 846.3
The normal transaction tab showed the same fee values. However, the generated
wrapper's JOINQUANT_FILL log records had trade_cost: 0.0, even for those
same fills. That means get_trades() did not expose the usable commission
value through the field the wrapper currently reads.
For reconciliation, use the transaction table or JoinQuant order logs for fee
details. Do not rely on the wrapper's current JOINQUANT_FILL.trade_cost.
Difference From The Internal Simulator
The current internal simulator cost model is
SimpleProportionalCostModel in pipeline/portfolio/costs.py:
trade_cost = abs(traded_shares * execution_price)
* (cost_bps + slippage_bps) / 10000
The smoke runner used:
cost_bps = 5slippage_bps = 5- combined one-way cash cost:
10 bps
Important differences:
- The internal simulator uses the same rate for buys and sells.
- It has no minimum commission.
- It has no sell-only stamp tax.
- Slippage is modeled as an extra cash cost.
- JoinQuant did not show slippage in the observed open-time fills.
- The local smoke download used
adjust="qfq", while the JoinQuant wrapper setset_option("use_real_price", True). That price-scale mismatch also affects PnL and cost comparisons.
Practical Implications
For a buy-only smoke test, JoinQuant may charge less than the local model when
the trade is large enough for 3 bps to apply, but it may charge more on small
orders because of the 5 CNY minimum.
For any test with sells, JoinQuant's default sell fee is materially higher than
the current local flat model because of the inferred 10 bps stamp tax.
The earlier 30-day buy-and-hold discrepancy was small because only one buy was executed. A rebalancing strategy with many sells will show a larger cost-model difference unless the local simulator is configured to match JoinQuant.
Recommended Follow-Ups
- Add a JoinQuant-style cost model to the internal simulator:
commission = max(min_commission, turnover * commission_bps / 10000)
stamp_tax = turnover * sell_stamp_tax_bps / 10000 for sells only
trade_cost = commission + stamp_tax
-
Add a CLI option or preset for
portfolio simulate, for example--cost-model joinquant-stock. -
Update JoinQuant reconciliation to parse fee values from the transaction table or order logs when CSV exports are unavailable.
-
Run a second local-vs-JoinQuant comparison with:
- raw or real-price local bars, not adjusted-price bars
- JoinQuant-style costs
- slippage disabled locally
That test should isolate remaining differences to data alignment, price source, rounding, and JoinQuant internal execution behavior.
Local Artifacts
The temporary artifacts from the investigation are:
/tmp/chinese-equity-quant-jq-long/comparison_report.md/tmp/chinese-equity-quant-jq-long/parsed_joinquant/daily_pnl_compare_from_positions_tab.csv/tmp/chinese-equity-quant-jq-cost-probe/jq_cost_analysis_report.md/tmp/chinese-equity-quant-jq-cost-probe/jq_cost_analysis_summary.json/tmp/chinese-equity-quant-jq-cost-probe/jq_cost_probe_transactions_parsed.csv/tmp/chinese-equity-quant-jq-cost-probe/detail_tabs/transactions.txt