什么是大类资产配置ETF基金交易策略
原文链接
这边我用通俗易懂的语言重新理解一下大类资产配置策略。大类资产配置策略是投资管理中国一种基于投资组合理论的策略。其主要目的就是把资产分散在不同类型的资产之间,比如股票、债券、现金及现金等价物、不动产、大宗商品以及其他替代投资品种。资产配置的目标是利用不同资产类别间的非完全相关性来降低整体投资组合的波动性和风险,同时寻求合理的回报。
大类资产配置策略的盈利逻辑主要基于以下几点:
- 分散风险:不同资产类别通常具有不同的风险回报特性和市场表现。
- 相关性:资产之间的相关性衡量了它们价格变动的同步性。
- 战略与战术配置:战略资产配置基于长期的市场预期和投资目标,而战术资产配置则涉及根据短期市场预期和条件对资产配置进行调整。
- 成本效率:选择成本效率高的投资方式,如低费率的指数基金或ETF,可以减少交易和管理费用,从而提高投资组合的整体回报。
在中国,对于散户来说要实现大类资产配置,最便捷的途径就是买场内的ETF基金。
策略构建
该策略主要配置的ETF有8个,涵盖了国内股票、香港股票、国外股票、商品、黄金、债券市场,读取数据用到的表是基金行情表,表名为cn_fund_bar1d。
- 商品ETF 510170.SH
- 日经ETF 513520.SH
- 德国指数ETF 513030.SH
- 纳斯达克ETF 159941.SZ
- 黄金ETF 518880.SH
- 恒生ETF 159920.SZ
- 沪深300ETF 510300.SH
- 国债ETF 511010.SH
cn_fund_bar1d数据表介绍
数据链接
该表详细介绍了基金的后复权日行情信息,包括基金的交易代码、名称、交易日期、价格波动(开高低收、前收盘价、张跌幅)、交易量(成交量、成交额、换手率)、交易笔数以及市场极端情况下的价格(涨停价、跌停价)。此外,累积后复权因子的记录对于分析基金的长期表现尤为关键。
@DataBot …好的,针对 cn_fund_bar1d - 基金后复权日行情
数据表,我将提供详细的数据字段描述以及使用建议,以帮助您进行量化投资策略研究和开发。
数据表:cn_fund_bar1d
- 基金后复权日行情
描述: 该表详细记录了基金的后复权日行情信息,包括基金的交易代码、名称、交易日期、价格波动(开高低收、前收盘价、涨跌幅)、交易量(成交量、成交额、换手率)、交易笔数以及市场极端情况下的价格(跌停价、涨停价)。此外,累积后复权因子的记录对于分析基金的长期表现尤为关键。
关键数据字段:
-
instrument
:基金代码 (例如:159915.SZ
),是基金的唯一标识符。 -
date
:日期 (格式:YYYY-MM-DD
),表示行情的交易日。 -
open
:开盘价(后复权),当日开盘时的价格。 -
high
:最高价(后复权),当日交易中的最高价格。 -
low
:最低价(后复权),当日交易中的最低价格。 -
close
:收盘价(后复权),当日收盘时的价格。 -
pre_close
:前收盘价,前一个交易日的收盘价格。 -
change_ratio
:涨跌幅,当日收盘价相对于前收盘价的变动百分比。计算公式为:涨跌幅 = 当日收盘价 − 前收盘价 前收盘价 × 100 % 涨跌幅 = \frac{当日收盘价 - 前收盘价}{前收盘价} \times 100\% 涨跌幅=前收盘价当日收盘价−前收盘价×100%
-
volume
:成交量,当日的总成交股数或手数。 -
amount
:成交额,当日的总成交金额。 -
turn
:换手率,当日的成交量与流通股本的比率。 -
adjust_factor
:累计后复权因子,用于计算后复权价格的调整因子,考虑了基金的分红、拆分等因素。 -
deal_number
:交易笔数,当日发生的交易次数。 -
upper_limit
:涨停价,当日的涨停价格。 -
lower_limit
:跌停价,当日的跌停价格。
使用建议:
- 长期表现分析: 使用
adjust_factor
字段进行后复权处理,可以更准确地分析基金的长期收益和风险。 - 量价关系研究: 结合
volume
(成交量)和价格字段(open
、high
、low
、close
)可以研究量价关系,例如成交量放大是否伴随价格上涨。 - 波动率分析: 使用
high
和low
字段计算每日的价格波动幅度,用于衡量基金的波动率。 - 趋势跟踪: 使用收盘价
close
计算均线等技术指标,进行趋势跟踪策略的研究。 - 极端行情过滤: 使用
upper_limit
和lower_limit
字段可以识别和过滤极端行情,避免异常值对策略的影响。
DAI SQL 示例:
以下是一些使用 DAI SQL 查询 cn_fund_bar1d
表的示例,可以帮助您快速上手:
-
查询指定基金的日行情数据:
import dai instrument_id = '159915.SZ' # 替换成您感兴趣的基金代码 result = dai.query("SELECT date, open, high, low, close, volume FROM cn_fund_bar1d WHERE instrument = '{}'".format(instrument_id), filters={"date": ["2023-01-01", "2023-12-31"]}).df() print(result)
-
计算指定时间段内所有基金的平均涨跌幅:
import dai result = dai.query("SELECT date, AVG(change_ratio) AS avg_change_ratio FROM cn_fund_bar1d", filters={"date": ["2023-01-01", "2023-12-31"]}).df() print(result)
-
计算指定基金的N日移动平均价
import dai instrument_id = '159915.SZ' # 替换成您感兴趣的基金代码 N = 20 # N日移动平均 sql = f""" SELECT date, AVG(close) OVER (ORDER BY date ASC ROWS {N-1} PRECEDING) AS moving_average FROM cn_fund_bar1d WHERE instrument = '{instrument_id}' """ result = dai.query(sql, filters={"date": ["2023-01-01", "2023-12-31"]}).df() print(result)
注意事项:
- 在进行回测时,务必使用后复权价格,以消除分红等因素对收益的影响。
- 注意数据的时间范围,确保您的策略在所选时间段内有足够的数据支持。
主要思想
- 按固定分配好的权重买入并持有
- 权重分配:一共8个标的,其中国债波风险较小,所以我们配置更大的权重,商品ETF权重是0.3,其余ETF是0.1,权重之后等于1。
代码构建讲解
输入特征:从数据表中固定选取所要买的8只股票
先介绍一下,BigQuant平台有种数据提取方式,表达式方式和sql语句方式,二者选其一就行,如果选了表达式就要填expr,expr_filters等等这些值。其中:
- expr:表达式特征,我们选取后复权的开盘价和收盘价作为特征
- expr_filters:表达式过滤条件,固定筛选这8只基金。
- expr_tables:表达式-默认数据表
- extra_fields:其他字段,会和expr合并起来一起使用,非特征值一般放在这里。
- order_by:表达式排序字段
- expr_drop_na: 表达式移除空值,True代表移除。
- sql:如果要使用sql语法,在sql模式下构建特征,更加灵活,功能更全面
- extract_data:抽取数据,如果要抽取数据,则返回一个BDB DataSource,包含数据Dataframe
- m_cached:启用缓存加速,模块只在缓存失效的情况下运行,如果存在有效缓存,则使用上一次的缓存数据。
- v30:代表官方将这个函数已经更新到了第30版。
m6 = M.input_features_dai.v30(
mode="""表达式""",
expr="""close
open""",
expr_filters="""
instrument in ('510170.SH', '513520.SH', '513030.SH', '159941.SZ', '518880.SH', '159920.SZ', '510300.SH','511010.SH')""",
expr_tables="""cn_fund_bar1d""",
extra_fields="""date, instrument""",
order_by="""date, instrument""",
expr_drop_na=True,
sql="""""",
extract_data=False,
m_cached=False,
m_name="""m6"""
)
写完上述代码之后,相应的在平台会有可视化模块,点开某个模块,表达的内容也是函数的传参。
抽取数据
- sql:传入的数据
- start_date:开始日期,用于替换sql中的开始日期
- start_date_bound_to_trading_date:开始日期绑定交易日,在模拟和实盘交易模式下,开始日期替换为交易日
- end_date:结束日期
- end_date_bound_to_trading_date:结束日期绑定交易日,在模拟和实盘交易模式下,结束日期替换为交易日
- before_start_days:历史数据向前取的天数,历史数据向前取的天数,实际开始日期会减去此天数,用于计算需要向前历史数据的因子,比如 m_lag(close,10),需要向前去10天数据
- keep_before:保留向前所取天数的数据
- debug:调试模式
- m_cached:启用缓存加速
# @module(position="-351,-836", comment="""抽取预测数据""")
m3 = M.extract_data_dai.v19(
sql=m6.data,
start_date="""2019-01-01""",
start_date_bound_to_trading_date=True,
end_date="""2025-02-10""",
end_date_bound_to_trading_date=True,
before_start_days=90,
keep_before=False,
debug=False,
m_cached=False,
m_name="""m3"""
)
高性能回测
这个模块最为繁琐,下面一个一个来介绍:
- data:输入数据
- start_date: 开始日期
- end_date: 结束日期
- initialize:初始化函数,[回调函数]初始化函数,整个回测中只在最开始时调用一次,用于初始化一些账户状态信息和策略基本参数,context也可以理解为一个全局变量,在回测中存放当前账户信息和策略基本参数便于会话。
- before_trading_start:盘前处理函数,[回调函数」选择实现的函数,每个单位时间开始前调用一次,即每日开盘前调用一次。你的算法可以在该函数中进行一些数据处理计算,比如确定当天有交易信号的股票池。
- handle_tick:Tick处理函数,[回调函数]选择实现的函数,该函数每个Tick会调用一次。一般策略的交易逻辑和订单生成体现在该函数中。
- handle_data:K线处理函数,[回调函数选择实现的函数,该函数每个单位时间会调用一次,如果按分钟,则每分钟调用一次。在交易中,可以通过对象data获取单只股票或多只股票的时间窗口价格数据。一般策略的交易逻辑和订单生成体现在该函数中。
- handle_trade:成交回报处理函数,[回调函数]选择实现的函数,该函数在每个订单成交发生时会调用一次。
- handle_order:委托回报处理函数,[回调函数]选择实现的函数,该函数在每个订单状态变化时会调用一次。
- after_trading:盘后处理函数,[回调函数】选择实现的函数,在当日交易结束后调用一次。
- capital_base:初始资金
- frequency:数据频率:日线(daily),分钟线(minute),快照(tick),逐笔(tick2)
- product_type:产品类型:股票(stock),期货(future),期权(option),基金(fund),可转债(cbond),自动(none)
- rebalance_period_type:调仓周期类型
- rebalance_period_days:调仓周期日期,多个日期数值用英文逗号分隔,例如 月度交易日 3,-5,表示每月的第三个交易日和导数第五个交日易
- backtest_engine_mode:回测引擎模式,标准模式python: 基于事件精确回测;高性能模式C++:基于事件高性能精确回测; 向量化模式Vector: 高性能向量化回测,目前只支持部分场景: 自动: 如果极速模式可用,优先选择极速模式,如果是高频回测,优先选择高性能C++回测
- before_start_days:历史数据向前取的天数
- volume_limit:成交率限制,执行下单时控制成交量参数,默认值2.5%,若设置为1时,不进行成交量检查
- order_price_field_buy:买入点,open=开盘买入,close=收盘买入
- order_price_field_sell:卖出点,open=开盘卖出,close=收盘卖出
- benchmark:基准指数
- plot_charts:显示回测结果图表
- debug:调试模式
- backtest_only:是否只在回测模式下运行
# @module(position="-333,-710", comment="""""", comment_collapsed=True)
m1 = M.bigtrader.v37(
data=m3.data,
start_date="""2019-01-01""",
end_date="""2025-04-22""",
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="""daily""",
product_type="""基金""",
rebalance_period_type="""交易日""",
rebalance_period_days="""1""",
rebalance_period_roll_forward=True,
backtest_engine_mode="""标准模式""",
before_start_days=0,
volume_limit=1,
order_price_field_buy="""open""",
order_price_field_sell="""open""",
benchmark="""沪深300指数""",
plot_charts=True,
debug=False,
backtest_only=False,
m_name="""m1"""
)
初始化函数介绍
# 交易引擎:初始化函数,只执行一次
def bigquant_run(context):
from bigtrader.finance.commission import PerOrder
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.00015, sell_cost=0.00015, min_cost=0))
#国债放大持有仓位倍数
context.BOND_ETF_LEVERAGE = 3
print('当前交易标的:', context.instruments)
#每标的购买资金
context.max_cash_per_instrument =context.portfolio.portfolio_value/(len(context.instruments)-1+context.BOND_ETF_LEVERAGE)
-
context.set_commission(PerOrder(buy_cost=0.00015, sell_cost=0.00015, min_cost=0))
- 含义:设置交易手续费。
context.set_commission()
是一个函数,用于设置交易的佣金(手续费)规则。PerOrder()
表示手续费按每笔订单计算。buy_cost=0.00015
表示买入时,手续费率为 0.015%,即交易金额的 0.00015 倍。sell_cost=0.00015
表示卖出时,手续费率为 0.015%。min_cost=0
表示没有最低手续费。- 目的:模拟真实的交易环境,将交易成本考虑在内,使回测结果更接近实盘交易。
-
context.BOND_ETF_LEVERAGE = 3
- 含义:设置国债 ETF 的杠杆倍数。
context.BOND_ETF_LEVERAGE
是一个自定义变量,用于存储国债 ETF 的杠杆倍数。3
表示国债 ETF 的仓位是其他标的的 3 倍。- 目的:
- 策略倾斜:可能策略制定者认为国债ETF风险较低,或者在策略中具有特殊作用,因此分配更多的仓位。
- 杠杆效应:放大国债ETF的收益或亏损,以达到更好的风险收益比。
-
print('当前交易标的:', context.instruments)
- 含义:打印当前交易的标的列表。
context.instruments
是一个变量,存储了策略中所有交易标的的代码列表。print()
函数用于在控制台输出信息。- 目的:方便查看当前策略所交易的标的,确认标的是否正确。
-
context.max_cash_per_instrument = context.portfolio.portfolio_value/(len(context.instruments)-1+context.BOND_ETF_LEVERAGE)
- 含义:计算每个标的的最大购买资金。
context.portfolio.portfolio_value
表示当前投资组合的总价值。len(context.instruments)
表示交易标的总数。len(context.instruments)-1
其他标的数量,分母中+context.BOND_ETF_LEVERAGE,目的是将国债ETF当成3份仓位来计算。- 目的:
- 资金分配:平均分配资金到每个标的,防止过度集中投资于单个标的,降低风险。
- 仓位控制:限制单个标的的购买金额,避免因个别标的的大幅波动而影响整个投资组合。
- 杠杆调整:国债 ETF 仓位放大后,需要重新计算每个标的最大购买资金,以保证整体仓位的平衡。
为什么要这么设置?
- 手续费设置:考虑交易成本,使回测更真实。
- 国债 ETF 杠杆设置:
- 可能为了利用国债 ETF 的避险特性,在风险较高时增加国债 ETF 的仓位。
- 可能为了放大国债 ETF 的收益,提高投资组合的整体收益率。
- 资金分配设置:
- 为了分散风险,避免过度集中投资。
- 为了平衡不同标的之间的仓位,实现更好的风险收益比。
总的来说,这些设置都是为了更好地模拟真实交易环境,并根据策略的特点进行优化,以达到更好的回测效果和实盘表现。
K线处理函数介绍
# 交易引擎:bar数据处理函数,每个时间单位执行一次
def bigquant_run(context, data):
#获取当前持仓
positions = {e: p.amount for e, p in context.portfolio.positions.items()}
#买入并持有
#每标的购买资金
for instrument in context.instruments:
if instrument not in positions.keys():
cash = context.max_cash_per_instrument
if instrument=='511010.SH':
# 如果是国债ETF,买入时权重大一点
cash = context.max_cash_per_instrument * context.BOND_ETF_LEVERAGE
context.order_value(instrument, cash)
除了初始化函数和K线处理函数之外,该策略没有再设置其他函数
整体代码
from bigmodule import M
# <aistudiograph>
# @param(id="m1", name="initialize")
# 交易引擎:初始化函数,只执行一次
def m1_initialize_bigquant_run(context):
from bigtrader.finance.commission import PerOrder
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.00015, sell_cost=0.00015, min_cost=0))
#国债放大持有仓位倍数
context.BOND_ETF_LEVERAGE = 3
print('当前交易标的:', context.instruments)
#每标的购买资金
context.max_cash_per_instrument = context.portfolio.portfolio_value/(len(context.instruments)-1+context.BOND_ETF_LEVERAGE)
# @param(id="m1", name="before_trading_start")
# 交易引擎:每个单位时间开盘前调用一次。
def m1_before_trading_start_bigquant_run(context, data):
# 盘前处理,订阅行情等
pass
# @param(id="m1", name="handle_tick")
# 交易引擎:tick数据处理函数,每个tick执行一次
def m1_handle_tick_bigquant_run(context, tick):
pass
# @param(id="m1", name="handle_data")
# 交易引擎:bar数据处理函数,每个时间单位执行一次
def m1_handle_data_bigquant_run(context, data):
# 获取当前持仓
positions = {e: p.amount for e, p in context.portfolio.positions.items()}
#买入并持有
#每标的购买资金
for instrument in context.instruments:
if instrument not in positions.keys():
cash = context.max_cash_per_instrument
if instrument=='511010.SH':
# 如果是国债ETF,买入时权重大一点
cash = context.max_cash_per_instrument * context.BOND_ETF_LEVERAGE
context.order_value(instrument, cash)
# @param(id="m1", name="handle_trade")
# 交易引擎:成交回报处理函数,每个成交发生时执行一次
def m1_handle_trade_bigquant_run(context, trade):
pass
# @param(id="m1", name="handle_order")
# 交易引擎:委托回报处理函数,每个委托变化时执行一次
def m1_handle_order_bigquant_run(context, order):
pass
# @param(id="m1", name="after_trading")
# 交易引擎:盘后处理函数,每日盘后执行一次
def m1_after_trading_bigquant_run(context, data):
pass
# @module(position="-392,-943", comment="""""", comment_collapsed=True)
m6 = M.input_features_dai.v30(
mode="""表达式""",
expr="""close
open""",
expr_filters="""-- DAI SQL 算子/函数: https://bigquant.com/wiki/doc/dai-PLSbc1SbZX#h-%E5%87%BD%E6%95%B0
-- 数据&字段: 数据文档 https://bigquant.com/data/home
-- c_pct_rank(short_return) BETWEEN 0.4 AND 0.6
-- rank_returns <= 10
instrument in ('510170.SH', '513520.SH', '513030.SH', '159941.SZ', '518880.SH', '159920.SZ', '510300.SH','511010.SH')""",
expr_tables="""cn_fund_bar1d""",
extra_fields="""date, instrument""",
order_by="""date, instrument""",
expr_drop_na=True,
sql="""""",
extract_data=False,
m_cached=False,
m_name="""m6"""
)
# @module(position="-351,-837", comment="""抽取预测数据""")
m3 = M.extract_data_dai.v19(
sql=m6.data,
start_date="""2020-01-01""",
start_date_bound_to_trading_date=True,
end_date="""2025-04-22""",
end_date_bound_to_trading_date=True,
before_start_days=90,
keep_before=False,
debug=False,
m_cached=False,
m_name="""m3"""
)
# @module(position="-333,-710", comment="""""", comment_collapsed=True)
m1 = M.bigtrader.v37(
data=m3.data,
start_date="""2020-01-01""",
end_date="""2025-04-22""",
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="""daily""",
product_type="""基金""",
rebalance_period_type="""交易日""",
rebalance_period_days="""1""",
rebalance_period_roll_forward=True,
backtest_engine_mode="""标准模式""",
before_start_days=0,
volume_limit=1,
order_price_field_buy="""open""",
order_price_field_sell="""open""",
benchmark="""沪深300指数""",
plot_charts=True,
debug=False,
backtest_only=False,
m_name="""m1"""
)
# </aistudiograph>
回测表现
回测时间:2020-01-01到2025-04-22
后评价
总结
可以看出大类资产配置策略长期看是一个正收益策略,远远超过银行利息,甚至能赶上通货膨胀带来的货币贬值,长期来看年化利率能做到5-8个点。金融学术领域看,大类资产配置是一个比较简单但可以持续收益的盈利策略,交易执行起来最大的障碍就是人的情绪,当下跌的时候是否能坚持,当其他资产涨势凶猛的时候是否能坚持都影响着最终到手的持仓收益。总体来说,这个策略比买银行理财或者买私募FOF产品是强太多了。
未来优化
- 再平衡。定期更新下单个资产的权重
- 使用组合优化,比如风险平价模型、最小化风险模型、最大化收益模型、最大化夏普模型等。
- 结合动量因子做标的择时