基于大数据技术对基金分析----By Glorio

回顾2020是基金元年,在疫情期间,医疗、白酒、新能源这些板块的抱团基金赚得盆满钵满,更是有不少基金经理火速出圈,易方达蓝筹、中小盘的张坤更是“饭圈化”,与之形成明显对比的诺安成长的蔡嵩松,被黑得甚至出了定制版手办。 放眼当下,基金自从春节过后,也频繁登上热搜,不过完全是“跌妈不认”地跌上热搜,截至2/26,抱团基金们更是绿得堪比健康码👍

注:本文为个人原创,仅供技术探讨与交流,对实际投资并不造成建议。
本人并非金融专业,所了解的金融知识有限,欢迎金融人士指正及提供意见进行改进优化,欢迎IT大佬们提供建议及交流。


前言

在当下热销的基金市场中,有着茫茫多的基金,本文希望能以科学的角度,以数据作为依托,更好地挑选基金 (成为韭菜)
本文共用到以下技术:Python爬虫、Spark数据清洗、SQL分析

未来构想:Spark数据挖掘(建模分析、基金经理实力大数据分析)、量化信息面(实时量化信息作为利好or利空指标)


本文切入角度:
1.希望做到在相对低的净值买入值得长期持有的基金并定投
2.通过已有的数据了解到当下及未来3个月市场热门的板块(抱团)
3.通过基金的表现及近期情况判断市场方向
基金是T+1交易制度,存在明显的市场时间滞后,不适合像股票一样频繁操作。

整体构思(完善后打包每交易日定期更新):
1.获取数据:
各基金的基本信息(名称、代号、类型、资金、基金经理)、昨日净值、昨日涨跌幅、近一月涨跌幅、近三月涨跌幅、近六月涨跌幅、近一年涨跌幅、近三年涨跌幅、成立以来涨跌幅
2.数据清洗及计算指标:
已终止或数据异常的基金数据去除、统一字段格式、计算得出
now_down:当前、仅一月下跌,仅半年、一年涨跌幅为正(数据层面上判断:该基金当前属于回撤调整期且值得长期持有)
all_win:三月、六月、一年、三年、所有均为正(长期正收益)

3.SQL分析:
筛选基金类型、各时间维度涨跌幅分析、金额总值top50、近3月新发基金 => 判断当前基金机构最看好的行业板块 ≈> 未来抱团方向(?)、now_down、 all_win
4.数据挖掘与建模:盈利模型、经理实力判断指标聚类分析、…
5.量化信息面(初步构思)

注:当前数据日期为:2020/03/02


一、数据获取:Python爬虫(Scrapy框架)=> FUND.txt => MF_INFO.txt

1).从天天基金数据接口获取数据

接入天天基金数据接口(所有基金名称列表代码):http://fund.eastmoney.com/js/fundcode_search.js
部分数据截图:

截取部分数据
从图中数据可以看出,js文件中的数据以列表格式存放,且按照 => (编号、拼音缩写、基金名称、基金类型、拼音 )的顺序排列,因为信息并不完善,所以还需要再爬取每个基金的详细信息

2).爬虫前期准备

通过观察,天天基金的robots规则为:

在这里插入图片描述
每个基金详情页面为
http://fund.eastmoney.com/+基金代号,如下图:

在这里插入图片描述
简单处理js文件数据 => FUND.txt
FUND.txt部分数据如下图:

在这里插入图片描述

3).Scrapy框架

FUND.txt => Python爬虫 => MF_INFO.txt
Python爬虫代码以及MF_INFO.txt文件部分数据如图:

在这里插入图片描述

因为该爬虫比较简单就不详细解析了(框架内其他配置可自行设置或评论区留言),代码如下:

import scrapy


class mfSpider(scrapy.Spider):
    name = 'mfspider'
    fd = open(r'C:\Users\Arrogant\Desktop\Bigdata_Fund\FUND.txt',encoding='utf-8')
    data_list=(fd.read()).replace("],","];").split(";")
    start_urls = []
    for a in data_list:
        mf_list=a.split(",")
        mf_code=mf_list[0].replace("[","").replace("\"","")
        start_urls.append('http://fund.eastmoney.com/'+str(mf_code)+'.html')

    def parse(self, response):
        infodict = {}
        infodict['MF_name'] =response.xpath('//div[@class="fundDetail-tit"]/div[1]/text()').extract_first()
        infodict['MF_num']=response.xpath('//div[@class="fundDetail-tit"]/div[1]/span[2]/text()').extract_first()
        infodict['MF_type'] = response.xpath('//td[contains(text(),"基金类型")]/a/text()').extract_first()
        infodict['MF_money'] = response.xpath('//td[contains(text(),"亿元")]/text()').extract_first()
        infodict['MF_manager'] = response.xpath('//td[contains(text(),"基金经理")]/a/text()').extract_first()
        infodict['MF_worth'] = response.xpath('//dl[@class="dataItem02"]/dd[1]/span[1]/text()').extract_first()
        infodict['MF_rate'] = response.xpath('//dl[@class="dataItem02"]/dd[1]/span[2]/text()').extract_first()
        infodict['MF_1mon'] = response.xpath('//div[@class="dataOfFund"]/dl[1]/dd[2]/span[2]/text()').extract_first()
        infodict['MF_3mon'] = response.xpath('//div[@class="dataOfFund"]/dl[2]/dd[2]/span[2]/text()').extract_first()
        infodict['MF_6mon'] = response.xpath('//div[@class="dataOfFund"]/dl[3]/dd[2]/span[2]/text()').extract_first()
        infodict['MF_12mon'] = response.xpath('//div[@class="dataOfFund"]/dl[1]/dd[3]/span[2]/text()').extract_first()
        infodict['MF_36mon'] = response.xpath('//div[@class="dataOfFund"]/dl[2]/dd[3]/span[2]/text()').extract_first()
        infodict['MF_all'] = response.xpath('//div[@class="dataOfFund"]/dl[3]/dd[3]/span[2]/text()').extract_first()

        yield infodict

二、数据清洗及计算指标:Spark =>FUND_RESULT

1.过滤数据

据观察,MF_INFO数据中存在已终止基金或已不更新数据的基金 仅保留:昨日涨跌幅、近一月涨跌幅、近三月涨跌幅、近六月涨跌幅、近一年涨跌幅、近三年涨跌幅%、成立以来涨跌幅 共7个字段中为 “--”(基金运作时间未久会以“--”表示,为正常)和存在“%”的数据

在这里插入图片描述

2.数据字段格式统一

(便于后续SQL查询)金额字段统一为:仅保留数值(以亿元为单位)

(便于后续SQL查询)统一涨幅字段:百分比 => double小数,"–" => 0.0

在这里插入图片描述

注意:这里为部分代码,去除了%号后,下面还统一乘以了0.01

3.计算得出字段now_down、all_win

在这里插入图片描述
all_win:将3个月、半年、一年、三年、历史总涨幅为正的基金标记为yes ≈> 从数据层面上看,该基金中长期正收益,值得长期持有并定投
now_down:将同时满足昨日涨跌幅<0,仅一月涨跌幅<0,近半年涨跌幅>0,仅一年涨跌幅>0的基金标记,并计算出昨日跌幅+ 仅一月跌幅作为参数 ≈> 数据层面上判断:该基金长期持有为正收益且当前属于回撤调整期,属于相对低值可入手

4.完整代码

import org.apache.spark.{SparkConf, SparkContext}

object fund_clear {

  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setAppName("fund").setMaster("local[*]")
    val sc=new SparkContext(conf)
    val input="hdfs://localhost:9000/fund"
    val lines=sc.textFile(input)
    var out=lines.map(_.split("\\|")).filter(x=>{
      (x(6).contains("%")||x(6).contains("--"))&&(x(7).contains("%")||x(7).contains("--"))&&(x(8).contains("%")||x(8).contains("--"))&&(x(9).contains("%")||x(9).contains("--"))&&(x(10).contains("%")||x(10).contains("--"))&&(x(11).contains("%")||x(11).contains("--"))&&(x(12).contains("%")||x(12).contains("--"))
    })
    var result1=out.map(x=> {
      x(3)=x(3).replace(":","").replaceAll("亿.+","")
      var now_down=0.0
      var mon_all_win="no"

      var today=0.0
      var mon1=0.0
      var mon3=0.0
      var mon6=0.0
      var mon12=0.0
      var mon36=0.0
      var monall=0.0
      if(x(6).contains("%")){
        today=x(6).replace("%","").toDouble
      }
      if(x(7).contains("%")){
        mon1=x(7).replace("%","").toDouble
      }
      if(x(8).contains("%")){
        mon3=x(8).replace("%","").toDouble
      }
      if(x(9).contains("%")){
        mon6=x(9).replace("%","").toDouble
      }
      if(x(10).contains("%")){
        mon12=x(10).replace("%","").toDouble
      }
      if(x(11).contains("%")){
        mon36=x(11).replace("%","").toDouble
      }
      if(x(12).contains("%")){
        monall=x(12).replace("%","").toDouble
      }
      if((today<0.0)&&(mon1<0.0)&&(mon6>0.0)&&(mon12>0.0)&&(mon36>0.0)){
        now_down=(today+mon1)*0.01
      }
      if((mon3>0.0)&&(mon6>0.0)&&(mon12>0.0)&&(mon36>0.0)&&(monall>0.0)){
        mon_all_win="yes"
      }
      x(6)=(today*0.01).formatted("%.4f")
      x(7)=(mon1*0.01).formatted("%.4f")
      x(8)=(mon3*0.01).formatted("%.4f")
      x(9)=(mon6*0.01).formatted("%.4f")
      x(10)=(mon12*0.01).formatted("%.4f")
      x(11)=(mon36*0.01).formatted("%.4f")
      x(12)=(monall*0.01).formatted("%.4f")

      var result=""
      for (i <- x) {
        if (i.equals(x(x.size - 1))) {
          result = result + i.trim() + "|" + now_down + "|" + mon_all_win
        } else {
          result = result + i.trim() + "|"
            }
      }
      result
    }
    )
    result1.saveAsTextFile("hdfs://localhost:9000/FUND_RESULT")
  }

}


三、数据分析:SQL

#去除worth净值为“--”(已终止、信息不全)
DELETE from fund where worth = '--'
去除已终止、信息不全的数据后,共有8933个基金

以下的SQL都只筛选了股票型、混合型、QDII型基金

1.昨日涨跌幅、近一月涨跌幅、近一年涨幅、仅三年涨幅 top20 => 当前主题行情分析


#昨日跌幅前20(只选择QDII型、混合型、股票型,下同)
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY rate ASC LIMIT 20

#昨日涨幅前20
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY rate DESC LIMIT 20

#近一月涨幅前20
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY mon1 DESC LIMIT 20

#近一月跌幅前20
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY mon1 ASC LIMIT 20 

#近一年涨幅前20
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY mon12 DESC LIMIT 20  

#近三年涨幅前20
select * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY mon36 DESC LIMIT 20  

2.金额总值top50=> 截至2020-12-31最热门抱团方向(金额总值的数据统一是2020-12-31更新)

SELECT * from fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY money DESc LIMIT 50

在这里插入图片描述
毫无悬念,消费医疗新能源三条主赛道占据了80%,据统计前50大资金的基金,指数型仅占一个,股票型仅占7个,剩下的全是混合型,可以看出分散风险的概念还是深得人心,当然,有个别会挂着混合型干着梭哈某板块的事…比如:诺安成长。
因为我们在挑选的时候一定要仔细看该基金的重仓股。

3. 3月份后涨跌幅为“- -” => 新发基金封闭期3个月=> 当前基金机构看好的行业板块方向 ≈> 未来抱团方向(?)

已事先把涨跌幅“--”转换为“0.0”
SELECT * FROM fund where mon3='0.0' AND (type like '%QDII%' OR type like '%混合%' OR type like '%股票%')

在这里插入图片描述

当前股票型、混合型、QDII型新发基金共计125个,据分析,大多数新发基金都可投资港股
主题板块方面热度: 医药≈新能源>科技≈消费>环保≈军工

4.now_down最小前20 => 数值越小,跌幅越大

SELECT * FROM fund WHERE type like '%QDII%' OR type like '%混合%' OR type like '%股票%' ORDER BY now_down ASC LIMIT 20

在这里插入图片描述

如图:近期回撤幅度最大的20只基金中,中长期正收益的为19个,主题板块方面:医药、白酒领跌,其中医药>白酒

5.all_win =>中长期正收益,值得长期持有

SELECT * from fund where all_win ="yes" AND (type like '%QDII%' OR type like '%混合%' OR type like '%股票%')

总结

本人个人认为:金融市场是一个混沌市场,市场受影响因素非常大,而当前单纯凭借数据的客观性判断意义不大,因此还得结合有关金融知识对市场进行主观判断,结合当前消息面、利好政策、市场情绪等多角度进行分析

数据客观角度:
根据当前新发基金:港股热度上涨,板块还是以医药及新能源为主,消费板块热度有所下降,科技不变,新增热度方向:军工及环保板块
根据当前下跌幅度(now_down):当前医药、消费(白酒)回撤幅度极大,跌幅最大的前20只基金近一月跌幅最大为14.32%,最小为11.36%,其中医药回撤幅度大于消费
根据中长期正收益指标(all_win):共有2099个基金符合中长期正收益,约占总基金数据中的23.4%长期持有的赚钱效益从数据上看其实并没有想象中的这么好,基金市场热度明显高于市场赚钱效应

金融消息主观角度(个人见解):
当前海外市场货币政策宽松,这是利好,央行之前表示:国内货币政策走向:稳字当头,不急转弯。在我的理解下:不急转弯也就是相当于会转弯,国内态度还是以稳为主,且近期政策打压炒房。
因此资金方面我认为还是利好于A股及港股,尤其是港股,近期港股提升印花税我个人认为更多的是为了维持市场稳定且持续向上作为一个抗力因素给资金降降火,否则只是为了提高财政收入而将市场资金流动性降低不会更得不偿失吗?
板块方面:近期医药消费回撤幅度极大预料当中,毕竟年前所产生的泡沫过高不利于维稳,个人认为医药+消费+新能源还是主赛道(消费的地位可能有所下降),且会增加环保+军工+顺周期工业板块的机会作为副赛道

综上所述:所选了10只基金,用定投模拟器以一个月时间(每周四定投500)进行模拟测试,看下这样选出来的基金的收益是否可观,如果有继续完善优化意义,会继续完成未来构思方向并实现每交易日/每周更新

在这里插入图片描述

注:以上所选基金仅进行测试,本文并不提供任何投资建议
注:以上数据日期为:2020/03/02

基金是一个持有时间越长所取得的回报越高的一个理财方式,以时间换空间,本文只是本人在业余时间出于兴趣爱好做的一个简单分析,不奢求于能抄底,毕竟抄底不成被抄家的例子比比皆是,只希望于能更好地选择基金进行定投并长期持有。


Update于2021/04/08:
用基金定投模拟器按一个月前的策略进行模拟,3月整月所有皆为正收益,最高为5.9%,最低为0.28%,平均2.4%。同期沪深300指数为-3.52%。
在这里插入图片描述
在这里插入图片描述
有时间会尝试着手数据建模挖掘及实时计算,随缘更新~

注:本文为个人原创,如需转载,请注明出处。同时仅供技术探讨与交流,对实际投资并不造成建议。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值