Python back testing system for trading strategies, customized for China market.
- Real A-share trading rules: commission (0.03% + ¥5 minimum) + stamp duty (sell-side 0.1%) + transfer fee (Shanghai 0.002%) + 100-share lot rounding + T+1 restriction + slippage.
- Unified data layer: eastmoney/sina dual-source failover, Parquet local cache, incremental update, normalized columns
[date, open, high, low, close, volume, amount]. - Core metrics: total return, annualized return, max drawdown, Sharpe ratio, win rate, profit/loss ratio, average trade pnl.
- Benchmark comparison: CSI 300 by default, automatic Alpha / Beta / excess cumulative return.
- Parameter optimization: cartesian grid search via backtrader
optstrategy. - Layered architecture: data (
data.py) → engine (engine.py) → orchestration (mainland.py) → renderer (renderer.py) → UI (ui.py).
This repository is tested on Python 3.10+, backtrader 1.9+ and AkShare 1.4+.
pip install cn-trader$ git clone https://github.com/codfish-zz/cn-trader
$ cd cn-trader
$ pip install -r requirements.txtOr
$ git clone https://gitee.com/huui/cn-trader.git
$ cd cn-trader
$ pip install -r requirements.txt>>> from cn_trader import ui
>>> ui.main()from cn_trader.stock.mainland import Mainland
m = Mainland()
m.strategy_check_sma_cross(
symbol="000002",
start_date="20210101",
end_date="20240101",
pfast=10,
pslow=30,
print_log=True,
)The output contains two rich tables — Backtest Summary and Core Metrics — covering Sharpe ratio, max drawdown, win rate, profit/loss ratio, plus Alpha / Beta / excess return against CSI 300.
from cn_trader.stock.data import get_daily, get_index_daily, get_benchmark_returns
# Daily K-line for a stock (with local cache + incremental update)
df = get_daily(symbol="000002", start_date="20210101", end_date="20240101", adjust="qfq")
# Daily K-line for CSI 300
bench = get_index_daily(index_code="000300", start_date="20210101", end_date="20240101")
# Daily return series of the benchmark
bench_rets = get_benchmark_returns("000300", "20210101", "20240101")import backtrader as bt
from cn_trader.stock.data import get_daily
from cn_trader.stock.engine import BacktestEngine
from cn_trader.stock.strategy.sma import SmaCross
df = get_daily(symbol="000002", start_date="20210101", end_date="20240101", adjust="qfq")
df.index = df["date"]
data = bt.feeds.PandasData(dataname=df)
engine = BacktestEngine()
cerebro, strat, end_cash, metrics = engine.run(
strategy=SmaCross,
data=data,
symbol="000002",
start_cash=1_000_000,
commission=0.0003,
percent_sizer=50,
pfast=10, pslow=30,
print_log=False,
)
print(metrics)from cn_trader.stock.engine import BacktestEngine
from cn_trader.stock.strategy.sma import SmaCross
engine = BacktestEngine()
results = engine.optimize(
strategy=SmaCross,
data=data,
symbol="000002",
start_cash=1_000_000,
commission=0.0003,
percent_sizer=50,
opt_kwargs={"pfast": [5, 10, 15], "pslow": [20, 30, 40]},
print_log=False,
)
# results: list[list[Strategy]]; read metrics via strat.analyzers.sharpe.get_analysis()
# to pick the best parameter combination.| Variable | Default | Description |
|---|---|---|
CN_TRADER_CACHE_DIR |
~/.cn_trader/cache |
Data cache directory |
CN_TRADER_USE_CACHE |
1 |
Enable local cache; 0/false to disable |
CN_TRADER_BENCHMARK |
000300 |
Benchmark index code; empty to disable |
MAX_RANK |
3 |
(legacy) max rank |
Run a single test case:
$ python -m unittest tests.test_data.DataLayerTestCase.test_adapt_eastmoney_columnsRun all non-network tests (data / engine / metrics / benchmark / optimization / A-share rules / util):
$ python -m unittest tests.test_data tests.test_ashare_rules tests.test_engine_metrics tests.test_utiltests.test_stock_mainland still calls the real akshare API and requires network access.
cn_trader/stock/
├── data.py # Data layer: akshare adapters / column normalization / Parquet cache / incremental / failover / index
├── commission.py # A-share commission scheme: commission + stamp duty + transfer fee + ¥5 minimum
├── sizer.py # A-share sizer: percent-based, rounded down to 100-share lots
├── strategy/base.py # Strategy base: T+1 restriction / order notifications / logging
├── metrics.py # Metrics extraction: Sharpe / MaxDD / win rate / annualized / Alpha / Beta
├── engine.py # Backtest engine: Cerebro assembly / run / optimize
├── renderer.py # Result rendering: rich table summary + candlestick chart
└── mainland.py # Orchestration: thin shell wiring data + engine + renderer
cn_trader/ui.py # Interactive CLI: StrategyConfig + generic param collector
- Create a subclass under
cn_trader/stock/strategy/inheriting fromcn_trader.stock.strategy.base.Strategy. - Implement
next()withself.buy()/self.sell()(T+1 is enforced automatically). - Append a
StrategyConfigentry to theSTRATEGY_CONFIGSregistry incn_trader/ui.py; the UI picks it up automatically. - Add a
strategy_check_xxxentry onMainland(or callMainland._backtestdirectly).
Apache License 2.0