zoukankan      html  css  js  c++  java
  • Day 80 量化投资与Python——项目案例

    数据分析项目案例

    股票分析

    小结:

    需求:

    使用tushare包获取某股票的历史行情数据。
    输出该股票所有收盘比开盘上涨3%以上的日期。
    输出该股票所有开盘比前日收盘跌幅超过2%的日期。

    # 需求四:假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票,到今天为止,我的收益如何?

    需求一:使用tushare包获取某股票的历史行情数据。

    # 获取行情
    df = ts.get_k_data(code="600519",start='2000-01-01')
    # 保存到本地
    df.to_csv('./maotai.csv')
    # 读取本地csv文件数据
    df = pd.read_csv('./maotai.csv')
    # 删除 Unnamed: 0 这一列,将 date 列转为时间类型,并设置为 index 列
    df.drop(labels='Unnamed: 0',axis=1,inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date',inplace=True)
    print(df.info())    # 查看整个数据集合中各个数据类型
    print(df)

    需求二:输出该股票所有收盘比开盘上涨3%以上的日期。

    # (收盘-开盘)/开盘 > 0.03  返回值为 boolean 值,将 boolean 作为行索引来使用
    
    # 在分析的过程中如果产生了boolean值则下一步马上将布尔值作为源数据的行索引
    # 如果布尔值作为df的行索引,则可以取出true对应的行数据,忽略false对应的行数据
    # print((df['close'] - df['open'])/df['open'] > 0.03)   # 获取了True对应的行数据(满足需求的行数据)
    print(df.loc[(df['close'] - df['open']) / df['open'] > 0.03].index)

    需求三:输出该股票所有开盘比前日收盘跌幅超过2%的日期。

    # (今日开盘价-昨日收盘价)/昨日收盘价 < -0.02
    # print(df['close'].shift(1))    # 使 df['close'] 列整体下移一位
    print(df.loc[(df['open'] - df['close'].shift(1)) / df['close'].shift(1) < -0.02].index)

    # 需求四:假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票,到今天为止,我的收益如何?

    - 分析:
      - 时间节点:2010-2020
      - 一手股票:100支股票
      - 买:
        - 一个完整的年需要买入1200支股票
      - 卖:
        - 一个完整的年需要卖出1200支股票
      -买卖股票的单价:
        - 开盘价

    # 买股票:找每个月的第一个交易日对应的行数据(捕获到开盘价)==》每月的第一行数据
    # 根据月份从原始数据中提取指定的数据
    # 每月第一个交易日对应的行数据
    new_df = df['2010-01-01':]
    mairu = new_df.resample('M').first()['open'].sum() * 100  # 数据的重新取样,取出每月的第一支股票
    maichu = new_df.resample('A').last()['open'][:-1].sum() * 1200  # 取出每年最后一个交易日的收盘价
    yu = new_df['close'][-1] * 600  # 剩余股票价值
    
    # print(new_df.resample('M').first()['open']*100)
    # print(new_df.resample('A').last()['close'][:-1] * 100)
    
    print(maichu - mairu + yu)

    双均线策略

    需求一:计算该股票历史数据的5日均线和60日均线
      - 什么是均线?
        - 对于每一个交易日,都可以计算出前N天的移动平均值,然后把这些移动平均值连起来,成为一条线,就叫做N日移动平均线。移动平均线常用线有5天、10天、30天、60天、120天和240天的指标。
        - 5天和10天的是短线操作的参照指标,称做日均线指标;
        - 30天和60天的是中期均线指标,称做季均线指标;
        - 120天和240天的是长期均线指标,称做年均线指标。
      - 均线计算方法:MA=(C1+C2+C3+...+Cn)/N C:某日收盘价 N:移动平均周期(天数)

    df = ts.get_k_data(code="600519", start='2000-01-01')
    df.to_csv('./maotai.csv')
    df = pd.read_csv('./maotai.csv')
    df.drop(labels='Unnamed: 0',axis=1,inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date',inplace=True)
    
    ma5 = df['close'].rolling(5).mean()     # 5日均线
    ma30 = df['close'].rolling(30).mean()     # 30日均线
    
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    plt.plot(ma5[20:80],'h-r', label='ma5')
    plt.plot(ma30[20:80],'h-b', label='ma30')
    plt.legend()
    plt.show()

    需求二:

    - 分析输出所有金叉日期和死叉日期
      - 股票分析技术中的金叉和死叉,可以简单解释为:
        - 分析指标中的两根线,一根为短时间内的指标线,另一根为较长时间的指标线。
        - 如果短时间的指标线方向拐头向上,并且穿过了较长时间的指标线,这种状态叫“金叉”
        - 如果短时间的指标线方向拐头向下,并且穿过了较长时间的指标线,这种状态叫“死叉”
      - 一般情况下,出现金叉后,操作趋向买入;死叉则趋向卖出。当然,金叉和死叉只是分析指标之一,要和其他很多指标配合使用,才能增加操作的准确性。

    # 分析输出所有金叉日期和死叉日期                                                     
    df = pd.read_csv('./maotai.csv')                                      
    df.drop(labels='Unnamed: 0', axis=1, inplace=True)                    
    df['date'] = pd.to_datetime(df['date'])                               
    df.set_index('date', inplace=True)                                    
                                                                          
    ma5 = df['close'].rolling(5).mean()                                   
    ma30 = df['close'].rolling(30).mean()                                 
    s5 = ma5[30:] < ma30[30:]                                             
    s30 = ma5[30:] > ma30[30:]                                            
    df = df[30:]                                                          
                                                                          
    down = s5 & s30.shift(1)                                                
    print(df.loc[down].index)       # 死叉                                     
                                                                          
    up = ~(s5 | s30.shift(1))                                           
    print(df.loc[up].index)    # 金叉  

    需求三:如果我从假如我从2010年1月1日开始,初始资金为100000元,金叉尽量买入,死叉全部卖出,则到今天为止,我的炒股收益率如何?

    df = pd.read_csv('./maotai.csv')
    df.drop(labels='Unnamed: 0', axis=1, inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)
    
    ma5 = df['close'].rolling(5).mean()
    ma30 = df['close'].rolling(30).mean()
    s5 = ma5[30:] < ma30[30:]
    s30 = ma5[30:] > ma30[30:]
    df = df[30:]
    
    up = ~(s5 | s30.shift(1))  # 金叉
    down = s5 & s30.shift(1)  # 死叉
    
    up_code = Series(data=1, index=(df.loc[up].index))
    down_code = Series(data=0, index=(df.loc[down].index))
    s = up_code.append(down_code)
    s = s.sort_index()['2010-01-01']
    
    first_monry = 100000  # 本金,不变
    money = first_monry  # 可变的,买股票话的钱和卖股票收入的钱都从该变量中进行操作
    hold = 0  # 持有股票的数量(股数:100股=1手)
    
    for i in range(0, len(s)):  # i表示的s这个Series中的隐式索引
        # i = 0(死叉:卖) = 1(金叉:买)
        if s[i] == 1:  # 金叉的时间
            # 基于100000的本金尽可能多的去买入股票
            # 获取股票的单价(金叉时间对应的行数据中的开盘价)
            time = s.index[i]  # 金叉的时间
            p = df.loc[time]['open']  # 股票的单价
            hand_count = money // (p * 100)  # 使用100000最多买入多少手股票
            hold = hand_count * 100
    
            money -= (hold * p)  # 将买股票话的钱从money中减去
        else:
            # 将买入的股票卖出去
    
            # 找出卖出股票的单价
            death_time = s.index[i]
            p_death = df.loc[death_time]['open']  # 卖股票的单价
            money += (p_death * hold)  # 卖出的股票收入加入到money
            hold = 0
    
    # 如何判定最后一天为金叉还是死叉
    last_monry = hold * df['close'][-1]  # 剩余股票的价值
    
    # 总收益
    money + last_monry - first_monry
    print(money)

    人口分析项目

    - 需求:
        - 导入文件,查看原始数据
        - 将人口数据和各州简称数据进行合并
        - 将合并的数据中重复的abbreviation列进行删除
        - 查看存在缺失数据的列
        - 找到有哪些state/region使得state的值为NaN,进行去重操作
        - 为找到的这些state/region的state项补上正确的值,从而去除掉state这一列的所有NaN
        - 合并各州面积数据areas
        - 我们会发现area(sq.mi)这一列有缺失数据,找出是哪些行
        - 去除含有缺失数据的行
        - 找出2010年的全民人口数据
        - 计算各州的人口密度
        - 排序,并找出人口密度最高的州
    # 导入文件,查看原始数据
    abb = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-abbrevs.csv') #state(州的全称)abbreviation(州的简称)
    area = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-areas.csv') #state州的全称,area (sq. mi)州的面积
    pop = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-population.csv')#state/region简称,ages年龄,year时间,population人口数量
    
    # 将人口数据和各州简称数据进行合并
    abb_pop = pd.merge(abb,pop,left_on='abbreviation',right_on='state/region')
    abb_pop
    
    # 将合并的数据中重复的abbreviation列进行删除
    abb_pop2.drop(labels='abbreviation',axis=1,inplace=True)
    abb_pop2
    
    # 查看存在缺失数据的列
    abb_pop2.isnull().any(axis=0)
    
    # 找到有哪些state/region使得state的值为NaN,进行去重操作
    abb_pop.loc[abb_pop['state'].isnull()]['state/region'].unique()
    
    # 为找到的这些state/region的state项补上正确的值,从而去除掉state这一列的所有NaN
    # 合并各州面积数据areas
    # 我们会发现area(sq.mi)这一列有缺失数据,找出是哪些行
    # 去除含有缺失数据的行
    # 找出2010年的全民人口数据
    # 计算各州的人口密度
    # 排序,并找出人口密度最高的州

    消费记录分析

    数据文件:消费记录数据

    第一部分

    第一部分:数据类型处理
        - 数据加载
            - 字段含义:
                - user_id:用户ID
                - order_dt:购买日期
                - order_product:购买产品的数量
                - order_amount:购买金额
        - 观察数据
            - 查看数据的数据类型
            - 数据中是否存储在缺失值
            - 将order_dt转换成时间类型
            - 查看数据的统计描述
                - 计算所有用户购买商品的平均数量
                - 计算所有用户购买商品的平均花费
            - 在源数据中添加一列表示月份:astype('datetime64[M]')

    代码实现

    import pandas as pd
    from pandas import DataFrame
    # 数据加载
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',header=None,sep='s+',names=['user_id','order_dt','order_product','order_amount'])
    df
    
    # 查看数据的数据类型
    df.info()
    
    # 数据中是否存储在缺失值
    # df.isnull().any()
    df.notnull().all()
    
    # 将 order_amount 转换成时间类型
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    df
    
    # 查看数据的统计描述
    df.describe()
    
    # 在源数据中添加一列表示月份:astype('datetime64[M]')
    df['month'] = df['order_dt'].astype('datetime64[M]')
    df
    View Code

    第二部分

    第二部分:按月数据分析
        - 用户每月花费的总金额
            - 绘制曲线图展示
        - 所有用户每月的产品购买量
        - 所有用户每月的消费总次数
        - 统计每月的消费人数

    代码实现

    # 用户每月花费的总金额
    df.groupby(by='month')['order_amount'].sum()
    
    # 绘制曲线图展示
    import matplotlib.pyplot as plt
    plt.plot(df.groupby(by='month')['order_amount'].sum())
    df.groupby(by='month')['order_amount'].sum().plot()
    
    # 所有用户每月的产品购买量
    df.groupby(by='month')['order_product'].sum()
    
    # 所有用户每月的消费总次数
    df.groupby(by='month')['user_id'].count()
    
    # 统计每月的消费人数
    df.groupby(by='month')['user_id'].nunique()
    View Code

    第三部分

    第三部分:用户个体消费数据分析
        - 用户消费总金额和消费总次数的统计描述
        - 用户消费金额和消费产品数量的散点图
        - 各个用户消费总金额的直方分布图(消费金额在1000之内的分布)
        - 各个用户消费的总数量的直方分布图(消费商品的数量在100次之内的分布)

    代码实现

    # 用户消费总金额和消费总次数的统计描述
    df.groupby('user_id')['order_amount'].sum()
    df.groupby('user_id')['order_product'].count()
    
    # 用户消费金额和消费产品数量的散点图
    order_amount = df.groupby('user_id')['order_amount'].sum()
    order_product = df.groupby('user_id')['order_product'].sum()
    plt.scatter(order_amount,order_product)
    
    # 各个用户消费总金额的直方分布图(消费金额在1000之内的分布)
    df.groupby(by='user_id').sum().query('order_amount <= 1000')['order_amount'].hist()
    
    # 各个用户消费的总数量的直方分布图(消费商品的数量在100次之内的分布)
    df.groupby(by='user_id').sum().query('order_product <= 100')['order_product'].hist()
    View Code

    第四部分

    第四部分:用户消费行为分析
        - 用户第一次消费的月份分布,和人数统计
            - 绘制线形图
        - 用户最后一次消费的时间分布,和人数统计
            - 绘制线形图
        - 新老客户的占比
            - 消费一次为新用户
            - 消费多次为老用户
                - 分析出每一个用户的第一个消费和最后一次消费的时间
                    - agg(['func1','func2']):对分组后的结果进行指定聚合
                - 分析出新老客户的消费比例
        - 用户分层
            - 分析得出每个用户的总购买量和总消费金额and最近一次消费的时间的表格rfm
            - RFM模型设计
                - R表示客户最近一次交易时间的间隔。
                    - /np.timedelta64(1,'D'):去除days
                - F表示客户购买商品的总数量,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃。
                - M表示客户交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低。
                - 将R,F,M作用到rfm表中
            - 根据价值分层,将用户分为:
                - 重要价值客户
                - 重要保持客户
                - 重要挽留客户
                - 重要发展客户
                - 一般价值客户
                - 一般保持客户
                - 一般挽留客户
                - 一般发展客户
                    - 使用已有的分层模型即可rfm_func

    代码实现

    import pandas as pd
    from pandas import DataFrame
    
    # 读取数据
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',sep='s+',header=None,names=['user_id','order_dt','order_product','order_amount'])
    
    # 转为时间格式
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    
    # 增加月份一列
    df['month'] = df['order_dt'].astype('datetime64[M]')
    
    # 用户第一次消费的月份分布,和人数统计,绘制线形图
    df.groupby(by='user_id')['month'].min().value_counts().plot()
    
    # 用户最后一次消费的时间分布,和人数统计,绘制线形图
    df.groupby(by='user_id')['month'].max().value_counts().plot()
    
    # 新老客户的占比
    new_old_user = df.groupby(by='user_id')['order_dt'].agg(['min','max'])
    val = (new_old_user['min'] == new_old_user['max']).value_counts()
    # 新用户占比
    val[True]/(val[True] + val[False])
    # 老用户占比
    val[False]/(val[True] + val[False])
    View Code

    构建 RFM 数据表

    # 分析得出每个用户的总购买量和总消费金额and最近一次消费的时间的表格rfm
    rfm = df.pivot_table(index='user_id',aggfunc={'order_product':'sum','order_amount':'sum','order_dt':'max'})
    rfm
    
    # R表示客户最近一次交易时间的间隔
    import numpy as np
    new_date = df['order_dt'].max() # 数据中最大时间,假设为当前时间
    rfm['R'] = -(rfm.groupby(by='user_id')['order_dt'].max()-new_date)/np.timedelta64(1,'D')
    rfm
    
    # F表示客户购买商品的总数量,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃
    # M表示客户交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低
    # 将R,F,M作用到rfm表中
    
    # 删除 order_dt 列
    rfm.drop(labels='order_dt',axis=1,inplace=True)
    
    # 对列进行重命名
    rfm.columns = ['M','F','R']
    rfm
    View Code

    用户分层

    def rfm_func(x):
        level = x.map(lambda x:'1' if x>=0 else '0')
        val = level.R + level.F + level.M
        dit = {
            '111':'重要价值客户',
            '011':'重要保持客户',
            '101':'重要挽留客户',
            '001':'重要发展客户',
            '110':'一般价值客户',
            '010':'一般保持客户',
            '100':'一般挽留客户',
            '000':'一般发展客户',
        }
        respons = dit[val]
        return respons
    rfm['level'] = rfm.apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
    rfm
    View Code

    第五部分:用户生命周期

    第五部分:用户的生命周期
        - 将用户划分为活跃用户和其他用户
            - 统计每个用户每个月的消费次数
            - 统计每个用户每个月是否消费,消费为 1 否则为 0
                - 知识点:DataFrame的apply和applymap的区别
                    - applymap:返回df
                    - 将函数做用于DataFrame中的所有元素(elements)
                    - apply:返回Series
                    - apply()将一个函数作用于DataFrame中的每个行或者列
            - 将用户按照每一个月份分成:
                - unreg:观望用户(前两月没买,第三个月才第一次买,则用户前两个月为观望用户)
                - unactive:首月购买后,后序月份没有购买则在没有购买的月份中该用户的为非活跃用户
                - new:当前月就进行首次购买的用户在当前月为新用户
                - active:连续月份购买的用户在这些月中为活跃用户
                - return:购买之后间隔n月再次购买的第一个月份为该月份的回头客

    代码实现

    import pandas as pd
    from pandas import DataFrame
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',sep='s+',header=None,names=['user_id','order_dt','order_product','order_amount'])
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    df['month'] = df['order_dt'].astype('datetime64[M]')
    df
    
    # 统计每个用户每个月的消费次数
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    
    # 统计每个用户每个月是否消费,消费为 1 否则为 0
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    df_purchase = month_sum.applymap(lambda x:1 if x>0 else 0 )
    df_purchase
    View Code

    区分用户类别

    #将df_purchase中的原始数据0和1修改为new,unactive......,返回新的df叫做df_purchase_new
    #固定算法
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    one_zero = month_sum.applymap(lambda x:'1' if x>0 else '0' )
    
    def active_status(data):
        status = []#某个用户每一个月的活跃度
        for i in range(18):
            
            #若本月没有消费
            if data[i] == 0:
                if len(status) > 0:
                    if status[i-1] == 'unreg':
                        status.append('unreg')
                    else:
                        status.append('unactive')
                else:
                    status.append('unreg')
                        
            #若本月消费
            else:
                if len(status) == 0:
                    status.append('new')
                else:
                    if status[i-1] == 'unactive':
                        status.append('return')
                    elif status[i-1] == 'unreg':
                        status.append('new')
                    else:
                        status.append('active')
        return status
    
    pivoted_status = df_purchase.apply(active_status,axis=1)
    
    
    # 转为 list 格式
    pivoted_status_list = pivoted_status.values.tolist()
    
    # 生成新的数据表
    # new_start_info = DataFrame(data=start_list)
    # 生成新的数据表并更换回原 index
    new_start_info = DataFrame(data=pivoted_status_list,index=month_sum.index,columns=month_sum.columns)
    new_start_info
    View Code

    - 每月【不同活跃】用户的计数
      - purchase_status_ct = df_purchase_new.apply(lambda x : pd.value_counts(x)).fillna(0)
      - 转置进行最终结果的查看

    new_start_info_ct = new_start_info.apply(lambda x : pd.value_counts(x)).fillna(0)
    new_start_info_ct
    
    new_start_info_ct.T
  • 相关阅读:
    MIne FirstBlog
    P6563 [SBCOI2020]一直在你身旁
    P6563 [SBCOI2020]一直在你身旁
    T122085 [SBCOI2020]时光的流逝
    LC 918. Maximum Sum Circular Subarray
    1026 Table Tennis
    LC 1442. Count Triplets That Can Form Two Arrays of Equal XOR
    LC 1316. Distinct Echo Substrings
    LC 493. Reverse Pairs
    1029 Median (二分)
  • 原文地址:https://www.cnblogs.com/ysging/p/13215072.html
Copyright © 2011-2022 走看看