分享

024 实战一,使用理杏仁数据模拟基金定投

 LIB达人 2022-05-15
本节难度有点大,写这篇文章也断断续续用了几天时间,所以大家不要压力太大,可以慢慢理解,多看几天也可以
前面我们学习了网络访问和 Excel 文档操作,这节我们来综合运用一下这两部分内容。
这节课我们主要来模拟一下指数基金中证500(000905)的定投
所谓基金定投,无非就是价格低的时候,定期投入,价格高的时候卖出。这种策略又叫微笑曲线,不了解微笑曲线的同学,可以上网搜一下。注意,微笑曲线很适合用于指数基金,其他的基金就不好说了
图片

判断价格高低,有的人用市盈率分位点,有的用市净率分位点,有的甚至用这两者的平均数。我们首先要从理杏仁的网站上获取这些数据。
和 21 节一样,我们首先找到对应的接口地址:
图片
然后,配置好相应的参数,先获取五个交易日的数据,用来观察数据字典格式:
图片
获得的字典数据的每条数据的意义,在这个接口文档里都有解释。大家可以拷贝参数配置试一试:

{

'token': '这里替换为您的理杏仁token',

'startDate': '2020-12-16',

'endDate': '2020-12-23',

'stockCodes': [

'000905'

],

'metricsList': [

'cp',

'pe_ttm.y10.mcw',

'pb.y10.mcw'

]

}

接下来我们根据上面的接口,写一个程序,将过去十年的中证500数据,从网络上获取下来,并保存到 Excel 文档中备用:

import requests

import json

from openpyxl import Workbook

# token 和接口地址

token = '这里替换为您的理杏仁token'

url = 'https://open./api/a/index/fundamental'

# 指定返回格式

headers = {

 'Content-Type': 'application/json'

}

# 设置参数

data = {

 'token': token, # token,即登录信息

 'startDate': '2010-12-23',

 'endDate': '2020-12-23',

 'stockCodes': [

   '000905'

 ],

 'metricsList': [

   'cp',

   'pe_ttm.y10.mcw',

   'pb.y10.mcw'

 ]

}

# 网络访问

res = requests.post(url, headers=headers, data=json.dumps(data))

# 打印获得的数据

data = res.json()

print(data)

# 声明要保存的数据

excel_data = []

# 打印结果

for item in data['data']:

 # 保存日期、指数点位、市盈率、市盈率分位点、市净率、市净率分位点

 row = [item['date'][:10], item['cp'], item['pe_ttm']['y10']['mcw']['cv'], item['pe_ttm']['y10']['mcw']['cvpos'],

   item['pb']['y10']['mcw']['cv'], item['pb']['y10']['mcw']['cvpos']]

 excel_data.append(row)

# 反转列表,因为原始数据是根据日期从近到远的排序

excel_data.reverse()

# 创建Excel文档

wb = Workbook()

sheet = wb.active

sheet.title = '指数数据'

# 写入表头

sheet.append(['日期', '指数点位', '市盈率', '市盈率分位点', '市净率', '市净率分位点'])

# 写入多行数据

for row in excel_data:

 sheet.append(row)

# 保存 Excel 文件

wb.save('指数数据.xlsx')

最终获得的 Excel 文档部分数据如下:

图片

既然数据已经到手了,接下来我们开始准备模拟基金定投。
首先我们来确定一个策略,即从微笑策略中的多种方法选一个:
  • 定投周期 - 每周一定投一次,当天没开市则略过

  • 定投金额 - 市盈率分位点 0 到 0.1 ,定投500元,0.1 到 0.2 定投200元,0.2 到 0.3 定投100元

  • 卖出时机 - 市盈率分位点 0.6 到 0.7 卖出四分之一,0.7 到 0.8 卖出二分之一,0.8 以上卖出四分之一

  • 手续费 - 我们使用场外基金进行模拟,假设每次买入卖出手续费都是千分之一,当然,场内基金手续费更低一些,但是每次必须买100份的整数倍,不方便模拟,场外基金最少买一元,比如现价5元,你可以买 0.2 份,如果是场外,最少买100份

指数数据中没有价格,但是我们知道价格和点位是成正比的,所以我们可以把价格除以1000,假定为其价格
下面是模拟基金定投的代码,里面有详细的注释,请大家细细理解:
fund.py(这部分代码比较多,所以先创建了一个模块)

# 创建一个类,用来保存每天的操作数据,面向对象的知识,大家不会忘了吧

classFundData:

   # 日期

   date = ''

   # 价格

   price = 0.0

   # 市盈率分位点

   position = 0.0

   # 操作类型:1,买入,2,卖出

   option = 0

   # 操作金额

   amount = 0.0

   # 操作份额

   volume = 0.0

   # 手续费

   fee = 0.0

   # 总手续费

   feeTotal = 0.0

   # 总成本

   amountTotal = 0.0

   # 总份额

   volumeTotal = 0.0

   # 入袋盈利

   profit = 0.0

   # 历史最大本金

   amountMax = 0.0

   def__str__(self):

       str = ''

       ifself.option == 1:

           str = '买入'

       else:

           str = '卖出'

       # %.2f 意思是保留两位小数的浮点型数字

       dataStr = '日期: %s, 价格: %.2f, 分位点: %.2f, %s%.2f份, 浮动盈利%.02f元'

       return dataStr % (self.date, self.price, self.position, str, self.volume, self.volumeTotal*self.price-self.amountTotal)

defbuyFund(lastData, data, amount):

   data.option = 1

   data.amount = amount

   # 注意把手续费算好

   data.fee = data.amount * 0.001

   data.volume = (data.amount - data.fee)/data.price

   # 下面记录好总的手续费、总成本、总份额

   data.feeTotal = data.fee + lastData.feeTotal

   data.amountTotal = data.amount + lastData.amountTotal

   data.volumeTotal = data.volume + lastData.volumeTotal

   if data.amountTotal > lastData.amountMax:

       data.amountMax = data.amountTotal

   else:

       data.amountMax = lastData.amountMax

   data.profit = lastData.profit

   return data

defsellFund(lastData, data, volumeRate):

   data.option = 2

   data.volume = lastData.volumeTotal * volumeRate

   data.amount = data.volume * data.price

   data.fee = data.amount * 0.001

   # 计算盈利

   data.profit = lastData.profit + data.volume * data.price - lastData.amountTotal * volumeRate - data.fee

   # 下面记录好总的手续费、总份额、总成本

   data.feeTotal = data.fee + lastData.feeTotal

   data.volumeTotal = lastData.volumeTotal - data.volume

   # 卖出计算总成本时,要用平均价格,直接乘以份额比例也可以

   data.amountTotal = lastData.amountTotal - lastData.amountTotal * volumeRate

   data.amountMax = lastData.amountMax

   # 矫正最后的数据,一般情况下是不需要的

   if data.volumeTotal < 0.01:

       data.volumeTotal = 0

       data.amountTotal = 0

   return data

main.py

from openpyxl import load_workbook

import datetime

from fund import FundData, buyFund, sellFund

# 打开 Excel 文件和工作表

wb = load_workbook('指数数据.xlsx')

sheet = wb['指数数据']

# 取出我们需要用到的数据

dataList = []

for i inrange(sheet.max_row):

   if(i == 0):

       # 第一行是表格标题,直接略过

       continue

   # 获取日期

   date = sheet.cell(row=i+1, column=1)

   # 字符串转日期

   dateTime = datetime.datetime.strptime(date.value,'%Y-%m-%d')

   # 不是周一则跳过(0 是周一,1 是周二,以此类推)

   if(dateTime.weekday() != 0):

       continue

   data = FundData()

   data.date = date.value

   # 获取价格

   price = sheet.cell(row=i+1, column=2)

   data.price = float(price.value)/1000

   # 获取市盈率分位点

   position = sheet.cell(row=i+1, column=4)

   data.position = float(position.value)

   dataList.append(data)

# 开始模拟

dataRecords = []

lastData = FundData()

lastSellPosition = 0.0

for data in dataList:

   if data.position <= 0.1:

       # 买入500元的

       data = buyFund(lastData, data, 500)

       lastSellPosition = 0.0

   elif data.position <= 0.2:

       # 买入200元的

       data = buyFund(lastData, data, 200)

       lastSellPosition = 0.0

   elif data.position <= 0.3:

       # 买入100元的

       data = buyFund(lastData, data, 100)

       lastSellPosition = 0.0

   elif data.position <= 0.6:

       continue

   elif data.position <= 0.7:

       # 已经卖完了,或者当前阶段卖过了,不再重复卖

       if lastData.volumeTotal == 0or lastSellPosition > 0.6:

           continue

       # 卖出四分之一

       data = sellFund(lastData, data, 0.25)

       lastSellPosition = data.position

   elif data.position <= 0.8:

       # 已经卖完了,或者当前阶段卖过了,不再重复卖

       if lastData.volumeTotal == 0or lastSellPosition > 0.7:

           continue

       # 卖出一半,就是剩下的三分之二

       data = sellFund(lastData, data, 0.66667)

       lastSellPosition = data.position

   else:

       # 已经卖完了,或者当前阶段卖过了,不再重复卖

       if lastData.volumeTotal == 0or lastSellPosition > 0.8:

           continue

       # 卖出四分之一,就是剩下的全部

       data = sellFund(lastData, data, 1)

       lastSellPosition = data.position

   print(data)

   lastData = data

   dataRecords.append(data)

print('最高投入金额: %.2f' % (lastData.amountMax))

print('手续费总计: %.2f' % (lastData.feeTotal))

# 计算盈利时应当算上浮动盈利

finalData = dataList[len(dataList)-1]

profit = lastData.profit+lastData.volumeTotal*finalData.price-lastData.amountTotal

print('总盈利: %.2f' % (profit))

rate = profit/lastData.amountMax/10 * 100

print('平均每年收益: %.2f%%' % (rate))

部分运行结果如下:

图片

单看上面的运行结果,平均年收益并不高,只有9.23%一方面是时间节点的问题,一般模拟测试应该用一个完整的牛熊周期,但是现在牛市还没有真正到来。另一方面,为了简便起见,我们用了投入最大金额当做是本金了,实际上应该用平均投入资金,因为最大投入金额是多年的总投入金额。
当然,实际应用中还有多种其他因素,会提高定投收益,比如多种指数基金混合定投,按分位点权重计算定投比例,或者历史新低时重仓投入一些,等等。这些就不细说了,大家重点把上面的代码给好好理解了。
然后作为今天的作业,大家根据自己的想法和策略测试一下沪深300的模拟

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多