FinMarketPy / 回测全链路

finmarketpy 回测:四步链路、九个内置指标与一张参数表

官方最短路径只有四步:MarketDataRequest 描述数据 → fetch_market 取回行情 → TechIndicator.create_tech_ind 生成信号 → Backtest.calculate_trading_PnL 跑回测,最后 TradeAnalysis 出统计。看上去简单,但决定结果量级的是 BacktestRequest 上那一串参数:成本写几个 bp、信号延后几天、杠杆上限多少、年化因子用 252 还是别的——这些官方 README 没有列表,本站从 0.11.19 源码里抽出来整理成一页。

两类入口:Backtest(底层)/ TradingModel(模板)内置指标 9 种示例成本 2.5bp年化因子示例值 252
1 描述数据MarketDataRequest
2 取回行情fetch_market
3 生成信号create_tech_ind
4 跑回测calculate_trading_PnL
5 出统计TradeAnalysis
最小回测链路示意(依据官方示例 finmarketpy_examples/backtest_example.pybacktest/ 源码);示意非官方流程图,参数与行为以仓库代码为准。
FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · FinMarketPy · Two entry points

Backtest 和 TradingModel 有什么区别:先决定用哪一个

同一份源码里有两套入口,源码实测:Backtest 是底层类(直接吃信号 DataFrame),TradingModel 是在它之上的模板(负责装载参数与统一流程)。

对比项Backtest(底层类)TradingModel(高层模板)
信号从哪来你自己传入一个 signal DataFrame在类内部按 tech_indTechParams 生成,或由子类实现
参数怎么给方法参数里直接传 br(BacktestRequest)load_parameters(br) 装载,另有 load_assets(br=None)save_model(path) / load_model(path)
核心调用calculate_trading_PnL(br, asset_df, signal_df, contract_value_df)模板方法 construct_strategy(br=None, …),内部再调底层
适合场景信号来自外部(你自己算的因子、外部指标库、事件信号)标准的技术指标策略,想少写重复代码
官方示例backtest_example.py(G10 趋势跟踪、EURUSD 单品种)tradingmodelfxtrend_example.pytradingmodelfxtrend_bbg_example.py
注意点四个位置参数顺序固定:br, asset_df, signal_df, contract_value_df模型可序列化保存,便于把一次实验的参数存档
信号必须自己防未来函数:官方示例直接把自己生成的 signal_df 传进引擎——引擎不会替你检查信号是否用了当日收盘后才有的信息。源码里内置指标的实现是带滞后处理的(例如 SMA 分支用 data_frame.shift(1).rolling(window=sma_period-1).sum() 再加当日值),但你自己写的信号没有这层保护。核对方法见回测可信度体检
Walkthrough

四步链路怎么走:官方最小示例逐段拆解

下面的代码全部来自官方 backtest_example.py(0.11.19 发布包),只做顺序整理与注释,没有改写逻辑。

  1. 取数:MarketDataRequest + Market.fetch_market

    md_request = MarketDataRequest(start_date="01 Jan 1989", finish_date=datetime.date.today(), freq='daily', data_source='alfred', tickers=tickers, fields=['close'], vendor_tickers=vendor_tickers, vendor_fields=['close'], cache_algo='internet_load_return', fred_api_key=fred_api_key),然后 asset_df = Market(market_data_generator=MarketDataGenerator()).fetch_market(md_request)。此时 asset_df 就是后面回测的行情底表。

  2. 声明回测口径:BacktestRequest

    br = BacktestRequest() 后赋值:br.start_date / br.finish_datebr.spot_tc_bp = 2.5(2.5bp 买卖价差)、br.ann_factor = 252;波动率目标组:br.signal_vol_adjust = Truebr.signal_vol_target = 0.05br.signal_vol_max_leverage = 3br.signal_vol_periods = 60br.signal_vol_obs_in_year = 252br.signal_vol_rebalance_freq = 'BM'br.signal_vol_resample_freq = None

  3. 生成信号:TechIndicator + TechParams

    tech_params = TechParams(); tech_params.sma_period = 200; indicator = 'SMA'tech_ind.create_tech_ind(spot_df, indicator, tech_params)signal_df = tech_ind.get_signal()。示例里还可以加 tech_params.only_allow_longs = True 做单边约束。

  4. 执行回测:calculate_trading_PnL

    backtest.calculate_trading_PnL(br, asset_df, signal_df, contract_value_df=None, run_in_parallel=False)。随后三个取结果的方法:portfolio_cum()(组合累计曲线)、portfolio_signal()(各品种持仓)、pnl()(逐品种 PnL);portfolio_pnl_desc() 可取组合描述文字,常用来拼图表标题。

  5. 统计与出图:TradeAnalysis + chartpy

    统计:TradeAnalysis().run_strategy_returns_stats(trading_model);逐笔交易收益可用 findatapy 的 Calculations().calculate_individual_trade_gains(signals, returns)。出图:Style() 上设 titlesourcescale_factor = -1file_outputauto_scale = True,再 Chart().plot(port, style=style)

官方示例用的标的:九对 G10 货币——EURUSD、USDJPY、GBPUSD、AUDUSD、USDCAD、NZDUSD、USDCHF、USDNOK、USDSEK,对应 FRED/ALFRED 的 DEXUSEU 一类序列代码。示例注释同时提醒:这里是用现货价算收益,更好的做法是用包含 carry 的外汇总收益指数
Indicators

内置哪些技术指标(9 种)与它们的参数

官方 README 没有列指标清单。下表由 economics/techindicator.py 源码实读得出:create_tech_ind(data_frame_non_nan, name, tech_params, data_frame_non_nan_early=None),按 name 分派。

name 取值含义对应参数信号形态注意点
SMA简单移动平均sma_period价格与均线的关系决定多空实现里对历史部分用了 shift(1) 滚动求和,属防未来函数设计
SMA2双均线组合sma2_period短均线相对长均线官方示例里常用 200 周期单均线,双均线要自己设参数
EMA指数移动平均ema_period同 SMA 逻辑对近期价格更敏感,换手通常更高
ROC变动率(动量)roc_period按动量方向典型动量策略的入口
GMMA顾比复合移动平均gmma_period_list多组均线的排列参数是列表,需按源码要求传一组周期
RSI相对强弱指标rsi_periodrsi_lowerrsi_upper超买/超卖,可设上下阈值源码注释写的是「RSI 退出超买时买、退出超卖时卖」——阈值语义要按注释理解
BB布林带bb_periodbb_mult价格与上下轨的关系倍数参数决定带宽,直接影响信号频率
ATR真实波动幅度atr_period常与止损/止盈配合更像风控输入而非方向信号
VWAP成交量加权均价—(按数据字段)价格相对 VWAP需要数据里有成交量字段,仅取收盘价会缺输入

通用参数:fillna

tech_params.fillna 控制是否对输入做前向填充(源码里走 ffill())。行情有缺口时它会改变信号序列,回测结果也会跟着变——所以这一项要写进你的可复现记录。

通用参数:signal_mult

信号倍数,用来放大或缩小信号强度;配合 only_allow_longs / only_allow_shorts 可以限制交易方向。单边约束会显著改变回测的换手与回撤结构。

通用参数:strip_signal_name

控制信号列名的处理方式。当你要把多个信号拼成一张表、再用 BacktestComparison 对比时,列名规则不一致会增加拼表成本,建议统一。

Parameters

BacktestRequest 常用参数有哪些:决定结果量级的那一组

BacktestRequest 继承自 findatapy 的 MarketDataRequest,在 0.11.19 里有近 60 个属性。全部列出没有意义,下面按「不设就一定会错」的顺序挑出关键组。

参数组代表属性作用示例值不设的后果 / 注意点
表本身trading_field指定用哪个价格字段参与回测close用错字段(收盘价 vs 结算价)会让结果整体偏移
交易成本spot_tc_bp(现货成本 bp)、spot_rc_bp(roll 成本 bp)把买卖价差与展期成本折进收益2.5不设成本=零摩擦回测,趋势类策略会被系统性高估
信号时点signal_delay信号生效前延后几个周期按数据频率设不延后=当日信号当日成交,容易构成未来函数
波动率目标(逐信号)signal_vol_adjustsignal_vol_targetsignal_vol_max_leveragesignal_vol_periodssignal_vol_obs_in_yearsignal_vol_rebalance_freqsignal_vol_resample_freq按估计波动率把仓位缩放到目标风险0.05 / 3 / 60 / 252 / 'BM'不设最大杠杆,低波动期仓位会被放得极大
波动率目标(组合层)portfolio_vol_* 同名一组 + portfolio_vol_resample_type对组合整体做目标波动控制同上结构逐信号与组合层同时开要清楚谁是外层约束
仓位与敞口max_net_exposuremax_abs_exposureposition_clip_*portfolio_notional_sizeportfolio_combination_weights限制净敞口/总敞口、裁剪单次调仓、定义名义规模与组合权重按资金规模设不裁剪会出现不现实的满仓换手
风控开关stop_losstake_profit止损/止盈按波动幅度设开了它们之后交易笔数与胜率口径都会变
统计口径ann_factorresample_ann_factorcum_index年化因子与累计方式(乘法/加法)252'mult'年化因子与数据频率不匹配会让年化收益/波动率全错
输出开关calc_statswrite_csvwrite_csv_pnlplot_interiminclude_benchmarkplot_start / plot_finish是否算统计、是否落 CSV、是否画中间图、是否带基准、绘图区间按需不开基准就没法判断「策略是在赚钱还是在跟风」
这一组就是「可复现」的全部内容。把上表里你实际用到的值抄下来贴进报告,别人才能复现你的结果——这也是官方资料没有提供、而回测可信度体检那一页要解决的问题。
Outputs

跑完之后:统计、报告与对比

回测只给你净值,判断好坏要靠这三个输出工具。

工具方法产出适合回答的问题注意点
TradeAnalysisrun_strategy_returns_stats(trading_model, engine="finmarketpy")收益统计表这个策略的收益、波动、回撤大致什么水平统计口径由 BacktestRequest 决定,换口径要重跑
TradeAnalysisrun_excel_trade_report(trading_model, excel_file='model.xlsx')Excel 文件要把结果交给别人看 / 存档默认文件名就是 model.xlsx,注意别覆盖上一轮结果
TradeAnalysisrun_day_of_month_analysis(trading_model, resample_freq='B')按日/按月的分解收益是不是集中在某几天(月末效应、数据发布日)resample_freq 用 pandas 频率串,写错会静默给出不同分组
BacktestComparisonplot_pnl(diff=True, silent_plot=False, reduce_plot=True)PnL 对比图改一个参数后,策略差异出在哪diff=True 看差值,False 看两条曲线
BacktestComparisonplot_sharpe(silent_plot=False, reduce_plot=True)Sharpe 对比图风险调整后哪个更好Sharpe 依赖年化因子与收益频率,比较前先确认两边口径一致
findatapy Calculationscalculate_individual_trade_gains(signals, returns)逐笔交易收益收益是不是靠少数几笔撑起来的这是判断「结论是否稳健」最直接的一步
chartpyChart().plot(port, style=style)图片文件 / 交互图要看曲线形态出图失败通常是 chartpy 引擎或字体问题,不是回测问题
FAQ

回测链路常见问题

类名、方法与参数以 0.11.19 源码与官方示例为准;本站未实机运行。

我没有 FRED key,能不能跑官方示例?

官方 backtest_example.py 默认用 data_source='alfred',需要一把 免费的 FRED API key(在 FRED 网站申请)。没有 key 时最简单的替代是自己准备一份 DataFrame 行情表,绕过取数步骤直接调用 calculate_trading_PnL;也可以改用 findatapy 支持的其它免费源。具体可用源与凭证路径见数据源与凭证

回测里怎么算「交易成本」才算合理?

官方示例给的是 br.spot_tc_bp = 2.5,也就是按 2.5 个基点计买卖价差,另有 spot_rc_bp 用于展期成本(外汇远期持有成本)。合理值取决于你的品种与频率:日频 G10 现货用个位数 bp 量级、日内高频要更高。本站不给具体推荐值——写清你用了多少、为什么用这个值,比选一个「标准值」更重要。

回测结果里出现非常大的杠杆,正常吗?

先检查波动率目标这一组参数。signal_vol_adjust=True 时,仓位由「目标波动率 ÷ 估计波动率」决定,如果没设 signal_vol_max_leverage,低波动期算出的仓位会非常大。官方示例里设的是 3。此外 max_net_exposure / max_abs_exposureposition_clip_* 也参与约束,这一组要一起看。

我想用 RSI 或布林带,需要额外装指标库吗?

不需要。TechIndicator 内置 9 种:SMA、SMA2、EMA、ROC、GMMA、RSI、BB、ATR、VWAP,参数集中在 TechParams(如 rsi_periodrsi_lowerrsi_upperbb_periodbb_mult)。注意 RSI 的阈值语义——源码注释写的是「退出超买时买、退出超卖时卖」,和常见教科书写法不完全一样,用之前先读一眼实现。

怎么把两个策略放在一张图上比?

BacktestComparisonplot_pnl() 看 PnL 差异(diff=True 看差值曲线),plot_sharpe() 看风险调整后的对比。前提是两次回测的统计口径一致(年化因子、成本、信号延迟都要写同样的值),否则比出来的是「口径差异」而不是「策略差异」。这也正是本站强调先做口径体检的原因。

它能不能做参数敏感性扫描(比如 SMA 从 50 到 300)?

源码与发布说明里能看到并行与敏感性相关的能力:calculate_trading_PnLrun_in_parallel 参数,TradeAnalysis 也有针对性的分析入口,官方发布说明里提到过为回测与敏感性分析加并行特性「在 Linux 上表现更好」。但要注意 marketconstants.pybacktest_thread_no 的平台默认值是 linux: 8 / windows: 1 / mac: 8——Windows 下默认只有 1 个线程,大规模扫描会很慢,详见报错与版本边界

回测出来的收益能代表以后吗?

不能。回测只说明「在这段数据、这套参数、这个成本假设下会发生什么」。它与实盘之间还差着滑点、容量、数据可得性、执行时点等一堆现实因素。本站不提供任何收益预期,也不评价第三方文章的具体数字;看到数字时请按体检清单自己复现一遍再采信。

跑通之后该做什么:先给结果做体检

同一套参数换个口径就能把结论反过来。回测可信度体检那一页把官方没有的检查项整理成三层清单与一份可复现协议。