最近事情好像有点多,处理得心不在焉。之前国庆计划把张五常老师的经济解释卷二看完,但也是只把第三章生产的成本看了一下,哈哈~
这是一篇python金融量化分析的闲杂且入门的笔记,感觉学习价值较低,我只是记一下我码的代码,怎么说。我也是刚刚接触这一块,后面有时间再看看书深入学习一下吧~
(1)金融:金融就是对现有资源进行重新整合之后,实现价值和利润的等效流通。
(2)金融工具:金融工具是在金融市场中可交易的金融资产。主要分为股票、期货、黄金、外汇、基金、债券等。
(3金融分析:
(4)为什么需要量化交易?
金融量化分析主要是指以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据当中选出能够带来超额收益的多种“大概率”事件以此来指定策略。
主要就是以下几步:灵光乍现、细化策略、策略转程序、检验策略结果、回测、模拟交易、实盘交易
量化交易的价值:
(5)金融书籍推荐
PS. 这是本人的一些认识。此外我也是刚刚入门
下面是一些常用的量化平台使用介绍
(1)Tushare pro是一个python财经数据接口包,说明文档很详细,可以通过文档学习。不过现在开始要积分了呀~
(2)主要实现对股票等金融数据从数据采集、清洗加工 到 数据存储的过程,能够为金融分析人员提供快速、整洁、和多样的便于分析的数据,为他们在数据获取方面极大地减轻工作量,使他们更加专注于策略和模型的研究与实现上。
(3)考虑到Python pandas包在金融量化分析中体现出的优势,Tushare返回的绝大部分的数据格式都是pandas DataFrame类型,非常便于用pandas/NumPy/Matplotlib进行数据分析和可视化。
(4)安装:pip install tushare
/ 访问https://pypi.python.org/pypi/Tushare/下载安装
(5)学习文档:https://tushare.pro/
import tushare as ts
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
# 方法1
# ts.set_token('你的token')
# pro = ts.pro_api()
# 方法2
pro = ts.pro_api('你的token')
# 基本使用
# 使用tushare 获取五粮液股票的历史行情数据
# 输出该股票所有收盘比开盘上涨3%以上的日期
# 输出该股票所有开盘比前日收盘跌幅超过2%的日期
# 假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票。到今天为止,我的收益如何?
# 使用tushare 获取五粮液股票的历史行情数据
# 学习于:https://tushare.pro/document/2?doc_id=27
wly_stock = pro.daily(ts_code="000858.SZ",start_date="20000101")
wly_stock.info()
wly_stock.head()
"""
将数据 保存为csv文件
"""
wly_stock.to_csv("000858.csv",index=False)
"""
读取五粮液文件数据,指定trade_date为时间序列索引。
- parse_dates 设置索引为时间序列索引。注意:类型-->list
"""
wly_df = pd.read_csv("000858.csv",index_col="trade_date",parse_dates=["trade_date"])
wly_df.info()
wly_df.head()
# 输出该股票所有收盘比开盘上涨3%以上的日期
"""
(close - open)/open>0.03 日期
"""
wly_df[(wly_df["close"]- wly_df["open"])/wly_df["open"]>0.1].index
# 输出该股票所有开盘比前日收盘跌幅超过2%的日期
"""
(开盘 - 前日收盘)/前日收盘
"""
wly_df[(wly_df["open"]-wly_df["close"].shift(-1))/wly_df["close"].shift(-1)>=0.02].index
# 假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票。到今天为止,我的收益如何?
"""
得出信息:
- 每月第一个交易日买入:1.支出 2.支出的是每月第一个交易日该股票的开盘价*1手
- 每年最后一个交易日卖出:2.收入 2.收入的是每年最后一个交易日该股票的开盘价*12手
"""
# 截取 2010开始 2019年结束
# wly_df["2010":"2019"] # 注意:为空,时间序列连续切片也是有序的
wly_df2 = wly_df["2019":"2010"]
# 每月第一个交易日数据 first()取每个月中第一个交易日的数据
wly_monthly_f = wly_df2.resample("MS").first()
wly_monthly_f.head()
# 每年最后一个交易日数据
wly_yearly_l = wly_df2.resample("A").last()
wly_yearly_l.head()
"""
收益 = 收入 - 成本
"""
# 初始化 每个月需要购买股票所花的成本
cost_money = 0
# 初始化 已持有的股票
hold = 0
# 获取 2010-2019 年 每一年 所有的 成本 以及收入
for year in range(2010,2019):
cost_money = wly_monthly_f[str(year)]["open"].sum()*100 # 每年每个月初买100手花的钱
hold = len(wly_monthly_f[str(year)])*100 # 一年持有的股票
sell_money = wly_yearly_l[str(year)]["open"].values[0]*hold # 年末卖出的价格*所卖出的股数
cost_money -= sell_money
hold = 0 # 卖出去,所以归为0
print(cost_money)
# 成本-收入 = -收益
print(f"save_money:{-cost_money},hold:{hold}")
对于每一个交易日,都可以计算出前N天的移动平均值,然后把这些移动平均值连起来,成为一条线,就叫做N日移动平均线。
移动平均线常用:5天,10天,30天,60天,120天和240天的指标
# 使用tushare包获取某股票的历史行情数据
# 初始化pro接口
pro = ts.pro_api('b003e7cabaa5ed2b7562426afe5e9802608f6c68563cb0f01290e91f')
pro
# 获取云南白药 2010年 至今的数据
by_stock = pro.daily(ts_code="000538.SZ",start_date="20100101")
# 保存至csv
by_stock.to_csv("000538.csv",index=False)
# 读取云南白药的数据
by_df = pd.read_csv("000538.csv",index_col="trade_date",parse_dates=["trade_date"])[["open","high","low","close"]]
by_df.head()
# 时间升序
by_df.sort_index(inplace=True)
# 使用tushare包计算该股票历史数据的5日均线和30日均线
# 添加 MA5,MA30 两列 赋值为nan
by_df["MA5"] = np.nan
by_df["MA30"] = np.nan
by_df.head()
"""
MA5
需求:0~4 1~5 2~6...
规律:定义一个变量i i的取值范围为4~len(by_df)。i-4~i+1
"""
for i in range(4,len(by_df)): # 从样本的第5个值才开始计算
# 获取MA5列i行的值 赋值为 收盘价的5日均价
by_df.loc[by_df.index[i],"MA5"] = by_df["close"][i-4:i+1].mean() # 取头不取尾
"""
MA30
需求:0~29 30~59 59~89...
规律:定义一个变量i i的取值范围为4~len(by_df)。i-4~i+1
"""
for i in range(29,len(by_df)):
# 获取MA30列i行的值 赋值为 收盘价的30日均价
by_df.loc[by_df.index[i],"MA30"] = by_df["close"][i-29:i+1].mean()
by_df.head()
# """
# 法2:rolling实现
# """
# by_df["close"].rolling(5).mean()
# by_df["close"].rolling(30).mean()
by_df[["close","MA5","MA30"]].plot()
plt.show()
# 分析输出所有金叉日期和死叉日期
# 清洗nan的值
by_data = by_df.dropna()
by_data.info()
by_data.head()
"""
法1:
- 金叉 : 短期均线 上穿 长期均线 --> 前一天 MA5<=MA30 并且 后一天MA5>=MA30
- 死叉 : 短期均线 下穿 长期均线 --> 前一天 MA5>=MA30 并且 后一天MA5<=MA30
"""
golden_cross = []
death_cross = []
for i in range(1,len(by_data)):
# 金叉
if by_data["MA5"][i] >= by_data["MA30"][i] and by_data["MA5"][i-1] <= by_data["MA30"][i-1]:
# 将 金叉 的时间 构建成列表
# print(by_data.index[i])
golden_cross.append(by_data.index[i])
# 死叉
if by_data["MA5"][i] <= by_data["MA30"][i] and by_data["MA5"][i-1] >= by_data["MA30"][i-1]:
# 将 死叉 的时间 构建成列表
death_cross.append(by_data.index[i])
print(golden_cross)
print(death_cross)
"""
方法2
以金叉为例
- 短均比长均波动大
- 短均上穿长均 为 金叉
- 金叉跟死叉交替出现
死叉过度到金叉
- MA5TF则为金叉点
如:
< TTTTFFFF
> FFFFTTTT
< TTTTFFFF
> FFFFTTTT
两个都是F的时候 金叉
两个都是T的时候 死叉
"""
se1 = by_data["MA5"] < by_data["MA30"]
se2 = by_data["MA5"] >= by_data["MA30"]
"""
两个都是F的时候 金叉
"""
golden_cross2 = by_data[~(se1 | se2.shift(1))].index # ma5大于ma30,或者前一天的ma5小于ma30
"""
两个都是T的时候 死叉
"""
death_cross02 = by_data[se1 & se2.shift(1)].index # ma5小于ma30,并且前一天的ma5大于等于ma30
# 假如我从2010年1月1日开始,初始资金为10W,金叉尽量买入,死叉全部卖出,则到今天为止。我的炒股收益率如何?
sr1 = pd.Series(1,index=golden_cross2) # 1代表 金叉
sr2 = pd.Series(0,index=death_cross02) # 0代表 死叉
"""
sr1 sr2拼接至一个series对象
"""
sr = sr1.append(sr2).sort_index()
sr
money = 100000 # 起始资金
hold = 0 # 持有股
for i in range(0,len(sr)):
"""
买卖需要价格
- 获取open这列
- 再通过取出sr的时间索引获取对应的时间价格
"""
p = by_data["open"][sr.index[i]]
# 并不确定这个循环的点 是死叉还是金叉
# 判断 如果说,该索引对应的值为1的时候 金叉 买入
if sr.iloc[i] == 1:
# 金叉
buy = money//(100*p) # 买入多少股 100的倍数
hold += buy*100 # 一共持有多少股
money -= buy*100*p # 买入了,起始资金会减少
else:
# 死叉 卖出
money += hold*p # 卖出就说明,资金变多了
hold = 0
"""
注意:
如果最后一个点是金叉点 买入 所花的这笔钱 也算作 你的收入
"""
money
聚宽官网:https://www.joinquant.com
有比较详细的介绍,不想描述了
以下代码均为在聚宽策略上的测试
# 聚宽基础使用
# • 每天买100股的平安银行
'''
聚宽的基础使用
每天买100股的平安银行股票
'''
# 1.初始化函数
def initialize(context):
# 初始化股票代码、全局变量、为平安银行;多支股票则可指定为列表
g.security = "000001.XSHE"
#初始化定时运行策略
run_daily(period,time="every_bar")
# 2.定时执行策略函数
def period(context):
# 每天买入100股,下单函数:API搜value --> 策略API介绍/交易函数
# 注意:因为买入的单位为100股的倍数,所以就算买的是130股,也会调整为每天买入100股
order(g.security,100)
# 计算该股票收益率,当股票亏损达到1%时,则卖出股票,幅度自己调整
# 获得股票的持仓成本
cost = context.portfolio.positions["000001.XSHE"].avg_cost
# 获得股票现价
price = context.portfolio.positions["000001.XSHE"].price
# 计算收益率
ret = price/cost - 1
# 如果收益率小于-0.01,即亏损达到1%则卖出股票
if ret<-0.01:
order_target('000001.XSHE',0)
# 简单量化策略
# 设置股票池为沪深300的所有成分股
# 如果当前股价⼩于10元/股且当前不持仓,则买⼊
# 如果当前股价⽐买⼊时上涨了25%,则清仓⽌盈
# 如果当前股价⽐买⼊时下跌了10%,则清仓⽌损
# 导入函数库
from jqdata import *
# 1.初始化函数
def initialize(context):
# 设置股票池为沪深300的所有成分股
g.security = get_index_stocks("000300.XSHG")
# 每个交易日执行一次
run_daily(period,time="every_bar")
# 2.每个交易日执行一次的函数
def period(context):
# 初始化tobuy
tobuy = []
# 筛选每支股票 --> 遍历出每支股票的股票代码
for stock in g.security:
"""
2. 如果当前股价小于10元/股且当前不持仓,则买入
- 每股开盘价
- 是否持仓
"""
# 获取当前股票的开盘价
p = get_current_data()[stock].day_open
# 获取当前股票持有股数
amount = context.portfolio.positions[stock].total_amount
# 买的时候成本价格
cost = context.portfolio.positions[stock].avg_cost
# 3. 如果当前股价⽐买⼊时上涨 25% ,则清仓⽌盈
if amount > 0 and p>= cost*(1+0.25):
order_target(stock,0)
# 4.如果当前股价⽐买⼊时下跌了 10% ,则清仓⽌损
if amount > 0 and p<= cost*(1-0.1):
order_target(stock,0)
# 2.如果当前股价<10元 且 当前不持仓 则买入
if p <= 10 and amount == 0:
# 一共100000 要分给 300支股票
# 将符合买入条件的股票 存储到tobuy列表里
tobuy.append(stock)
# 2. 将可⽤的钱 除以 需要购买股票的⻓度 为每⽀股票可花的钱
cash_per_stock = context.portfolio.available_cash / len(tobuy)
# 2. 循环出 可买股票的代码
for stock in tobuy:
# 将可买股票的钱 购买它
order_value(stock,cash_per_stock)
"""
黄金交叉:短期均线上穿⻓期均线,买⼊信号
死亡交叉:短期均线下穿⻓期均线,卖出信号
"""
from jqdata import *
# 1 初始化函数
def initialize(context):
# 初始化全局变量 : 股票代码
g.security = ["601318.XSHG"]
# 初始化全局变量:天数
g.d5 = 5
g.d60 = 60
# 初始化交易函数
run_daily(handle_daily,time="every_bar")
# 2 定义每个交易日执行函数
def handle_daily(context):
"""
实现:金叉买入,死叉卖出
"""
# 循环遍历股票
for stock in g.security:
# 构建5日及60日均线
df_5 = attribute_history(stock,g.d5)
df_60 = attribute_history(stock,g.d60)
# print(df_5)
# print(df_60)
# 求均值
MA_5 = df_5["close"].mean()
MA_60 = df_60["close"].mean()
# 判断金叉死叉
# ⾦叉:短上穿⻓,前⼀天: 5MA<60MA 并且5MA>=60MA
# 死叉:短下穿⻓,前⼀天: 5MA>60MA 并且5MA<=60MA
# ⾦叉死叉肯定是交替出现的 并且 ⾦买 死卖
# ⾦叉: MA5>MA60 并且 不持仓
# 死叉: MA5
# 死叉
if MA_5 < MA_60 and stock in context.portfolio.positions:
# 清仓卖出
order_target(stock,0)
# 金叉
if MA_5 > MA_60 and stock not in context.portfolio.positions:
# 只买一支股票,不用担心配资。有多少可使用资金则买多少
order(stock,context.portfolio.available_cash)
# 显示线性图
record(ma_5=MA_5,ma_60=MA_60)
小市值策略:选取股票池中市值最小的N只股票持仓
# 导入函数库
from jqdata import *
# 1.初始化函数
def initialize(context):
# 初始化全局变量:A股指数 数据池
g.security = get_index_stocks("000002.XSHG")
# 查询的市值对象 为后续获取市值数据做准备
# select market_cap from valuation where code in g.security
g.q = query(valuation).filter(valuation.code.in_(g.security))
g.N = 20
# handle_month每月执行一次 注意:1不是第一天 而是第一个交易日
run_monthly(handle_month,1)
# 2.每月第一个交易日调用一次
def handle_month(context):
# 2.1 获取A股指数 市值最小的20支股票
# 通过get_fundamentals 获取对应股票代码(code)、总市值(market_cap)
df = get_fundamentals(g.q)[["code","market_cap"]]
# 对值进行升序排序 --> 将市值较小的 前g.N行切出来
df = df.sort_values("market_cap").iloc[:g.N,:]
print(df)
# 2.2 调仓 持有的股票在df(前20)里面就保留 没有持有的则买进来
# 取出股票代码
to_hold = df["code"].values
# 遍历现有所持有的股票
for stock in context.portfolio.positions:
# 如果持有股票没有在to_hold列表里 就卖掉
if stock not in to_hold:
order_target(stock,0)
# 需要买入的股票:在to_hold里但不在我持有的股票列表里
tobuy = [stock for stock in to_hold if stock not in context.portfolio.positions]
# 计算每支股票可以使用的钱 但注意:tobuy有可能为0 所以需要做判断
if len(tobuy) > 0:
cash_per_stock = context.portfolio.available_cash // len(tobuy)
for stock in tobuy:
order_value(stock, cash_per_stock)
综合多个因子:市值,市盈率,ROE(净资产收益率)等等
构建评分模型步骤:
## 多因子选股策略
"""
roe - 市值,代表盈利潜质,值越大越好
"""
from jqdata import *
# 1 初始化函数
def initialize(context):
# 初始化股票池
g.security = get_index_stocks("000002.XSHG")
# 市值,roe --> 查找在哪个表
# 市值 market_cap 所属表 valuation
# roe 所属表:indicator
g.q = query(valuation,indicator).filter(valuation.code.in_(g.security))
# 执行函数
run_monthly(handle_month,1)
# 2 循环执行函数
def handle_month(context):
# 获取对应 code market_cap roe
df = get_fundamentals(g.q)[["code","market_cap","roe"]]
# print(df)
# 市值 与 roe 相差甚远 归一化(0-1)
# (x-min)/(max-min)
df["market_cap"] = (df["market_cap"] - df["market_cap"].min())/(df["market_cap"].max() - df["market_cap"].min())
df["roe"] = (df["roe"] - df["roe"].min())/(df["roe"].max() - df["roe"].min())
# print(df)
# roe - market_cap
df["score"] = df["roe"] - df["market_cap"]
# 选择前20个综合指标
df = df.sort_values("score",ascending=False).iloc[:20,:]
# 股票代码
to_hold = df["code"].values
# 目前账户所持有的股票
for stock in context.portfolio.positions:
# 不再to_hold 股票 给 卖掉
if stock not in to_hold:
order_target(stock,0)
# 买入 在to_hold里面 但是不在我持有的股票列表里面
tobuy = [stock for stock in to_hold if stock not in context.portfolio.positions]
if len(tobuy) > 0:
cash_per_stock = context.portfolio.available_cash/len(tobuy)
for stock in tobuy:
order_value(stock,cash_per_stock)
简而言之:价格围绕价值波动,当超越价值过高,就回归;当被低估过分,也会回归。
价格的波动一般会以它的均线为中心。也就是说,当标的价格由于波动而偏离移动均线时,它将调整并重归于均线
定义偏离程度:(MA-P)/MA
MA是日平均线,P是股价
"""
均值回归策略执行
• 计算股票池中所有股票的N日均线
• 计算股票池中所有股票与均线的偏离度
• 选取偏离度最高的M支股票并调仓(是否风险最大)
"""
from jqdata import *
def initialize(context):
g.security = get_index_stocks("000300.XSHG")
g.ma_days = 30
run_monthly(handle_month,1)
def handle_month(context):
# 索引:股票代码 值:偏离率
sr = pd.Series(index=g.security)
for stock in sr.index:
# MA30
ma = attribute_history(stock,g.ma_days)["close"].mean()
# 获取当天价格
p = get_current_data()[stock].day_open
# 偏离率
ratio = (ma-p)/ma
sr[stock] = ratio
# 从sr里面选择 10个 最大的选出来
to_hold = sr.nlargest(10).index.values
# print(to_hold)
# 目前账户所持有的股票
for stock in context.portfolio.positions:
# 不再to_hold 股票 给 卖掉
if stock not in to_hold:
order_target(stock,0)
# 买入 在to_hold里面 但是不在我持有的股票列表里面
tobuy = [stock for stock in to_hold if stock not in context.portfolio.positions]
if len(tobuy) > 0:
cash_per_stock = context.portfolio.available_cash/len(tobuy)
for stock in tobuy:
order_value(stock,cash_per_stock)
布林带/布林线/保利加(Bollinger Band)通道策略:由三条轨道线组成,其中上下两条线分别可以看成是价格的压力线和支撑线,在两条线之间是一条价格平均线。
• 压力线 = M日均线 + NSTD
• 支撑线 = M日均线 - NSTD
• STD为标准差
• N为参数,意味着布林带宽度
其实这里就是假设价格在这个区间波动,达到这个区间高点,就要下落,及时卖出,降到这个区间的低点,就要回升,及时买入
"""
布林带:
价格是在一个区间波动
支撑线:高点
压力线:低点
"""
from jqdata import *
def initialize(context):
g.security = "600036.XSHG"
# 择时
run_daily(period,time="every_bar")
def period(context):
# 压力线 支撑线 (a.mean()-2*a.std(),a.mean()+2*a.std())
# 求10日均线
sr = attribute_history(g.security,10)["close"]
# 求均值
ma_10 = sr.mean()
# 求标准差
std_10 = sr.std()
# 压力线
up_line = ma_10 + 2 * std_10
# 支撑线
down_line = ma_10 - 2 * std_10
# 当当前价格 突破了 上线 则卖出 跌破了下线 买入
p = get_current_data()[g.security].day_open
# 当前可用资金
cash = context.portfolio.available_cash
if p < down_line and g.security not in context.portfolio.positions:
order_value(g.security,cash)
elif p > up_line and g.security in context.portfolio.positions:
order_target(g.security,0)
彼得林奇:任何一家公司股票如果定价合理的话,市盈率就会与收益增长率相等。这就是PEG估值法。
市盈率:当前股价§相对每股收益(EPS)的比值
收益增长率
PEG策略条件为:市盈率会与收益增长率相等。也就是PE = G,则得出公式:
因此,如果大于1,代表市盈率大于收益增长率,公司被高估,所以不该买入,当小于1,被低估,买入
点评:PEG是一个综合指标,既考察价值,又兼顾成长性。PEG估值法适合应用于成长型的公司
"""
PEG策略执行思路
大于1,代表市盈率大于收益增长率,公司被高估,所以不该买入;
当小于1,被低估,买入
PEG策略(选股):
• 计算股票池中所有股票的PEG指标
• 选择PEG最小的N只股票调仓(小的买入,大的卖出)
注意:过滤掉市盈率或收益增长率为负的数据。
"""
from jqdata import *
def initialize(context):
g.security = get_index_stocks("000300.XSHG")
# 市盈率 净利润同比增长率 财务数据 query对象
# pe_ratio 所属表:valuation
# inc_net_profit_year_on_year 所属表:indicator
g.q = query(valuation.code,valuation.pe_ratio,indicator.inc_net_profit_year_on_year).filter(valuation.code.in_(g.security))
run_monthly(handle_month,1)
def handle_month(context):
# 获取财务数据
df = get_fundamentals(g.q)
# print(df)
# 选出 PE 并且 G 都大于 0 的数据
df = df[(df["pe_ratio"]>0) & (df["inc_net_profit_year_on_year"]>0)]
# 计算PEG
df["peg"] = df["pe_ratio"]/df["inc_net_profit_year_on_year"]*100
# 排序 选出最小的
df = df.sort_values("peg")
# 取前20支股票的code 放到 tohold 中
to_hold = df["code"][:20].values
# print(to_hold)
# 目前账户所持有的股票
for stock in context.portfolio.positions:
# 不再to_hold 股票 给 卖掉
if stock not in to_hold:
order_target(stock,0)
# 买入 在to_hold里面 但是不在我持有的股票列表里面
tobuy = [stock for stock in to_hold if stock not in context.portfolio.positions]
if len(tobuy) > 0:
cash_per_stock = context.portfolio.available_cash/len(tobuy)
for stock in tobuy:
order_value(stock,cash_per_stock)
最后总结一句:以上所有策略经检验,都较为失败,跑不赢大盘 ==