导图社区 backtestingpy源码解析
backtestingpy是常用的量化回测框架,本思维导图描绘了该框架的代码执行细节,能够帮助量化研究人员更深刻理解event-based backtesting的技术细节
编辑于2023-08-06 06:57:31 天津市backtesting.py 源码解析
_Broker 执行orders
成员变量
_data:外界传进来的dataframe
可见长度由外界控制
orders待执行的orders
trades尚未平仓的trades
closed_trades已经平仓的trades
position
_equity:各步的账户余额,包括现金+股票损益
_adjusted_price 根据开仓方向用commission修正价格

new_order
获取市价=当天close再adjust

单子的价格只可能是limit(限制单)或stop(止损单)或市价

如果开long,sl<单子价格<tp
如果开short,tp<单子价格<sl
判断orde是否隶属于trade
是contingent 插在orders头部,优先执行
否则插入order队尾
如果_exclusive_orders 要cancel之前所有order 要close之前所有trade
next
len(self._data) - 1返回的当前时刻的index 而非最后一步的index
_process_orders:处理当前bar待处理的orders
记录当前步骤后的equity
如果equity<=0
不是没有现金那么简单 而是现金+亏损为负,资不抵债,破产
按市价关闭所有trade
抛_OutOfMoneyError
_process_orders 处理当前所有orders
获取bar的价格

这个函数可以认为是在当前bar close之后再执行 所以data[-1]上的所有OHLC都可以使用
data[-1]是下单日的下一天,所以data[-2]就是下单日当天 prev_close就是下单日当天的收盘价
不太可能按下单日的收盘价成交
遍历所有orders
确定成交价格
如果有stop_price就是止损单 检查这个stop_price在当前bar上是否会被触发
如果order is stop-long,说明原始order是short, 所以high > stop_price涨得太高了,才启动这个stop-long order
否则order is stop-short,说明原始order是long, 只有跌得足够低了,才启动stop-short order
如果有limit_price就是限价单

如果不考虑stop_limit这种矛盾的order 就拿open和limit比较
昨天下的long limit order在今天执行
如果open低< limit order,那就按open成交
否则open高开,但是low < limit order, 所以有一个从高open下降至low的过程,在下降过程中的limit处成交
昨天下的一个short limit order
如果open就很高了,就按open成交
否则有一个从open上升到high的过程,在上升至limit时成交
market_order or stop_market order
最常见合理的是昨天单子按今天的open成交

self._trade_on_close按下单日的close成交
如果没有设置stop_price,就是普通market order 按照今天的open成交
昨天下的stop long order 说明我持有short仓位,价格太高时买入止损
如果open就很高,超过了stop_price,就按open成交
否则就是日间才超过了stop_price,就按stop_price成交
昨天下的stop short order 说明我持有long仓位,价格太低时卖出止损
如果open就小于stop price,就按市价出售,就是open
否则open高,所以是在日间跌破了stop price,就按stop price成交
计算出的同一价格price,可以同时是新仓位的开仓价格, 也是老仓位的平仓价格,假设是同时成交
如果order有parent_trade 说明order是contingent 平现有仓位,而不是新开仓位
_reduce_trade
新开仓位
adjusted_price 注意调整价格只发生在开仓价格上,平仓时不用重复调整
如果输入的size是比例,计算买卖share数量 得到need_size

乘以这个比例的基数是margin_available * leverage
如果_hedging=False 即不允许方向不同的交易同时存在
调用_close_trade或_reduce_trade 关闭部分反向交易
如果仍然有足够的流动性

_open_trade
如果设置了sl或tp,可能会在当前bar头部插入new order 设置reprocess_orders = True立刻执行新插入的止盈止损单
如果处理old orders时插入了new orders 重新执行
操作trade
_reduce_trade 关闭全部或部分trade
_close_trade
平仓时的price
设置trade的exit_price
更新cash
_open_trade
设置trade的tp,会在queue头部插入limit order
设置trade的sl,会在queue头部插入stop order
账户信息
equity 当前现金与所有交易损益之和

在买股票的时候并没有扣除现金
只在_close_trade时才更新现金
margin_available 目前还可用的流动性,考虑了杠杆

计算已用margin时用的是trade.value,是绝对值 所以short也是占margin的
Backtest
成员变量
_data:DataFrame,外界传入
_broker
_strategy
run
strategy.init
初始化indicator_attrs 即strateg中用到的所有信号向量
这些信号向量肯定与Data长度相等,否则就match不上了 为此头部会有一些NaN
过滤掉这些nan,找到所有信号都有效的位置,start
遍历data的每一行
将数据与indicator的时间游标前进一步 保证其他函数在调用[-1]时正确返回当前时间对应的数据 防止使用未来数据

broker.next() 执行前边积累下来的orders
strategy.next() 会产生新的orders,留待明天再执行
可以认为这两个函数都是在今天的bar close后执行 所以今天bar的OHLC都可用
并非说是在close后才执行order 执行可能是在日内 但是在close后才结算、检查当天的价格是否允许发生limit or stop order
把所有剩下的trades都close掉,好统计回测结果
调用compute_stats统计回测结果
输入broker._equity
策略
基类Strategy
I
 
最常见的是自定义函数返回一个pd.Series 必须与OHLC有相同的长度,保证对齐

不用担心自定义信号状况会出现Nan的问题
buy & sell:只是size是否取反的区别
size是小数,或者整数 如果是整数,单位就是shares

SignalStrategy
init
调用set_signal将传入的信号变成indicator
好处1:backtest会自动管理indicator的长度 [-1]就是当前时刻的indicator,不会泄漏未来数据
好处2:plot时会自动画出来
某时刻没有买卖信号,可以写0,会自动替换成nan
next
先卖 exit_portion>0是关掉所有long trades exit_portion<0是关掉所有short trades
后买 entry_size>0是买,<0是卖
TrailingStrategy
init
调用set_atr_periods计算每个时刻的ATR 一定要保证长度与OHLC保持gggc
next
循环所有还开仓的trades
如果是多头
新止损价是当前close向下移动若干ATR
long这里stop-loss设置的是价格下限,所以max(新老sl), 每次将下限抬高,收窄风险
如果空头
新止损价是当前close向上移动若干ATR
short这里stop-loss设置的是价格上限,所以min(新老sl), 每次将上限压低,收窄风险
Position 对broker中open状态trades的封装
size:所有开仓的总size
pl:所有开仓的总损益
pl_pct:所有开仓的百分比损益 但是在计算权重时有误

close:所有开仓都平仓
Trade
sl: get / set当前trade的止损价格
__sl_order 当前交易的止损单
止损价格就是sl_order的stop
tp: get / set当前trade的止盈价格
__tp_order 当前交易的止盈单
止盈价格就是tp_order的limit
由Trade自动创建止盈止损单 __set_contingent

价格变化,老的止盈止损单会被cancel
调用__broker.new_order创建止盈止损单 单子的parent_trade是当前trade
开仓量是-size
trade的sl就是sl_order的stop
trade的tp就是tp_order的limit
close
portion:可以关闭部分仓位
开一个与当前size反向的单子 而且插到broker.orders头部,优先执行
尽管优先,也会按下一个bar的open执行
账户信息

pl:损益的钱数,有正有负
pl_pct
value:持仓市值(绝对值)
计算时 如果trade还open就用最新收盘价 否则就用平仓退出的价格
price = self.__exit_price or self.__broker.last_price
Order
辨析两组价格
limit_price限价单的门槛价格
stop_price止损单的触发价格
sl_price:止损价格
市价单也可以用止损价格
trade在处理sl时创建一个stop order 并且将sotp order的stop_price设置成这个sl
tp_price:止盈价格
市价单也可以有止盈价格
trade在处理tp时创建一个limit order 并且将limit order的limit_price设置成这个tp
__parent_trade
普通的market order是没有的
只有trade根据sl / tp自动创建的sl_order / tp_order 才设置这个属性
实现is_contingent属性
True for [contingent] orders, i.e. [OCO] stop-loss and take-profit bracket orders placed upon an active trade. Remaining contingent orders are canceled when their parent `Trade` is closed.
compute_stats
统计回撤

dd是每步距离之前equity高点的回撤百分比

max_dd=dd.max()
dd_dur是每步距离之前equity高点的回撤时间
Calmar Ratio=annualized_return / -max_dd 翻盘年数的倒数
Exposure Time 拥有open trade占整个回测时段的百分比

Return是策略收益 由首尾equity计算得到
Buy & Hold Return 用OHLC数据中首尾close计算
HPY
年化收益与波动

先由每步的equity计算出daily return
按复利计算出年化收益
基于day_returns计算出天级波动再年化
Sharpe & Sortino Ratio

计算Sortino Ratio时用clip排除上行波动 计算volatility时用了简化算法
Win Rate 所有交易中盈利交易的次数占比

Profit Factor盈亏比 盈利交易的总return / 亏损交易的总return

平均收益
Avg. Trade是所有交易Return的几何平均
Expectancy是所有交易pl_pct的算术平均
SQN:System Quality Number

Kelly Criterion

plot
输入

results: pd.Series compute_stats的输出

df是原始的OHLC数据
画trade数据
trade_source代表每次trade

index属性是ExitBar
cmap和trades_cmap描述trade 根据returns_positive在COLORS挑颜色
_plot_pl_section
上三角代表long的trade
下三角代表short的trade
颜色是cmap也就是根据return_positive决定红绿
三角大小只是代表size大小,并非PL的大小
scatter的X是平仓的位置
_plot_ohlc_trades 把每个交易的 (开仓日,开仓价格)到(平仓日,平仓价格)画一条粗线
画OHLC+Equity
source代表OHLC数据
inc_cmap是OHLC的颜色,根据CO大小挑颜色
_plot_volume_section
_plot_superimposed_ohlc 聚合成更粗粒度的OHLC再画图
粒度转换规则resample_rule

数据聚合方法OHLCV_AGG

_plot_ohlc
segment画HL vbar画OC
_plot_equity_section 画equity曲线
source插入equity数据
缺省relative_equity=True 也就是初始净值=1

peak是equity最大的点
final是最后一个equity的点
最大回撤点:equity_data['DrawdownPct']最大
dd_start, dd_end是最大回撤的始末两节点
_plot_drawdown_section 单独画出每个时刻的drawdown并联成线
其他
_Array
重载了__float__返回序列的最后一位