量化交易backtrader实践(二)_基础加强篇(1)_数据列表准备与主要实践内容

上一篇回顾

上一篇是数据获取篇,在上一篇里,我们初步接触了backtrader的回测逻辑,重点放在了回测的数据获取的问题上,确保了我们在用合适且有效的正规数据在做回测,我们的目的是要通过backtrader深入讨论量化交易的内容,并在实践中逐步完善自己的交易系统和养成正确的交易思路。

  1. 第1节里讨论了backtrader的数据来源,对pandas的数据要求进行了分析,做到能把相应的数据处理成backtrader接受的格式。
  2. 第2节里通过akshare获取了全股票列表/ETF列表,使用pypinyin库制作了中文名/拼音缩写/代码都能查找的功能,制作了自选股列表并且通过akshare获取了日线的历史行情数据。
  3. 第3节爬取数据是支线拓展与主线任务关系不太。
  4. 第4节结合通达信的应用,为backtrader更深入、多样化应用提供了新的思路。

那么,我们会以数据获取篇为基础,继续我们的backtrader实践。

基础回测加强

01_数据及列表准备

从第一篇已经知道怎样获取用于回测的数据,但单一的数据没有代表性,这里先准备5支股票+2个指数用于后续的实践,并且直接制作函数通过akshare下载并处理数据,存放在df_list中,对于Notebook而言,它的workspace会保存我们在操作过程中的变量,数据和函数,让我们可以在其他格子里代码调用它们。

001_选股

一般在股票软件中都是选股的功能,不同的软件都有其各自的特色。而且现在又出来AI选股的功能,比如通达信的小达,同花顺的问财等。

选股是交易的前置条件,如果选中了一支接下来趋势向上的股票,那么多半最终收益是正的,而你应用的策略胜率和盈亏比都会比较高,否则就是相反,多半是亏损的。

以小达为例,我们可以通过”热点“,”主题“,“板块” 及“热搜” 针对当前的热点及进选股,也可以根据基本面(市盈率,市净率...)或者资金面,或者K线形态(红三兵,出水芙蓉...)以及技术指标策略(MACD金叉死叉...)进行选股操作。

后续我们的策略也会参考着对指标,基本面以及热度进行回测。不过当前,我们需要从5000多支股票中选出5支股票。这里我们采用akshare读取股票热度排名,然后以第500名开始每隔1000名选择一个。

# 获取问财最近交易日的热度排名

import akshare as ak

stock_hot_rank_wc_df = ak.stock_hot_rank_wc(date="20240913")
stock_hot_rank_wc_df

-----------------------
	序号	股票代码	股票简称	现价	涨跌幅	个股热度	个股热度排名	排名日期
0	    1	000536	华映科技	3.63	10.00	30762469.0	1/5359	20240913
1	    2	600611	大众交通	6.76	-9.99	27546732.5	2/5359	20240913
2	    3	000062	深圳华强	29.48	-9.98	27264696.5	3/5359	20240913
3	    4	000158	常山北明	8.30	-4.16	23935850.0	4/5359	20240913
4	    5	600550	保变电气	8.51	9.95	17938728.5	5/5359	20240913
...	...	...	...	...	...	...	...	...
4995	4996	688100	威胜信息	34.31	-0.70	14574.0	4996/5359	20240913
4996	4997	688432	有研硅	8.75	-0.79	14512.5	4997/5359	20240913
4997	4998	600051	宁波联合	5.20	-0.76	14504.0	4998/5359	20240913
4998	4999	688130	晶华微	16.60	-2.24	14503.5	4999/5359	20240913
4999	5000	002200	ST交投	4.86	-0.20	14477.5	5000/5359	20240913
5000 rows × 8 columns

 获取5支股票,使用pandas的列表索引功能获取这5支股票的信息如下:

sel_index = [500,1500,2500,3500,4500]
df_sel = stock_hot_rank_wc_df.iloc[sel_index,:]
df_sel

-----------------------------
	    序号	股票代码	股票简称	现价	涨跌幅	个股热度	个股热度排名	排名日期
500	    501	    001287	中电港	15.51	-2.76	537239.5	501/5359	20240913
1500	1501	002179	中航光电	36.95	-2.28	169509.0	1501/5359	20240913
2500	2501	600860	京城股份	7.53	-1.57	89909.0	2501/5359	20240913
3500	3501	300233	金城医药	10.99	-0.81	50961.5	3501/5359	20240913
4500	4501	002774	快意电梯	5.39	-1.46	26007.5	4501/5359	20240913

通过pandas的tolist()把选中的股票代码列(Series类型)转成列表,得到我们的自选股列表:

myStockList = df_sel.股票代码.tolist()
myStockList 

---------------------
['001287','002179','600860','300233','002774']

002_选指数

除了5支股票外,我们还需要选择2个指数,用于基准。在后续的“评价”里会详细说关于基准日收益的问题,现在只需要知道我们要一个上证指数,一个沪深300指数作为回测股票的对比基准即可。

指数有很多,在通达信里打开市场--主要指数的页面,就会把沪深京的主要指数列出来,从这里我们先记录下这些指数的代码:

  • 上证指数: 000001 (这个就是sh000001,深市的平安银行也是000001(sz000001?))
  • 沪深300指数: 000300
  • 深证成指:399001
  • 创业板指:399006
  • 5年国债指数:000140

仍然可以采用akshare来获取指数的历史行情数据,函数从stock开头换成index开头:

df_index_em = ak.index_zh_a_hist('000300',start_date='20220101')
df_index_em

------------------------
	日期	开盘	收盘	最高	最低	成交量	成交额	振幅	涨跌幅	涨跌额	换手率
0	2022-01-04	4957.98	4917.77	4961.45	4874.53	151534776	3.365170e+11	1.76	-0.46	-22.60	0.48
1	2022-01-05	4907.93	4868.12	4916.28	4851.98	178816100	3.639445e+11	1.31	-1.01	-49.65	0.57
2	2022-01-06	4842.16	4818.23	4857.56	4786.43	157665825	3.217501e+11	1.46	-1.02	-49.89	0.50
3	2022-01-07	4824.32	4822.37	4856.65	4818.19	187139412	3.322189e+11	0.80	0.09	4.14	0.60
4	2022-01-10	4812.23	4844.05	4844.39	4780.82	156211697	3.050338e+11	1.32	0.45	21.68	0.50
...	...	...	...	...	...	...	...	...	...	...	...
651	2024-09-09	3214.80	3192.95	3222.93	3180.93	108673069	1.465161e+11	1.30	-1.19	-38.40	0.35
652	2024-09-10	3193.46	3195.76	3205.10	3171.79	97290078	1.336105e+11	1.04	0.09	2.81	0.31
653	2024-09-11	3187.05	3186.13	3197.34	3174.03	102482110	1.415138e+11	0.73	-0.30	-9.63	0.33
654	2024-09-12	3189.43	3172.47	3201.55	3171.88	92868657	1.363872e+11	0.93	-0.43	-13.66	0.30
655	2024-09-13	3174.03	3159.25	3188.91	3159.25	88336677	1.374489e+11	0.93	-0.42	-13.22	0.28
656 rows × 11 column

003_用宽指ETF代替指数

简单的说,就是可以用ETF在某些情况下代替指数,且ETF是可以交易的。我们可以首先获取到ETF的实时行情,并只取代码,名称和前几列的数据:

import akshare as ak
import pandas as pd

fund_etf_spot_em_df = ak.fund_etf_spot_em()
df_etf = fund_etf_spot_em_df.iloc[:,:5]

---------------------
	代码	名称	最新价	IOPV实时估值	基金折价率
0	159321	黄金股票ETF	0.834	0.8272	-0.82
1	159315	黄金股ETF基金	0.873	0.8695	-0.40
2	159562	黄金股ETF	1.141	1.1395	-0.13
3	517400	黄金股票ETF	0.846	0.8452	-0.09
4	517520	黄金股ETF	1.050	1.0508	0.08
...	...	...	...	...	...
951	159327	半导体设备ETF基金	0.856	0.8583	0.27
952	159775	新能源车电池ETF	0.423	0.4233	0.07
953	517880	品牌消费ETF	0.727	0.7256	-0.19
954	515030	新能源车ETF	0.909	0.9110	0.22
955	588830	科创新能源ETF	0.919	0.9221	0.34
956 rows × 5 columns

然后使用Pandas的DataFrame进行查询,得到含有“沪深300”的项或者含有“上证”的项

df_hs300 = df_etf.loc[df_etf['名称'].str.contains('沪深300'),:]
df_hs300.head(10)

---------------------
	代码	名称	最新价	IOPV实时估值	基金折价率
181	560330	沪深300价值ETF申万菱信	0.959	0.9586	-0.04
270	562320	沪深300价值ETF	1.028	1.0294	0.14
275	159330	沪深300ETF基金	0.951	0.9490	-0.21
284	159510	沪深300价值ETF	0.898	0.8968	-0.13
301	561900	沪深300ESGETF	0.678	0.6776	-0.06
342	512530	沪深300红利ETF	1.251	1.2506	-0.03
388	515360	方正沪深300ETF	4.273	4.2649	-0.19
394	515390	沪深300ETF指数基金	0.894	0.8937	-0.03
395	561000	沪深300ETF增强基金	0.892	0.8929	0.10
406	159673	沪深300ETF鹏华	0.845	0.8434	-0.19

比如这里我们就选择515390这支ETF做沪深300指数的替代数据。

004_数据与列表保存

myStockList = ['001287','002179','600860','300233','002774']
myIndexList = ['000001','000300']

df_stock_list = []   # 用于保存每支股票的dataframe数据
df_index_list = []

def get_df_from_stock(code1):
    stock_df = ak.stock_zh_a_hist(symbol=code1, period="daily", start_date="20210301", adjust="qfq")
    stock_df.rename(columns={
        '日期':'date','开盘':'open','收盘':'close',
        '最高':'high','最低':'low', '成交量':'volume',
    },inplace=True)

    stock_df.index = pd.to_datetime(stock_df.date)
    stock_df['openinterest'] = 0
    stock_df = stock_df[['open','high','low','close','volume','openinterest']]
    return stock_df

def get_df_from_index(code1): # 与上面函数只是请求函数名不同index_zh_a_hist
    pass  # 略

for x in myIndexList:
    df_tmp = get_df_from_index(x)
    df_index_list.append(df_tmp)

for x in myStockList:
    df_tmp = get_df_from_stock(x)
    df_stock_list.append(df_tmp)

到这里,我们就把后续回测要用到的5支股票和2支指数的数据准备好了。

02_最简回测系统及问题

001_生成最简回测代码

我们还是通过AI助手生成backtrader双均线策略的回测代码,这里的代码就相当于一个最简回测系统。然后在上面把我们的数据放进去,这次用的是myStockList[2],即“600860-京城股份”的数据,开始日期为2021/9/1,结束日期默认当前最后一个交易日,跨度差不多为3年。

from datetime import datetime
import backtrader as bt

class SmaCross(bt.Strategy):
    params = (
        ('fast', 5),
        ('slow', 10),
    )

    def __init__(self):
        self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast)
        self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow)

        self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)

    def next(self):
        if not self.position:
            if self.crossover > 0:
                self.buy() 
        else:
            if self.crossover < 0:
                self.close()

cerebro = bt.Cerebro()                           # 创建Cerebro引擎
# 将数据源设置为PandasData,并加载数据
data = bt.feeds.PandasData(dataname=df_stock_list[2],fromdate=datetime(2021, 9, 1))
cerebro.adddata(data)                           # 将数据添加到Cerebro

cerebro.addstrategy(SmaCross)                   # 添加策略

cerebro.broker.setcash(100000.0)                # 设置初始资金
cerebro.broker.setcommission(commission=0.001)  # 设置交易佣金
cerebro.addsizer(bt.sizers.PercentSizer, percents=50) # 设置每次交易使用资金的比例为50%
print(f'Start Portfolio Value: {cerebro.broker.getvalue():.2f}')  # 打印起始组合价值

cerebro.run()  # 运行分析

print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')    # 打印最后组合价值
cerebro.plot()    # 绘制结果图 

----------------------------
Start Portfolio Value: 100000.00
Final Portfolio Value: 138102.81

从结果上看这次回测是盈利的,而且利润率达到惊人的38%,但从图形输出上看,恰好开局在低位不到7块,然后拉升到了25之上,如果回测的时间点往后2个月(2021/11/1)再开始,那么从高位掉到最后的7.53,最终的资产是 67172.88,反而亏了30%多。所以,单一的时间跨度可以有很大的概率出现妖的情况,并不代表策略的整体水平,这也是评价指标里大多会有近半年,近1年,近2年收益的指标,从而更多方位,较全面的策略进行分析。策略是需要择时的。

002_最简回测的问题

由于上面只是一个最简的回测系统,只是刚刚能把一个策略跑起来,因此它必然存在很多的疑问和问题,我们要一个一个的来看,并通过实践理解、解决或改善它们。

我们从代码顺序和显示图像的从上至下的顺序来看一下:

  1. bt.feeds.PandasData() - 把准备好的股票DataFrame数据转换成backtrader的格式,涉及到起止日期的选择,datetime类型的应用,另外如果对多支股票(我们已经准备好了5支股票)进行回测如何变化等。
  2. adddata(data) - 可以添加多个data吗?多个data数据怎么获取和处理?
  3. addstrategy() - 策略类怎么写,可以添加多个strtegy吗?几个data能用同一个策略吗?
  4. broker.setcash , setcommission - 怎么设置资金、佣金、滑点...与交易相关的数值
  5. cerebro.plot() - 绘制图形,(观察器 observer)
    • 图的第一栏是资产和现金变化曲线,一开始就冲到240000去了,后面全是在回撤
    • 所以有没有显示回撤的曲线,需要添加
    • 交易的成功/失败图标和颜色不易于观察,需要修改
    • 主图上没有K线,默认是收盘close的连线,需要修改
    • 主图上buy/sell的图标与颜色是国际的涨绿跌红,与我们国情相反,需要修改
    • 主图上的volume柱状图的颜色也反了,也需要修改
    • 哪些指标在主图上显示,哪些在副图上?怎么设置?

03_后续主要实践内容与目标

001_后续实践内容

由上面最简回测系统初步观察到的问题,我们把本篇后续的实践内容制定了初步的计划:

  • 数据(data)进阶 - 包括feeds多个股票数据,adddata多个data,这些数据怎么获取和应用
  • 策略(strategy)进阶 - strategy类包含哪些,多策略循环
  • 交易设置(broker)进阶 - 各种交易的设置,建立自己的交易体系
  • 观察器与绘图进阶 - 绘制符合我们需要的图形

002_本篇的目标

通过完成本篇的基础加强的实践后,我们可以把所有的更改和添加的数据、类、函数等都制作到一个xx.py文件,这样后续在notebook里%run xx.py文件就能把增强的配置load进Notebook中,每次新的回测只需要写策略类的核心代码即可,就能完成股票的回测,且能够循环回测。

在完成本篇实践后,对交易系统有更深层的理解,并能把策略的研究作为重点,可以忽略其他一些繁琐的小问题,把时间和精力集中放在策略的编写和优化上,能大大提升我们的效率。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值