量化杂记


启动jupyter


启动:jupyter notebook


pandas基础


pandas基础:

# -*- coding: utf-8 -*-
"""
程序开头注释
功能:本程序主要介绍pandas最最常用的一些方法。这些方法在之后的课程、作业中都会用到。
"""
import pandas as pd  # 将pandas作为第三方库导入,我们一般为pandas取一个别名叫做pd

# =====导入数据
df = pd.read_csv(
    # 该参数为数据在电脑中的路径,可以不填写
    filepath_or_buffer='/Users/jxing/Desktop/201704课程/20170423_class3/data/sz000002.csv',
    # 该参数代表数据的分隔符,csv文件默认是逗号。其他常见的是'\t'
    sep=',',
    # 该参数代表跳过数据文件的的第1行不读入
    skiprows=1,
    # nrows,只读取前n行数据,若不指定,读入全部的数据
    nrows=15,
    # 将指定列的数据识别为日期格式。若不指定,时间数据将会以字符串形式读入。一开始先不用。
    # parse_dates=['交易日期'],
    # 将指定列设置为index。若不指定,index默认为0, 1, 2, 3, 4...
    # index_col=['交易日期'],
    # 读取指定的这几列数据,其他数据不读取。若不指定,读入全部列
    usecols=['交易日期', '股票代码', '股票名称', '收盘价', '涨跌幅', '成交量', '新浪概念', 'MACD_金叉死叉'],
    # 当某行数据有问题时,报错。设定为False时即不报错,直接跳过该行。当数据比较脏乱的时候用这个。
    error_bad_lines=False,
    # 将数据中的null识别为空值
    na_values='NULL',

    # 更多其他参数,请直接搜索"pandas read_csv",要去逐个查看一下。比较重要的,header等
)

# print df

# 使用read_csv导入数据非常方便

# 导入的数据的数据类型是DataFrame。

# 导入数据主要使用read系列函数
# 还有read_table、read_excel、read_json等,他们的参数内容都是大同小异,可以自行搜索查看。


# =====看数据
# print df.shape  # 输出dataframe有多少行、多少列。
# print df.shape[0]  # 取行数量,相应的列数量就是df.shape[1]
# print df.columns  # 顺序输出每一列的名字,演示如何for语句遍历。
# print df.index  # 顺序输出每一行的名字,可以for语句遍历。
# print df.dtypes  # 数据每一列的类型不一样,比如数字、字符串、日期等。该方法输出每一列变量类型
# print df.head(3)  # 看前3行的数据,默认是5。与自然语言很接近
# print df.tail(3)  # 看最后3行的数据,默认是5。
# print df.sample(n=3)  # 随机抽取3行,想要去固定比例的话,可以用frac参数
# print df.describe()  # 非常方便的函数,对每一列数据有直观感受;只会对数字类型的列有效

# 对print出的数据格式进行修正
# pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
# pd.set_option('max_colwidth', 8) # 设定每一列的最大宽度,恢复原设置的方法,pd.reset_option('max_colwidth')
# 更多设置请见http://pandas.pydata.org/pandas-docs/stable/options.html


# =====如何选取指定的行、列
# print df['股票代码']  # 根据列名称来选取,读取的数据是Series类型
# print df[['股票代码', '收盘价']]  # 同时选取多列,需要两个括号,读取的数据是DataFrame类型
# print df[[0, 1, 2]]  # 也可以通过列的position来选取

# loc操作:通过label(columns和index的名字)来读取数据
# print df.loc['12/12/2016']  # 选取指定的某一行,读取的数据是Series类型
# print df.loc['13/12/2016': '06/12/2016']  # 选取在此范围内的多行,和在list中slice操作类似,读取的数据是DataFrame类型
# print df.loc[:, '股票代码':'收盘价']  # 选取在此范围内的多列,读取的数据是DataFrame类型
# print df.loc['13/12/2016': '06/12/2016', '股票代码':'收盘价']  # 读取指定的多行、多列。逗号之前是行的范围,逗号之后是列的范围。读取的数据是DataFrame类型
# print df.loc[:, :]  # 读取所有行、所有列,读取的数据是DataFrame类型
# print df.at['12/12/2016', '股票代码']  # 使用at读取指定的某个元素。loc也行,但是at更高效。

# iloc操作:通过position来读取数据
# print df.iloc[0]  # 以index选取某一行,读取的数据是Series类型
# print df.iloc[1:3]  # 选取在此范围内的多行,读取的数据是DataFrame类型
# print df.iloc[:, 1:3]  # 选取在此范围内的多列,读取的数据是DataFrame类型
# print df.iloc[1:3, 1:3]  # 读取指定的多行、多列,读取的数据是DataFrame类型
# print df.iloc[:, :]  # 读取所有行、所有列,读取的数据是DataFrame类型
# print df.iat[1, 1]  # 使用iat读取指定的某个元素。使用iloc也行,但是iat更高效。


# =====列操作
# 行列加减乘除
# print df['股票名称'] + '_地产'  # 字符串列可以直接加上字符串,对整列进行操作
# print df['收盘价'] * 100  # 数字列直接加上或者乘以数字,对整列进行操作。
# print df['收盘价'] * df['成交量']  # 两列之间可以直接操作。收盘价*成交量计算出的是什么?
# 新增一列
# df['股票名称+行业'] = df['股票名称'] + '_地产'

# =====统计函数
# print df['收盘价'].mean()  # 求一整列的均值,返回一个数。会自动排除空值。
# print df[['收盘价', '成交量']].mean()  # 求两列的均值,返回两个数,Series
# print df[['收盘价', '成交量']]
# print df[['收盘价', '成交量']].mean(axis=1)  # 求两列的均值,返回DataFrame。axis=0或者1要搞清楚。
# axis=1,代表对整几列进行操作。axis=0(默认)代表对几行进行操作。实际中弄混很正常,到时候试一下就知道了。

# print df['收盘价'].max()  # 最大值
# print df['收盘价'].min()  # 最小值
# print df['收盘价'].std()  # 标准差
# print df['收盘价'].count()  # 非空的数据的数量
# print df['收盘价'].median()  # 中位数
# print df['收盘价'].quantile(0.25)  # 25%分位数
# 肯定还有其他的函数计算其他的指标,在实际使用中遇到可以自己搜索


# =====shift类函数、删除列的方式
# df['昨天收盘价'] = df['收盘价'].shift(-1)  # 读取上一行的数据,若参数设定为3,就是读取上三行的数据;若参数设定为-1,就是读取下一行的数据;
# print df[['收盘价', '昨天收盘价']]
# del df['昨天收盘价']  # 删除某一列的方法

# df['涨跌'] = df['收盘价'].diff(-1)  # 求本行数据和上一行数据相减得到的值
# print df[['收盘价', '涨跌']]
# df.drop(['涨跌'], axis=1, inplace=True)  # 删除某一列的另外一种方式,inplace参数指是否替代原来的df
# print df
# df['涨跌幅_计算'] = df['收盘价'].pct_change(-1)  # 类似于diff,但是求的是两个数直接的比例,相当于求涨跌幅


# =====cum(cumulative)类函数
# df['成交量_cum'] = df['成交量'].cumsum()  # 该列的累加值
# print df[['成交量', '成交量_cum']]
# print (df['涨跌幅'] + 1.0).cumprod()  # 该列的累乘值,此处计算的就是资金曲线,假设初始1元钱。


# =====其他列函数
# df['收盘价_排名'] = df['收盘价'].rank(ascending=True, pct=False)  # 输出排名。ascending参数代表是顺序还是逆序。pct参数代表输出的是排名还是排名比例
# print df[['收盘价', '收盘价_排名']]
# del df['收盘价_排名']
# print df['股票代码'].value_counts()  # 计数。统计该列中每个元素出现的次数。返回的数据是Series


# =====筛选操作,根据指定的条件,筛选出相关拿数据。
# print df['股票代码'] == 'sh000002'  # 判断股票代码是否等于sz000002
# print df[df['股票代码'] == 'sz000002']  # 将判断为True的输出:选取股票代码等于sz000002的行
# print df[df['股票代码'].isin(['sz000002', 'sz000003 ', 'sz000004'])]  # 选取股票代码等于sz000002的行
# print df[df['收盘价'] >= 24.0]  # 选取收盘价大于24的行
# print df[(df.index >= '03/12/2016') & (df.index <= '06/12/2016')]  # 两个条件,或者的话就是|


# =====缺失值处理:原始数据中存在缺失值,如何处理?
# 删除缺失值
# print df.dropna(how='any')  # 将带有空值的行删除。how='any'意味着,该行中只要有一个空值,就会删除,可以改成all。
# print df.dropna(subset=['MACD_金叉死叉', '涨跌幅'], how='all')  # subset参数指定在特定的列中判断空值。
# all代表全部为空,才会删除该行;any只要一个为空,就删除该行。

# 补全缺失值
# print df.fillna(value='没有金叉死叉')  # 直接将缺失值赋值为固定的值
# df['MACD_金叉死叉'].fillna(value=df['收盘价'], inplace=True)  # 直接将缺失值赋值其他列的数据
# print df.fillna(method='ffill')  # 向上寻找最近的一个非空值,以该值来填充缺失的位置,全称forward fill,非常有用
# print df.fillna(method='bfill')  # 向下寻找最近的一个非空值,以该值来填充确实的位置,全称backward fill

# 找出缺失值
# print df.notnull()  # 判断是否为空值,反向函数为isnull()
# print df[df['MACD_金叉死叉'].notnull()]  # 将'MACD_金叉死叉'列为空的行输出


# =====排序函数
# df.reset_index(inplace=True)
# print df.sort_values(by=['交易日期'], ascending=1)  # by参数指定按照什么进行排序,acsending参数指定是顺序还是逆序,1顺序,0逆序
# print df.sort_values(by=['股票名称', '交易日期'], ascending=[1, 1])  # 按照多列进行排序


# =====两个df上下合并操作,append操作
# df.reset_index(inplace=True)
# df1 = df.iloc[0:10][['交易日期', '股票代码', '收盘价', '涨跌幅']]
# print df1
# df2 = df.iloc[5:15][['交易日期', '股票名称', '收盘价', '涨跌幅']]
# print df2
# print df1.append(df2)  # append操作,将df1和df2上下拼接起来。注意观察拼接之后的index
# df3 = df1.append(df2, ignore_index=True)  # ignore_index参数,用户重新确定index
# print df3


# =====对数据进行去重
# df3中有重复的行数,我们如何将重复的行数去除?
# df3.drop_duplicates(
#     subset=['收盘价', '交易日期'],  # subset参数用来指定根据哪类类数据来判断是否重复。若不指定,则用全部列的数据来判断是否重复
#     keep='first',  # 在去除重复值的时候,我们是保留上面一行还是下面一行?first保留上面一行,last保留下面一行,False就是一行都不保留
#     inplace=True
# )
# print df3


# =====其他常用重要函数
# print df.rename(columns={'MACD_金叉死叉': '金叉死叉', '涨跌幅': '涨幅'})  # rename函数给变量修改名字。使用dict将要修改的名字传给columns参数
# print df.empty  # 判断一个df是不是为空,此处输出不为空
# print pd.DataFrame().empty  # pd.DataFrame()创建一个空的DataFrame,此处输出为空
# print df.T  # 将数据转置,行变成列,很有用


# =====字符串处理
# print df['股票代码']
# print 'sz000002'[:2]
# print df['股票代码'].str[:2]
# print df['股票代码'].str.upper()  # 加上str之后可以使用常见的字符串函数对整列进行操作
# print df['股票代码'].str.lower()
# print df['股票代码'].str.len()  # 计算字符串的长度,length
# df['股票代码'].str.strip()  # strip操作,把字符串两边的空格去掉
# print df['股票代码'].str.contains('sh')  # 判断字符串中是否包含某些特定字符
# print df['股票代码'].str.replace('sz', 'sh')  # 进行替换,将sz替换成sh
# split操作
# print df['新浪概念'].str.split(';')  # 对字符串进行分割
# print df['新浪概念'].str.split(';').str[:2]  # 分割后取第一个位置
# print df['新浪概念'].str.split(';', expand=True)  # 分割后并且将数据分列
# # 更多字符串函数请见:http://pandas.pydata.org/pandas-docs/stable/text.html#method-summary


# =====时间处理
# 导入数据时将index参数注释掉

# df['交易日期'] = pd.to_datetime(df['交易日期'])  # 将交易日期由字符串改为时间变量
# print df['交易日期']
# print df.iloc[0]['交易日期']
# print df.dtypes
# print pd.to_datetime('1999年01月01日')  # pd.to_datetime函数:将字符串转变为时间变量
# print df.at[0, '交易日期']

# print df['交易日期'].dt.year  # 输出这个日期的年份。相应的month是月份,day是天数,还有hour, minute, second
# print df['交易日期'].dt.week  # 这一天是一年当中的第几周
# print df['交易日期'].dt.dayofyear  # 这一天是一年当中的第几天
# print df['交易日期'].dt.dayofweek  # 这一天是这一周当中的第几天,0代表星期一
# print df['交易日期'].dt.weekday  # 和上面函数相同,更加常用
# print df['交易日期'].dt.weekday_name  # 和上面函数相同,返回的是星期几的英文,用于报表的制作。
# print df['交易日期'].dt.days_in_month  # 这一天是这一月当中的第几天
# print df['交易日期'].dt.is_month_end  # 这一天是否是该月的开头,是否存在is_month_end?
# print df['交易日期'] + pd.Timedelta(days=1)  # 增加一天,Timedelta用于表示时间差数据
# print (df['交易日期'] + pd.Timedelta(days=1)) - df['交易日期']  # 增加一天然后再减去今天的日期


# =====rolling、expanding操作
# 计算'收盘价'这一列的均值
# print df['收盘价'].mean()
# 如何得到每一天的最近3天收盘价的均值呢?即如何计算常用的移动平均线?
# 使用rolling函数
# df['收盘价_3天均值'] = df['收盘价'].rolling(5).mean()
# print df[['收盘价', '收盘价_3天均值']]
# rolling(n)即为取最近n行数据的意思,只计算这n行数据。后面可以接各类计算函数,例如max、min、std等
# print df['收盘价'].rolling(3).max()
# print df['收盘价'].rolling(3).min()
# print df['收盘价'].rolling(3).std()

# rolling可以计算每天的最近3天的均值,如果想计算每天的从一开始至今的均值,应该如何计算?
# 使用expanding操作
# df['收盘价_至今均值'] = df['收盘价'].expanding().mean()
# print df[['收盘价', '收盘价_至今均值']]

# expanding即为取从头至今的数据。后面可以接各类计算函数
# print df['收盘价'].expanding().max()
# print df['收盘价'].expanding().min()
# print df['收盘价'].expanding().std()

# rolling和expanding简直是为量化领域量身定制的方法,经常会用到。


# =====输出
# print df
# df.to_csv('output.csv', encoding='gbk', index=False)


# =====文档
# 以上是我认为最常用的函数
# 哪里可以看到全部的函数?http://pandas.pydata.org/pandas-docs/stable/api.html
# 一般的使用方法

resample


resample获取周、月数据。将数据转换为制定周期:开盘价(周期第1天)、收盘价(周期最后1天)、最高价(周期内)、最低价(周期内)。

import pandas as pd
import datetime
import time
import os

pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
pd.set_option('max_row',4000)#最多行数

# =====读入数据
df = pd.read_csv('G:/share//note/000001上证指数.csv', encoding='gbk')
df.columns = [i for i in df.columns]
df = df[['日期','收盘价','开盘价','最高价','最低价','涨跌幅']]
df.sort_values(by=['日期'], inplace=True)
df['日期'] = pd.to_datetime(df['日期'])
# 最近十二年数据
df = df[df['日期'] >= '2009-04-12']
df.reset_index(inplace=True, drop=True)
print(df)

def transfer_price_freq(data, time_freq):
    """
    将数据转换为制定周期:开盘价(周期第1天)、收盘价(周期最后1天)、最高价(周期内)、最低价(周期内)
    :param data:
    :param time_freq:
    :return:
    """
    df_trans = pd.DataFrame()
    df_trans['开盘价'] = data['开盘价'].resample(time_freq).first()
    df_trans['收盘价'] = data['收盘价'].resample(time_freq).last()
    df_trans['最高价'] = data['最高价'].resample(time_freq).max()
    df_trans['最低价'] = data['最低价'].resample(time_freq).min()
    return df_trans

# 转换index
df = df.set_index(df['日期'])
#print(transfer_price_freq(df,'M'))
print(transfer_price_freq(df,'W'))

shift涨跌幅


def calculate_change_pct(data):
    """
    涨跌幅 = (当期收盘价-前期收盘价) / 前期收盘价
    :param data: dataframe,带有收盘价
    :return: dataframe,带有涨跌幅
    """
    data['close_pct'] = (data['close'] - data['close'].shift(1)) \
                        / data['close'].shift(1)
    return data

数据存储和读取


def update_daily_price(stock_code, type='price'):
    # 3.1是否存在文件:不存在-重新获取,存在->3.2
    file_root = data_root + type + '/' + stock_code + '.csv'
    if os.path.exists(file_root):  # 如果存在对应文件
        # 3.2获取增量数据(code,startsdate=对应股票csv中最新日期,enddate=今天)
        startdate = pd.read_csv(file_root, usecols=['date'])['date'].iloc[-1]
        df = get_single_price(stock_code, 'daily', startdate, datetime.datetime.today())
        # 3.3追加到已有文件中
        export_data(df, stock_code, 'price', 'a')
    else:
        # 重新获取该股票行情数据
        df = get_single_price(stock_code, 'daily')
        export_data(df, stock_code, 'price')

    print("股票数据已经更新成功:", stock_code)


def export_data(data, filename, type, mode=None):
    """
    导出股票相关数据
    :param data:
    :param filename:
    :param type: 股票数据类型,可以是:price、finance
    :param mode: a代表追加,none代表默认w写入
    :return:
    """
    file_root = data_root + type + '/' + filename + '.csv'
    data.index.names = ['date']
    if mode == 'a':
        data.to_csv(file_root, mode=mode, header=False)
        # 删除重复值
        data = pd.read_csv(file_root)  # 读取数据
        data = data.drop_duplicates(subset=['date'])  # 以日期列为准
        data.to_csv(file_root, index=False)  # 重新写入
    else:
        data.to_csv(file_root)  # 判断一下file是否存在 > 存在:追加 / 不存在:保持

    print('已成功存储至:', file_root)

计算交易指标


import pandas as pd
import numpy as np
import datetime
import matplotlib.pyplot as plt

def get_single_price():
    """
    获取单数据
    """
    pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
    pd.set_option('max_row',4000)#最多行数

    # =====读入数据
    df = pd.read_csv('G:/share/note/000001上证指数.csv', encoding='gbk')
    df.columns = [i for i in df.columns]
    df = df[['日期','收盘价','开盘价','最高价','最低价','涨跌幅']]
    df.sort_values(by=['日期'], inplace=True)
    df['日期'] = pd.to_datetime(df['日期'])
    # 最近十二年数据
    df = df[df['日期'] >= '2009-04-12']
    df.reset_index(inplace=True, drop=True)
    df = df.set_index(df['日期'])
    return df

def compose_signal(data):
    """
    整合信号
    :param data:
    :return:
    """
    data['buy_signal'] = np.where((data['buy_signal'] == 1)
                                  & (data['buy_signal'].shift(1) == 1), 0, data['buy_signal'])
    data['sell_signal'] = np.where((data['sell_signal'] == -1)
                                   & (data['sell_signal'].shift(1) == -1), 0, data['sell_signal'])
    data['signal'] = data['buy_signal'] + data['sell_signal']
    return data

def calculate_prof_pct(data):
    """
    计算单次收益率:开仓、平仓(开仓的全部股数)
    :param data:
    :return:
    """
    data = data[data['signal'] != 0]  # 筛选
    data['profit_pct'] = (data['收盘价'] - data['收盘价'].shift(1)) / data['收盘价'].shift(1)
    data = data[data['signal'] == -1]
    return data

def calculate_cum_prof(data):
    """
    计算累计收益率
    :param data: dataframe
    :return:
    """
    data['cum_profit'] = pd.DataFrame(1 + data['profit_pct']).cumprod() - 1
    return data

def caculate_max_drawdown(data):
    """
    计算最大回撤比
    :param data:
    :return:
    """
    # 选取时间周期(时间窗口)
    window = 252
    # 选取时间周期中的最大净值
    data['roll_max'] = data['收盘价'].rolling(window=window, min_periods=1).max()
    # 计算当天的回撤比 = (谷值 — 峰值)/峰值 = 谷值/峰值 - 1
    data['daily_dd'] = data['收盘价'] / data['roll_max'] - 1
    # 选取时间周期内最大的回撤比,即最大回撤
    data['max_dd'] = data['daily_dd'].rolling(window, min_periods=1).min()

    return data

def calculate_sharpe(data):
    """
    计算夏普比率,返回的是年化的夏普比率
    :param data: dataframe, stock
    :return: float
    """
    # 公式:sharpe = (回报率的均值 - 无风险利率) / 回报率的标准差
    daily_return = data['收盘价'].pct_change()
    avg_return = daily_return.mean()
    sd_reutrn = daily_return.std()
    # 计算夏普:每日收益率 * 252 = 每年收益率
    sharpe = avg_return / sd_reutrn
    sharpe_year = sharpe * np.sqrt(252)
    return sharpe, sharpe_year

def week_period_strategy(data):
    """
    周期选股(周四买,周一卖)
    """
    # 新建周期字段
    data['weekday'] = data.index.weekday
    # 周四买入
    data['buy_signal'] = np.where((data['weekday'] == 3), 1, 0)
    # 周一卖出
    data['sell_signal'] = np.where((data['weekday'] == 0), -1, 0)

    data = compose_signal(data)  # 整合信号
    data = calculate_prof_pct(data)  # 计算收益
    data = calculate_cum_prof(data)  # 计算累计收益率
    # data = caculate_max_drawdown(data)  # 最大回撤
    return data

if __name__ == '__main__':

    df = get_single_price()
    df = week_period_strategy(df)
    df = df[['收盘价','开盘价','涨跌幅','signal','profit_pct','cum_profit']]
    
    caculate_max_drawdown(df)
    df[['daily_dd','daily_dd']].plot()
    plt.show()
    print(df)
    
    df['profit_pct'].plot()
    plt.show()
    
    df['cum_profit'].plot()
    plt.show()

    #计算夏普比率
    sharpe = calculate_sharpe(df)
    print(sharpe)

双曲均线多股比较


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def get_single_price(code):
    """
    获取单数据
    """
    pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
    pd.set_option('max_row',4000)#最多行数

    # =====读入数据
    df = pd.read_csv('G:/share/note/' + code + '.csv', encoding='gbk')
    df.columns = [i for i in df.columns]
    df = df[['日期','收盘价','开盘价','最高价','最低价','涨跌幅']]
    df.sort_values(by=['日期'], inplace=True)
    df['日期'] = pd.to_datetime(df['日期'])
    # 最近数据
    df = df[df['日期'] >= '2019-01-01']
    df.reset_index(inplace=True, drop=True)
    df = df.set_index(df['日期'])
    return df

def compose_signal(data):
    """
    整合信号
    :param data:
    :return:
    """
    data['buy_signal'] = np.where((data['buy_signal'] == 1)
                                  & (data['buy_signal'].shift(1) == 1), 0, data['buy_signal'])
    data['sell_signal'] = np.where((data['sell_signal'] == -1)
                                   & (data['sell_signal'].shift(1) == -1), 0, data['sell_signal'])
    data['signal'] = data['buy_signal'] + data['sell_signal']
    return data

def calculate_prof_pct(data):
    """
    计算单次收益率:开仓、平仓(开仓的全部股数)
    :param data:
    :return:
    """
    data = data[data['signal'] != 0]  # 筛选
    data['profit_pct'] = (data['收盘价'] - data['收盘价'].shift(1)) / data['收盘价'].shift(1)
    # 数据预览
    #print(data[['收盘价', 'short_ma', 'long_ma','signal','profit_pct']])
    data = data[data['signal'] == -1]
    return data

def calculate_cum_prof(data):
    """
    计算累计收益率
    :param data: dataframe
    :return:
    """
    data['cum_profit'] = pd.DataFrame(1 + data['profit_pct']).cumprod() - 1
    return data

def ma_strategy(data, short_window=5, long_window=20):
    """
    双均线策略
    :param data: dataframe, 投资标的行情数据(必须包含收盘价)
    :param short_window: 短期n日移动平均线,默认5
    :param long_window: 长期n日移动平均线,默认20
    :return: 
    """
    data = pd.DataFrame(data)
    # 计算技术指标:ma短期、ma长期
    data['short_ma'] = data['收盘价'].rolling(window=short_window).mean()
    data['long_ma'] = data['收盘价'].rolling(window=long_window).mean()

    # 生成信号:金叉买入、死叉卖出
    data['buy_signal'] = np.where(data['short_ma'] > data['long_ma'], 1, 0)
    data['sell_signal'] = np.where(data['short_ma'] < data['long_ma'], -1, 0)
    # 过滤信号
    data = compose_signal(data)
    # 计算单次收益
    data = calculate_prof_pct(data)
    # print(data.describe())
    # 计算累计收益
    data = calculate_cum_prof(data)
    # 删除多余的columns
    data.drop(labels=['buy_signal', 'sell_signal'], axis=1)
    return data

if __name__ == '__main__':
    # 股票列表
    stocks = ['000001上证指数', '000300沪深300', '000905中证500']
    # 存放累计收益率
    cum_profits = pd.DataFrame()
    # 循环获取数据
    for code in stocks:
        df = get_single_price(code)
        # 调用双均线策略
        df = ma_strategy(df)  
        cum_profits[code] = df['cum_profit'].reset_index(drop=True)  # 存储累计收益率
        # 折线图
        df['cum_profit'].plot(label=code)
        # 筛选有信号点
        # df = df[df['signal'] != 0]
        # 预览数据
        print("开仓次数:", int(len(df)))
        #print(df[['日期','收盘价', 'signal', 'cum_profit']])

    # 预览
    print(cum_profits)
    # 可视化
    # cum_profits.plot()
    plt.legend()
    plt.title('Comparison of Ma Strategy Profits')
    plt.show()

P值计算


p值越小,可能就越赚钱。p_value < 0.05

import matplotlib.pyplot as plt
from scipy import stats

def ttest(data_return):
    """
    对策略收益进行t检验
    :param strat_return: dataframe,单次收益率
    :return: float,t值和p值
    """
    # 调用假设检验ttest函数:scipy
    t, p = stats.ttest_1samp(data_return, 0, nan_policy='omit')
    # 判断是否与理论均值有显著性差异:α=0.05
    p_value = p / 2  # 获取单边p值
    # 打印
    print("t-value:", t)
    print("p-value:", p_value)
    print("是否可以拒绝[H0]收益均值=0:", p_value < 0.05)
    return t, p_value

if __name__ == '__main__':
    # 股票列表
    stocks = ['000001.XSHE', '000858.XSHE', '002594.XSHE']
    for code in stocks:
        print(code)
        df = get_single_price(code, 'daily', '2016-12-01', '2021-01-01')
        df = ma_strategy(df)  # 调用双均线策略
        # 策略的单次收益率
        returns = df['profit_pct']
        # print(returns)
        # 绘制一下分布图用于观察
        # plt.hist(returns, bins=30)
        # plt.show()
        # 对多个股票进行计算、测试
        ttest(returns)

资源网站


github开源项目:

http://sunshowerc.github.io/fund/#/

https://github.com/x2rr/funds

东方财富:

//BOLL + MACD
https://www.eastmoney.com/

聚宽:

https://www.joinquant.com

数据下载:

https://query1.finance.yahoo.com/v7/finance/download/000001.SS?period1=1262304000&period2=1579106020&interval=1d&events=history&crumb=t6/7IIwak4j 

https://fundsuggest.eastmoney.com/FundSearch/api/FundSearchAPI.ashx?m=10&t=700&IsNeedBaseInfo=0&IsNeedZTInfo=0&key=${key}&_=${Date.now()}

http://quotes.money.163.com/service/chddata.html?code=0000905&start=19901219&end=20200913'

我们从得到的链接入手来分析:http://quotes.money.163.com/service/chddata.html?code=1000002&start=19910129&end=20161006&fields=TCLOSE;HIGH;LOW;TOPEN;LCLOSE;CHG;PCHG;TURNOVER;VOTURNOVER;VATURNOVER;TCAP;MCAP 其中:

code: 深市六位代码前加“1”,沪市股票代码前加“0”
start: 开始日期,如果想得到每只股票的所有历史交易数据,可以以公司上市日期来表达,8位数字,分别为yyyymmdd
end: 结束日期,表示的也是yyyymmdd八位数字
fields字段包括了开盘价、最高价、最低价、收盘价等。。。

code的另一种获取方式:访问 http://quotes.money.163.com/stock,然后搜索股票,然后再浏览器的链接上获得股票code。

举例-下载数据文件:

import os
import requests
import time

# 时间格式:20160320
timeParams = time.strftime("%Y%m%d", time.localtime())

# 文件名和文件路径
urls = [('000905中证500.csv',"http://quotes.money.163.com/service/chddata.html?code=0000905&start=19901219&end=%s" % timeParams),
       ('000016上证50.csv',"http://quotes.money.163.com/service/chddata.html?code=0000016&start=19901219&end=%s" % timeParams),
       ('000300沪深300.csv',"http://quotes.money.163.com/service/chddata.html?code=0000300&start=19901219&end=%s" % timeParams),
       ('000922中证红利.csv',"http://quotes.money.163.com/service/chddata.html?code=0000922&start=19901219&end=%s" % timeParams),
       ('399812养老产业.csv',"http://quotes.money.163.com/service/chddata.html?code=1399812&start=19901219&end=%s" % timeParams),
       ('000991全指医药.csv',"http://quotes.money.163.com/service/chddata.html?code=0000991&start=19901219&end=%s" % timeParams),
       ('000827中证环保.csv',"http://quotes.money.163.com/service/chddata.html?code=0000827&start=19901219&end=%s" % timeParams),
       ('399975证券公司.csv',"http://quotes.money.163.com/service/chddata.html?code=1399975&start=19901219&end=%s" % timeParams),
       ('399971中证传媒.csv',"http://quotes.money.163.com/service/chddata.html?code=1399971&start=19901219&end=%s" % timeParams),
       ('399006创业板指.csv',"http://quotes.money.163.com/service/chddata.html?code=1399006&start=19901219&end=%s" % timeParams),
       ('000932中证消费.csv',"http://quotes.money.163.com/service/chddata.html?code=0000932&start=19901219&end=%s" % timeParams),
       ('399986中证银行.csv',"http://quotes.money.163.com/service/chddata.html?code=1399986&start=19901219&end=%s" % timeParams),
       ('399967中证军工.csv',"http://quotes.money.163.com/service/chddata.html?code=1399967&start=19901219&end=%s" % timeParams),
       ('000001上证指数.csv',"http://quotes.money.163.com/service/chddata.html?code=0000001&start=19901219&end=%s" % timeParams),
       ('399001深证成指.csv',"http://quotes.money.163.com/service/chddata.html?code=1399001&start=19901219&end=%s" % timeParams),
       ('000993全指信息.csv',"http://quotes.money.163.com/service/chddata.html?code=0000993&start=19901219&end=%s" % timeParams),
       ('399330深证100.csv',"http://quotes.money.163.com/service/chddata.html?code=1399330&start=19901219&end=%s" % timeParams),
       ('000903中证100.csv',"http://quotes.money.163.com/service/chddata.html?code=0000903&start=19901219&end=%s" % timeParams)]

def url_response(url):
    path,url = url
    print(url)
    r = requests.get(url,stream= True)
    with open(path,'wb') as f:
        for ch in r:
            f.write(ch)
for x in urls:
    url_response(x)

操作

import pandas as pd
import datetime
import datetime

pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
pd.set_option('max_row',4000)#最多行数

# =====读入数据
df = pd.read_csv('G:/share/04/note/.ipynb_checkpoints/000905中证500.csv', encoding='gbk')
df.columns = [i for i in df.columns]
df = df[['日期','收盘价','开盘价','最高价','最低价','涨跌幅']]
df.sort_values(by=['日期'], inplace=True)
df['日期'] = pd.to_datetime(df['日期'])
# 最近十二年数据
df = df[df['日期'] >= '2009-04-12']
df.reset_index(inplace=True, drop=True)

# # =====计算复权价
#df['复权因子'] = (df['涨跌幅'] + 1.0).cumprod()
# initial_price = df.iloc[0]['收盘价'] / (1 + df.iloc[0]['涨跌幅'])  # 计算上市价格
# df['收盘价_后复权'] = initial_price * df['复权因子']  # 相乘得到复权价
# df['开盘价_后复权'] = df['开盘价'] / df['收盘价'] * df['收盘价_后复权']
# df['最高价_后复权'] = df['最高价'] / df['收盘价'] * df['收盘价_后复权']
# df['最低价_后复权'] = df['最低价'] / df['收盘价'] * df['收盘价_后复权']
# # df[['开盘价', '最高价', '最低价', '收盘价']] = df[['开盘价_后复权', '最高价_后复权', '最低价_后复权', '收盘价_后复权']]
# # df = df[['日期', '股票代码', '开盘价', '最高价', '最低价', '收盘价', '涨跌幅', '开盘价_后复权', '最高价_后复权', '最低价_后复权', '收盘价_后复权']]
# df = df[['日期', '收盘价', '涨跌幅', '开盘价_后复权', '最高价_后复权', '最低价_后复权', '收盘价_后复权']]


# =====计算均线策略
# 均线策略:
# 当短期均线由下向上穿过长期均线的时候,第二天以开盘价全仓买入并在之后一直持有股票。
# 当短期均线由上向下穿过长期均线的时候,第二天以开盘价卖出全部股票并在之后一直空仓,直到下一次买入。

# ===计算均线
ma_short = 1  # 短期均线。ma代表:moving_average
ma_long = 20  # 长期均线
df['ma_short'] = df['收盘价'].rolling(ma_short, min_periods=1).mean()
df['ma_long'] = df['收盘价'].rolling(ma_long, min_periods=1).mean()

# ===找出买入信号
# 当天的短期均线大于等于长期均线
condition1 = (df['ma_short'] >= df['ma_long'])
# 上个交易日的短期均线小于长期均线
condition2 = (df['ma_short'].shift(1) < df['ma_long'].shift(1))
# 将买入信号当天的signal设置为1
df.loc[condition1 & condition2, 'signal'] = 1

# ===找出卖出信号
# 当天的短期均线小于等于长期均线
condition1 = (df['ma_short'] <= df['ma_long'])
# 上个交易日的短期均线大于长期均线
condition2 = (df['ma_short'].shift(1) > df['ma_long'].shift(1))
# 将买入信号当天的signal设置为0
df.loc[condition1 & condition2, 'signal'] = 0
# 将无关的变量删除
# df.drop(['ma_short', 'ma_long'], axis=1, inplace=True)

# =====由signal计算出实际的每天持有股票仓位
# ===计算仓位
# signal的计算运用了收盘价,是每天收盘之后产生的信号,到第二天的时候,仓位position才会改变。
# 例如2009-11-17产生买入信号,2009-11-18仓位才会编变成1。满仓用1表示,空仓用0表示
df['pos'] = df['signal'].shift()
df['pos'].fillna(method='ffill', inplace=True)
df['pos'].fillna(value=0, inplace=True)  # 将初始行数的position补全为0


# 涨跌幅定义为今日收盘价减去昨日收盘价
#rise = df['收盘价'].diff()
#df['rise'] = rise

# 涨跌幅定义为今日收盘价减去昨日收盘价/昨日收盘价
#rets = df['收盘价'].pct_change() * 100
#df['涨跌幅'] = rets

print(df)
#print(df[(df.signal > 0.5) | (df.signal == 0.0)]) 

print("-----------------------------------------------------")

print (df['收盘价'].describe())

print("-----------------------------------------------------")

print(df['收盘价'].max())  # 最大值
print(df['收盘价'].min())  # 最小值
print(df['收盘价'].std())  # 标准差
print(df['收盘价'].count())  # 非空的数据的数量
print(df['收盘价'].median())  # 中位数
print(df['收盘价'].quantile(0.25))  # 25%分位数
print(df['收盘价'].mean()) #平均值

杂记


下降行情->最低->上升行情->最高->下降行情

量化投资:本质是数据里面找规律。市场越不成熟越有效。

择时策略:比如移动平均线:20日的平均值,股价超过这个值就买入,股价低于这个值就卖掉;或者平均值比上次大就买入,平均值比上次小就卖出。

股票的量化投资可以说是一种价值投资,我们所做的也是去挖掘市场中的价值股票,而并非去预测股票涨跌来进行投资等等(至少目前机构不会采取这种方式指导投资)。

量化投资涵盖了整个交易过程,需要一个完整的作为研究的量化回测框架和实盘交易系统作为支撑。

https://www.ricequant.com/

每个投资策略的收益率可以分解成为两部分:一部分与市场完全相关,整个市场的平均收益率乘以一个贝塔系数。贝塔可以称为这个投资组合的系统风险。另一部分和整个市场无关的叫做**阿尔法(Alpha)**。

1、Alpha很难得,Beta很容易。2、Alpha就是精选个股,跑赢市场。3、Beta就是有市场行情时跟上,有风险时候躲避。

多因子选股策略是一种应用十分广泛的选股策略,其基本思想就是找到某些和收益率最相关的因子



文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录