BigTrader - 回测与交易引擎
由qxiao创建,最终由jliang 被浏览 1343 用户
什么是BigTrader
BigTrader是宽邦科技推出的致力于为用户提供便捷、功能强大的交易引擎。
在量化研究的过程中,量化研究员(宽客)需要在历史数据里回放模拟,验证策略效果,这就是BigTrader交易引擎的应用场景。
主要功能: 量化策略编写、回测分析、仿真模拟和实盘交易。
支持品种: 股票、基金、期货、可转债、指数;未来会支持期权、债券、两融等。
交易频率: 日线、分钟、Tick、逐笔。
交易引擎的优势:
- 回测研究贴近真实 交易,最大程度保证回测的准确性
- 回测研究、模拟交易、实盘交易为同一套代码,无需做任务修改
- 交易引擎是一个有体系、结构化的工程框架,能大幅提升策略开发的效率
交易引擎介绍
BigQuant回测引擎为时间驱动回测引擎,当设定好每一根K线后,会根据K线频率读取数据。如当在回测引擎中设置为daily时,回测引擎会按照天从传入数据读取。
回测引擎的运行逻辑为每一根K线运行一次,在当前K线进行下单操作时会在下一根K线开始撮合成交。
由于BigQuant模块间传参均使用内置DataSource格式,以下是将【DataFrame】与【DataSource】互转的方式以及读取方式。
\
DataFrame转DataSource
#创建DataFrame
df = pd.DataFrame([])
#将DataFrame转为DataSource
ds = DataSource.write_df(df)
DataSource转DataFrame
#例如某个模块为m1,将m1的输出转为DataFrame.
#将 m1模块内的data(DataSource)赋值给变量df
ds = m1.data
#将DataSource 转为 DataFrame
df = ds.read()
\
回测引擎的调取
可视化版本
搜索hftrade, 确认模块版本为 HFTrade.v2, 可直接在该模块内进行代码修改。
代码版本
# 交易引擎:初始化函数,只执行一次
def m1_initialize_bigquant_run(context):
# 加载预测数据
pass
# 交易引擎:每个交易日开盘前调用一次。
def m1_before_trading_start_bigquant_run(context, data):
# 盘前处理,订阅行情等
pass
# 交易引擎:tick数据处理函数,每个tick执行一次
def m1_handle_tick_bigquant_run(context, tick):
pass
# 交易引擎:bar数据处理函数,每个时间单位执行一次
def m1_handle_data_bigquant_run(context, data):
pass
# 交易引擎:成交回报处理函数,每个成交发生时执行一次
def m1_handle_trade_bigquant_run(context, trade):
pass
# 交易引擎:委托回报处理函数,每个委托变化时执行一次
def m1_handle_order_bigquant_run(context, order):
pass
# 交易引擎:盘后处理函数,每日盘后执行一次
def m1_after_trading_bigquant_run(context, data):
pass
m1 = M.hftrade.v2(
instruments=["000001.SZA", "600000.SHA"],
start_date='2023-01-03',
end_date='2023-01-06',
initialize=m1_initialize_bigquant_run,
before_trading_start=m1_before_trading_start_bigquant_run,
handle_tick=m1_handle_tick_bigquant_run,
handle_data=m1_handle_data_bigquant_run,
handle_trade=m1_handle_trade_bigquant_run,
handle_order=m1_handle_order_bigquant_run,
after_trading=m1_after_trading_bigquant_run,
capital_base=1000000,
frequency='minute',
price_type='真实价格',
product_type='股票',
before_start_days='0',
volume_limit=1,
order_price_field_buy='open',
order_price_field_sell='close',
benchmark='000300.HIX',
plot_charts=True,
disable_cache=False,
replay_bdb=False,
show_debug_info=False,
backtest_only=False
)
\
回测引擎启动参数
以下为模块版本为 HFTrade.v2的回测引擎启动参数介绍:
参数 | 数据类型 | 参数功能 | 调用方法 |
---|---|---|---|
instruments | DataSource | 回测引擎会自动订阅日期内股票的日线行情,同时会根据日线时间区间运行回测引擎。 | context.instruments |
options_data | DataSource | 回测引擎接入options_data后,可从options_data读取预测数据,并且根据该数据进行下单。 | context.options |
frequency | str | 可填参数**'daily’’minute’’tick’**,会根据填入的参数更新引擎内置运行时间,以此为依据提取数据,达到切换频率的效果。 | data.current_dt |
\
回测引擎函数介绍
回测引擎内置了几类主要函数,会按照顺序依次运行。用户可通过可视化或代码方式对函数内容进行更改,最终实现通过回测引擎达到回测的目的。以下是各类函数的运行逻辑。
函数名称 | 函数英文名 | 运行逻辑 |
---|---|---|
初始化函数 | initialize_bigquant_run | 启动回测引擎时运行,只运行一次 |
盘前处理函数 | before_trading_start_bigquant_run | 每日开盘前运行一次 |
K线处理函数 | handle_data_bigquant_run | 每根K线运行一次,日频回测时每日运行一次,分钟回测时每分钟运行一次 |
tick处理函数 | handle_tick_bigquant_run | 每个tick运行一次,仅在频率为’tick’时生效,股票一般3s一次,期货一般 500ms 一次 |
盘后处理函数 | after_trading_bigquant_run | 每日盘后运行一次 |
\
重要函数用法示例
初始化函数
初始化函数主要用于设置回测引擎的实例参数,通过实例参数可以在之后的模块中实现交易中的需求。
以下是一个示例,假如我们传入context.options
是一个字典,字典中data对应着DataSource文件,这时我们可通过下列方法将数据存入回测引擎并设置交易周期。
# m7_ 为模块编号前缀
def m7_initialize_bigquant_run(context):
#=========================== 加载预测数据 ===================================
#传入的options是一个字典,字典中'data'对应着DataSource文件,我们可将DataSource文件转为DataFrame中并存入回测引擎
context.ranker_prediction = context.options['data'].read_df()
context.ranker_prediction.set_index('date',inplace=True) #用日期作为索引,可提高之后数据读写速度
#=======================================功能设置
# 设置最大买入股票为5只
context.buy_bum = 5
# 设置股票交易费率(仅做股票回测时)
from bigtrader.finance.commission import PerOrder
context.set_commission(PerOrder(buy_cost=0.00015, sell_cost=0.00115, min_cost=5))
# 设置期货交易费率(仅做期货回测时)
from bigtrader.finance.commission import PerContract
context.set_commission(futures_commission=PerContract(cost={'IF': (0.000023, 0.000115, 0.000023), 'RB': (2, 2, 2)}))
# 股票 T+1 交易(默认就是True)
context.set_stock_t1(True)
\
盘前处理函数
盘前处理函数通常用于订阅数据,回测引擎内置自动订阅股票日线数据。当我们需要使用高频数据,如需要分钟数据时则订阅K线,需要tick数据时则订阅tick数据。我们每日都需要提前订阅当天的行情数据,来实现最终的下单以及收益的计算。
以下是我们订阅股票分钟行情的示例:
def m7_before_trading_start_bigquant_run(context, data):
#订阅股票分钟数据
#获取全部需要订阅的股票代码
instruments = context.instruments
# 分钟回测:订阅分钟行情(一般我们只订阅当天需要的分钟数据的股票池,否则可能非常占用内存和时间)
context.subscribe_bar(instruments, '1m')
# Tick回测:订阅Tick行情
context.subscribe(instruments)
context.subscribe_bar(instruments: List[str], frequency: str)
实例方法参数:
参数 | 是否必选 | 内容 |
---|---|---|
instrument | 必选 | 标的代码的列表 |
frequency | 必选 | 可通过更改参数选择不同的行情进行订阅,可选 ‘1d’ , ‘1m’ |
\
K线处理函数
K线处理函数会在每根K线运行一次,通常我们会在该函数内进行K线数据读取,并且下单。
以下是一个我们使用该函数进行数据并下单的示例:
def m7_handle_data_bigquant_run(context, data):
"""
context: StrategyContext 是交易引擎策略上下文对象,提供下单、获取资金持仓等对象接口
data: BarDatas 是K线集合对象,提供了访问当前时间,K线价格等接口
"""
#创建买入列表
buy_list = []
#==================== 数据准备
#获取当前时间 pydatetime 类型
time = data.current_dt
#将时间格式转为年月日
date = data.current_dt.date()
#读取预测数据,根据时间进行索引,再将索引重置。
try:
today_data = context.ranker_prediction.loc[date,:]
today_data.reset_index(inplace=True)
except:
return
#假如我们需要选择最小市值的n只股票,n已在初始化模块中设置,以下是实现方法。
#获得当日目标持仓
buy_num = context.buy_num
today_data = today_data.sort_values(by='市值',ascending=True)
buy_list = today_data.instrument.tolist()[:buy_num]
#获取目前持仓列表
holding_list = list(context.get_positions().keys())
#先卖出已持仓股票不在目标持仓中的
for ins in holding_list:
if ins not in buy_list:
context.order_target(ins, 0)
#买入目标持仓中,未持仓的股票
for ins in buy_list:
if ins not in holding_list:
context.order_percent(ins, 0.02)
\
实例方法介绍
查询实例方法
查询单只标的持仓信息
当查询单只标的持仓信息时,可使用 context.get_position(instrument: str, direction = Direction.NONE)
来获取该标的信息。
实例参数 | 参数类型 | 是否必填 | 参数描述 |
---|---|---|---|
instrument | str | 是 | 查询传入参数标的的持仓信息 |
示例:
def m7_handle_data_bigquant_run(context, data):
instrument = '000001.SZA'
#获取股票持仓信息
pos = context.get_position(instrument)
print(pos)
通过以上代码,将返回一个字典,该字典数据格式如下:
StockPosition(bkt000,002875.SZA,LONG,current_qty:2100,avail_qty:0,cost_price:9.44,last_price:9.44,margin:0.0)
返回值 | 返回值含义 | 获取方式 |
---|---|---|
current_qty | 当前持仓 | context.get_position(ins).current_qty |
avail_qty | 可用持仓 | context.get_position(ins).avail_qty |
cost_price | 持仓成本 | context.get_position(ins).cost_price |
last_price | 最新价格 | context.get_position(ins).last_price |
margin | 保证金占用 | context.get_position(ins).margin |
last_sale_date | 最后交易日 | context.get_position(ins).last_sale_date |
\
查询多只标的持仓信息
当查询多只标的持仓信息时可以使用 context.get_positions()
来获取标的的信息。
实例参数 | 参数类型 | 是否必须 | 参数描述 |
---|---|---|---|
instrument | list[str] | 是 | 标的的列表 |
direction | Direction | 否 | 可通过 direction=Direction.LONG/SHORT进行持仓方向过滤 |
示例:
def m7_handle_data_bigquant_run(context, data):
instruments = ['000001.SZA','000002.SZA']
#获取股票持仓信息
pos = context.get_positions(instruments)
print(pos)
通过以上代码,可以获得一个如下字典:
{'603176.SHA': StockPosition(bkt000,603176.SHA,LONG,current_qty:0,avail_qty:0,cost_price:0.0,last_price:2.97,margin:0.0),
'600768.SHA': StockPosition(bkt000,600768.SHA,LONG,current_qty:2000,avail_qty:0,cost_price:9.72,last_price:9.57,margin:0.0)}
返回值 | 返回值含义 | 获取方式 (注: instrument为股票代码,instrument_list为股票代码列表) |
---|---|---|
keys() | 全部持仓列表 | context.get_positions(instrument_list).keys() |
current_qty | 持仓数量 | context.get_positions(instrument_list).instrument.current_qty |
avail_qty | 可用持仓 | context.get_positions(instrument_list).instrument.avil_qty |
cost_price | 持仓成本 | context.get_positions(instrument_list).instrument.cost_price |
last_price | 最新价格 | context.get_positions(instrument_list).instrument.last_price |
last_sale_date | 最后交易日 | context.get_positions(instrument_list).instrument.last_sale_date |
\
查询当前日期
可以通过下列方法获取当前日期,主要用于从引擎中提取当日数据使用.
通常我们在初始化时将全部数据读取进入引擎,并将index转为日期形式,在每日k线处理函数运行时,通过data.current_dt
获取当前日期。通过当前日期从全部数据中获取当日数据。
示例:
#初始化模块中将全部数据
def m7_initialize_bigquant_run(context):
#=========================== 加载预测数据 ===================================
#传入的options是一个字典,字典中'data'对应着DataSource文件,我们可将DataSource文件转为DataFrame中并存入回测引擎
#将全部数据存入 all_data.并重新设定索引
context.all_data = context.options['data'].read_df()
context.all_data.set_index('date',inplace=True) #用日期作为索引,可提高之后数据读写速度
def m7_handle_data_bigquant_run(context, data):
#获取当前时间 pydatetime
time = data.current_dt
#将时间格式转为年月日
date = data.current_dt.date()
#读取数据,根据时间进行索引,再将索引重置。
try:
today_data = context.all_data.loc[date,:]
today_data.reset_index(inplace=True)
except:
return
\
查询投资组合信息
可以通过使用 context.portfolio
查询当前账户信息
实例方法 | 返回值含义 | 获取方式 |
---|---|---|
positions_value | 持仓市值 | context.portfolio.positions_value |
portfolio_value | 总资产(资金+持仓市值) | context.portfolio.portfolio_value |
cash | 可用资金 | context.portfolio.cash |
positions | 当前持仓字典 | context.portfolio.positions |
\
查询当前标的价格
可通过使用 data.history()
获取股票当前价格。
参数 | 类型 | 是否必须 | 参数含义 |
---|---|---|---|
instrument | str | 是 | 需要获取的代码 |
fields | str or list[str] | 是 | 需要获取的字段,如 “close” 或 [“close“, “volume“] |
bar_count | int | 是 | 需要的Bar条数 |
frequency | str | 默认 ‘1d’ | 可选 ‘1d’ ‘1m’ ,按照该频率返回数据 |
以下是一个在回测引擎中获取当日股票分钟数据的实例:
def m7_handle_data_bigquant_run(context, data):
#获取股票代码
instrument = '000001.SZA'
now_time = context.current_df
#获取该股票截止至当前分钟的全部日内分钟数据
df = data.history(instrument, ["close", "volume"], 30, '1m')
\
下单实例方法
按数量下单
可通过 context.order(instrument, volume, price=0, offset=Offset.NONE, **kwargs)
方式进行按数量下单。
实例参数 | 参数类型 | 是否必选 | 参数含义 |
---|---|---|---|
instrument | str | 是 | 需要交易的代码 |
volume | int | 是 | 需要交易的数量,>0表示买入,<0表示卖出 |
price | float | 否 | 交易的价格,默认为0表示市价单 |
order_type | OrderType | 默认为MARKET市价单 | 下单类型 LIMIT:限价单 MARKET:市价单 |
offset | Offset | 开平方向,默认为None | OPEN:开仓 CLOSE:平仓 CLOSETODAY平今 |
以下是一个使用context.order()
下单的示例:
def m7_handle_data_bigquant_run(context, data):
#获取股票代码
instrument = '000001.SZA'
#获取当前K线的价格
price = data.current(instrument, "close")
#获取当前账户现金
cash = context.portfolio.cash
#计算买入数量
buy_num = cash//price
#买入
context.order(instrument, buy_num)
\
按目标持仓金额下单
可通过 context.order_value(instrument, value, price=0, **kwargs)
方式进行按目标持仓金额下单。
参数类型 | 是否必选 | 参数含义 | |
---|---|---|---|
instrument | str | 是 | 需要买入的代码 |
value | float | 是 | 需要买入的金额 |
price | float | 否 | 交易的价格,默认为0表示市价单 |
order_type | OrderType | 默认为MARKET市价单 | 下单类型 LIMIT:限价单 MARKET:市价单 |
以下是一个使用 context.order_value()
下单的示例:
def m7_handle_data_bigquant_run(context, data):
#获取股票代码
instrument = '000001.SZA'
#获取当前价格
price = data.current(instrument, "close")
#获取当前账户现金
cash = context.portfolio.cash
#计算要买入的现金,假如在这里为半仓买入
buy_cash = cash * 0.5
#买入
context.order_value(instrument, buy_cash)
\
按照目标持仓百分比下单
当买入某只标的是,如希望最终可以按照某百分比持仓。可通过 context.order_percent(instrument, percent, price=0, **kwargs)
方式进行按目标持仓百分比下单,通常用于买单。
参数类型 | 是否必选 | 参数含义 | |
---|---|---|---|
instrument | str | 是 | 需要买入的标的代码 |
percent | float | 是 | 目标持仓百分比 |
price | float | 否 | 交易的价格,默认为0表示市价单 |
order_type | OrderType | 默认为MARKET市价单 | 下单类型 LIMIT:限价单 MARKET:市价单 |
以下是一个使用 context.order_percent()
下单的示例:
def m7_handle_data_bigquant_run(context, data):
#获取股票代码
instrument = '000001.SZA'
#买入50%仓位
context.order_percent(instrument, 0.5)
\
按目标持仓数量下单
如希望将持仓变动到某个固定数量时可通过 context.order_target(symbol, target, price=0, order_type=OrderType.LIMIT)
实现,通常用于清空某只标的。
参数类型 | 是否必选 | 参数含义 | |
---|---|---|---|
instrument | str | 是 | 需要买入的标的代码 |
target | int | 是 | 目标持仓数量 |
price | float | 否 | 交易的价格,默认为0表示市价单 |
order_type | OrderType | 默认为MARKET市价单 | 下单类型 LIMIT:限价单 MARKET:市价单 |
以下是一个使用 context.order_target()
平仓的示例:
def m7_handle_data_bigquant_run(context, data):
#获取全部持仓股票代码
holding_list = list(context.get_positions().keys())
#全部平仓
for ins in holding_list:
context.order_target(ins, 0)
\
其他实例方法
行情订阅
可通过context.subscribe_bar()
订阅行情;
订阅K行情后,可在K线处理函数中使用 data.history
的方式获取行情数据。
实例参数 | 数据类型 | 是否必须 | 参数含义 |
---|---|---|---|
instrument | list[str] | 是 | 需要订阅的标的代码 |
frequency | str | 否 默认’1m’ | 需要订阅的频率,通常为 ‘1d’ ‘1m’ |
\
策略回测架构
使用BigTrader交易引擎进行策略回测时采取的是事件驱动机制,策略回测有特定的架构,架构包含以下多个事件函数。
名称 | 说明 |
---|---|
initialize | 策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置等 |
before_trading | 策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等 |
handle_bar | 当根bar行情通知函数,每根bar时间周期会触发,包括日线和分钟。注册多个合约时,每个合约都会调用一次handlebar。handlebar和handle_data不能同时使用。 |
handle_data | 行情通知函数,频率支持日线和分钟。注册多个合约时,handle_data会等待所有合约数据到齐后统一触发一次。例如多合约套利时,需要同时处理多个合约建议用handle_data。handle_data和handlebar不能同时使用。 |
handle_tick | Tick快照行情通知函数,每个标的的行情有变化时则会触发。 |
handle_l2trade | 逐笔成交行情更新时的处理函数 |
handle_order | 委托回报通知函数,每个订单状态有变化时会触发。 |
handle_trade | 成交回报通知函数,有成交时会触发。 |
\
主要事件函数说明
有两个使用频率很高的函数:initialize函数和handle_data(handle_bar)函数,理解了这两个函数开发策略就再也不是什么难事了,结合下面K线图来理解这两个函数。
从图中可以看出,其实一共有26个事件,即26根K线,第一根K线既对应黑色箭头,又对应灰色箭头,其余都只对应灰色箭头。Initialize函数只在第一个事件上调用,即第一根K线,因此很多初始设置可以放在Initialize函数里面。每个K线都对应灰色箭头,表示每个事件都会调用handle_data函数,即从第一根K线到最后一根K线都会运行handle_data一次,于是很多策略逻辑部分就可以放在handle_data里。
\
策略典型代码结构
下图给出了一个典型策略需要的代码结构
from biglearning.api import M
#给每个策略取一个名字
STRATEGY_NAME = "STRATEGY_name"
def initialize(context):
"""策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置和全局使用数据"""
#输出关键日志
msg = "initialize:"
context.write_log(msg, stdout=1)
def before_trading(context, data):
"""盘前处理,策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等"""
#输出关键日志
msg = "before_trading dt:{}".format(data.current_dt)
context.write_log(msg, stdout=1)
#如果需要处理tick数据,需要添加tick处理函数:handle_tick(context, tick):
def handle_data(context, data):
"""Bars行情通知函数,每个bar时间周期会触发,包括日线和分钟"""
#输出关键日志
msg = "handle_bar dt:{}".format(data.current_dt)
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报通知函数,每个订单状态有变化时会触发"""
#输出关键日志
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报通知函数,有成交时会触发"""
#输出关键日志
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
backtest_result = M.hftrade.v2(
instruments=['000002.SZA'],
start_date='2022-01-01',
end_date='2023-01-01',
handle_data=handle_data,
initialize=initialize,
volume_limit=0.025,
order_price_field_buy='open',
order_price_field_sell='close',
capital_base=1000000,
frequency='daily',
price_type='真实价格',
product_type='股票',
plot_charts=True,
backtest_only=False,
benchmark='000300.HIX'
)
通用说明
- 该框架对标的,均是 代码.后缀 的形式,如 000001.SZA, 600000.SHA, RB2110.SHF,注意这里期货代码时,统一为 大写产品代码+4位年月+后缀,而目前后缀主要有:DCE, CZC, SHF, INE, CFX。其中,郑商所的实际交易代码为 SR109,但在该平台中为 SR1109 或 SR2109,即补全为4位年月的数字。
- 回测时,平台内部会自动读取历史行情进行回测,当然也支持外部传入自定义历史数据进行回测。
\
策略运行与撮合说明
回测代码的编写和运行
策略逻辑编写完成后通过接口函数 M.hftrade (也是一个可视化模块的入口)来进行回测,如下是此函数的详细说明
M.hftrade.v2( #v2表示hftrade的版本号
start_date, #回测开始日期
end_date, #回测结束日期
instruments=None, #回测股票/基金/期货列表
initialize=None, #初始化函数初始化函数,initialize(context)
before_trading_start=None, #在每个交易日开始前的处理函数,before_trading_start(context, data)
handle_data=None, #数据更新时的处理函数, handle_data(context, data)
handle_tick=None, #在每个Tick快照行情更新时的处理函数,handle_tick(context, tick)
handle_l2trade=None, #在每个逐笔成交行情更新时的处理函数,handle_l2trade(context, l2trade)
handle_trade=None, #在成交回报更新时的处理函数,handle_trade(context, trade)
handle_order=None, #在委托回报更新时的处理函数,handle_order(context, order)
after_trading=None, #策略运行结束处理函数,after_trading(context)
capital_base=1000000, #初始资金,默认为 1000000
volume_limit=0.25, #执行下单时控制成交量参数,若设置为0时,不进行成交量检查;默认值是0.25,下单量如果超过该K线成交量的2.5%,多余的订单量会自动取消
product_type=None, #回测产品类型,如 stock/future/option等,一般不用指定,系统自动根据合约代码判断产品类型
price_type=None, #回测复权类型,如 真实价格[real],后复权[post]
frequency='1m', #回测频率,如 1d/1m/tick/
benchmark='000300.HIX', #benchmark:回测基准数据, 可以是DataSource,DataFrame,或者股票/指数代码如000300.
plot_charts=True, #是否画回测评估图
options=None, #其他参数从这里传入,可以在 handle_data 等函数里使用
disable_cache=0, #默认0,表示不启用数据缓存。设置为1,如果本次回测的数据范围之前已经读取过则不会重复读取,这样能加快回测速度
show_debug_info=False #默认False,表示是否打印回测框架的debug信息
)
在BigQuant平台上,新建一个空白的代码策略模板,把编写好的策略逻辑代码和回测代码拷贝进去(也可以建好模板后,直接在模板的notebook里面编写代码)。
点击”全部运行”按钮就可以进行回测了。
\
订单撮合处理
本章节主要介绍基于历史行情的模拟撮合介绍,主要包括基于Bar行情、快照行情、逐笔行情撮合。支持上交所和深交所两大交易所上市的A股股票、基金、债券、期权、期货等品种。不支持新股申购、市值配售、增发申购、配股等交易。股票交易费用买入按0.03%计算,卖出按0.13%计算(其中默认包含0.1%的印花税),期货则按对应的品种费率计算,实际费率也可在回测模拟中设置。
通用规则
- 对于没有成交的委托,或者部分成交的委托,可以撒单。当天的委托如果没有成交,收市以后自动作废,不参加下一交易日的撮合。
- 对于市价委托,未成交部分会自动撤销(即成剩撤)。
基于Bar行情数据撮合规则
- 成交参考价格可指定为open/close,但是一般分钟回测时,买卖参考价格都为下一分钟的开盘价 open。
- 成交数量最大为当次bar的成交量,日线是一般还会加一个成交率比例。
- 委托量过大时,可能出现模拟撮合结果与真实情况严重失真的情况。
基于快照行情数据的撮合规则
主要原则是基于行情中的最新价撮合,而不是买卖盘口的价格撮合,发出订单后,使用下一笔快照行情撮合。
- 买入
- 如果最新成交价等于委托价,按照委托价成交。
- 如果最新成交价低于委托价,按照最新价成交。
- 若成交价在买一价或涨停时,不能即时成交,委托会放入撮合等待队列,并且记录当时买一量,如果阶段成交量大于买一量,可成交数量是阶段成交量和当时的买一量的差,以这种方式模拟在真实交易市场排队的情形。但未及时考虑买一上的撤单量。
- 如果涨停板被打开,价格低于委托价,则按照现价成交。
- 卖出
-
如果最新成交价等于委托价,按照委托价成交
-
如果最新价高于委托价,按照最新价撮合成交 注意,若成交价在卖一价或跌停时,不能即时成交,委托会放入撮合等待队列,并且记录当时卖一量,如果阶段成交量大于卖一量,可成交数量是阶段成交量和当时的卖一量的差,以这种方式模拟在真实交易市场排队的情形。但未及时考虑卖一上的撤单量。
-
如果跌停板被打开,价格高于委托价,则按照现价成交。 当次成交量按两个快照之间的真实成交量计算,若成交量为0,则不成交。如果真实成交数量小于委托未成交数量,则部分成交,仅撮合真实交易的成交数量,剩余的委托仍保留在撮合队列,等待新的成交明细。因此早上集合竞价期间的报单,会在 09:25:00 进行一次撮合,或 09:30:00开始进行连续竞价撮合。
例如:600804(鹏博士)上午开市后涨停,用户在10:10以涨停价委托买入100手,此时的成交量是51000手,涨停板上买一的单子是5000手,如果涨停板没有被打开,只有阶段成交量大于5000手时,用户的委托才等到可以成交。如果成交量到了56010手,则用户成交10手(56010-51000-5000),剩下的部分等待更多的成量。
-
基于逐笔成交数据的撮合规则
主要原则是基于逐笔行情中的最新成交价撮合,发出订单后,使用下一笔逐笔行情撮合,需要配合当前快照盘口信息(会在收到委托时,获取当前盘口)。
- 买入委托
- 市价委托
- 当前在涨停版上,不成交,自动撤销
- 成交,即成剩余,成交量也依次从卖一到卖五档
- 不支持FOK
- 委托价格 > 最新价:
- 成交,成交价=最新价,成交量依次从卖一到卖十档,生成多笔成交信息,FIXME:如果超出卖十档的处理,其中价格触发到涨停板的处理。
- 委托价格 == 最新价
- 成交类型主动卖,判断当前记录的排队数量,排队数量 <= 0,则成交,成交量为当次逐笔成交量,FIXME: 更逼真模式是取 逐笔成交-排队数量
- 成交类型主动买,不成交
- 撤单成交(仅适用于深交所)
- 撤单价格 == 买一价,对订单队列中的买入委托的排队数量做减少
- 其它撤单,不做处理
- 其它不成交
- 市价委托
- 卖出委托:
- 市价委托:
- 当前在跌停版上,不成交,自动撤销
- 成交,即成剩余,成交量也依次从买一到买五档
- 不支持FOK
- 委托价格 < 最新价:
- 成交,成交价=最新价,成交量依次从买一到买十档,生成多笔成交信息,FIXME:如果超出买十档的处理,其中价格触发到跌停板的处理。
- 委托价格 == 最新价:
- 成交类型主动买,判断当前记录的排队数量,排队数量 <= 0,则成交,成交量为当次逐笔成交量,FIXME: 更逼真模式是取 逐笔成交-排队数量
- 成交类型主动卖,不成交
- 撤单成交(仅适用于深交所)
- 撤单价格 == 卖一价,对订单队列中的买入委托的排队数量做减少
- 其它撤单,不做处理 其它不成交
- 市价委托:
\
回测结果分析
当我们完成一个策略回测时,我们会得到如下的一个图形:
上图为策略回测结果图,红色矩形标记部分包含了策略的主要信息,包括 收益概况、交易详情、每日持仓及收益、输出日志 。接下来,我们详细介绍这几个部分。
收益概况
收益概况以折线图的方式显示了策略在时间序列上的收益率,黄色曲线为策略收益率。同时也显示了沪深300收益率曲线作为比较基准,蓝色曲线为基准收益率。同时,最下面的绿色曲线为持仓占比,持仓占比即仓位,10%的持仓占比表示账户里股票价值只占10%。相对收益率的曲线并没有直接绘制在图上,点击图例 相对收益率,就可以将其绘制出来。
不仅如此,衡量一个策略好坏的关键指标在收益概览页面也得到展示。
- 收益率:策略整个回测时间段上的总收益率。比如,如果收益率为30%,表明起始时间是1万的本金,结束时间本金就变成1.3万了,一共赚了3000元。
- 年化收益率:该策略每一年的收益率。比如,如果回测时间段为2年,总收益率为30%,那么每年的年化收益率就在15附近(不考虑复利)。
- 基准收益率:策略需要有一个比较基准,比较基准为沪深300。若基准收益率为15%,表明在整个回测时间段,大盘本身就上涨了15%,如果策略收益率小于基准收益率,说明策略表现并不好,连大盘都没有跑赢。
- 阿尔法:衡量策略的一个重要指标,该值越大越好。
- 贝塔:衡量策略的一个重要指标,该值越小越好。
- 夏普比率:衡量策略最重要的一个指标,该指标的计算不仅考虑收益率,还考虑了风险,因此比较具有参考价值,可以理解为经过风险调整后的收益率。
- 胜率:衡量策略盈利一指标,胜率越大越好。比如10次投资中有8次获利,胜率就是80%。
- 盈亏比:衡量策略盈亏能力大小比较,盈亏比越大越好。比如投资盈利时,平均每次盈利4元,亏损时,平均每次亏损2元,那么此时的盈亏比为2。
- 收益波动率:收益率的标准差,是风险的一个指标。
- 最大回撤:策略在整个时间段上亏损最严重的时候相比净值最高值下跌的百分比。如果最大回撤为20%,表明策略在某个时间点上,相比之前的净值最高点下降了20%。最大回撤是策略评估时非常关键的一个指标,通常与风险承受能力相关。
- 信息比率:信息比率也是一个常用的策略评价指标
- 策略回测结果指标详解
\
交易详情
交易详情主要显示了策略在整个回测过程中每个交易日的买卖信息。包括买卖时间、股票代码、交易方向、交易数量、成交价格、交易成本。具体见下图:
每日持仓及收益
每日持仓及收益主要呈现每日持有股票代码、当日收盘价、持仓股票数量、持仓金额、收益等指标。具体见下图:
关于回测结果和指标更详细的分析可参照链接:策略回测结果指标详解
{{heading_numbering_zhCN}}