這個策略來自聚寬量化課堂。【量化課堂】因子研究系列之一 – 估值和資本結構因子
# 克隆自聚寬文章:https://www.joinquant.com/post/3709
# 標題:【量化課堂】因子研究系列之一 -- 估值和資本結構因子
# 作者:JoinQuant量化課堂
# 多因子選股模型
# 先導入所需要的程序包
import datetime
import numpy as np
import pandas as pd
import time
from jqdata import *
from pandas import Series, DataFrame
'''
================================================================================
總體回測前
================================================================================
'''
#總體回測前要做的事情
def initialize(context):
set_params() #1 設置策參數
set_variables() #2 設置中間變量
set_backtest() #3 設置回測條件
#1 設置策略參數
def set_params():
g.factor = 'ALR' # 當前回測的單因子
g.shift = 21 # 設置一個觀測天數(天數)
g.precent = 0.01 # 持倉佔可選股票池比例
g.index='000001.XSHG' # 定義股票池,上交所股票
# 定義因子以及排序方式,默認False方式爲降序排列,原值越大sort_rank排序越小
g.factors = { 'ALR': False}
# 設定選取sort_rank: True 爲最大,False 爲最小
g.sort_rank = True
# 'BP': False, 'EP': False, 'PEG': False, 'DP': False,
# 'CFP': False, 'PS': False, 'ALR': False,
# 'FACR': False, 'CMC': False
#2 設置中間變量
def set_variables():
g.feasible_stocks = [] # 當前可交易股票池
g.if_trade = False # 當天是否交易
g.num_stocks = 0 # 設置持倉股票數目
#3 設置回測條件
def set_backtest():
set_benchmark('000001.XSHG') # 設置爲基準
set_option('use_real_price', True) # 用真實價格交易
log.set_level('order', 'error') # 設置報錯等級
'''
================================================================================
每天開盤前
================================================================================
'''
#每天開盤前要做的事情
def before_trading_start(context):
# 獲得當前日期
day = context.current_dt.day
yesterday = context.previous_date
rebalance_day = shift_trading_day(yesterday, 1)
if yesterday.month != rebalance_day.month:
if yesterday.day > rebalance_day.day:
g.if_trade = True
#5 設置可行股票池:獲得當前開盤的股票池並剔除當前或者計算樣本期間停牌的股票
g.feasible_stocks = set_feasible_stocks(get_index_stocks(g.index), g.shift,context)
#6 設置手續費與手續費
set_slip_fee(context)
# 購買股票爲可行股票池對應比例股票
g.num_stocks = int(len(g.feasible_stocks)*g.precent)
#4
# 某一日的前shift個交易日日期
# 輸入:date爲datetime.date對象(是一個date,而不是datetime);shift爲int類型
# 輸出:datetime.date對象(是一個date,而不是datetime)
def shift_trading_day(date,shift):
# 獲取所有的交易日,返回一個包含所有交易日的 list,元素值爲 datetime.date 類型.
tradingday = get_all_trade_days()
# 得到date之後shift天那一天在列表中的行標號 返回一個數
shiftday_index = list(tradingday).index(date)+shift
# 根據行號返回該日日期 爲datetime.date類型
return tradingday[shiftday_index]
#5
# 設置可行股票池
# 過濾掉當日停牌的股票,且篩選出前days天未停牌股票
# 輸入:stock_list爲list類型,樣本天數days爲int類型,context(見API)
# 輸出:list=g.feasible_stocks
def set_feasible_stocks(stock_list,days,context):
# 得到是否停牌信息的dataframe,停牌的1,未停牌得0
suspened_info_df = get_price(list(stock_list),
start_date=context.current_dt,
end_date=context.current_dt,
frequency='daily',
fields='paused'
)['paused'].T
# 過濾停牌股票 返回dataframe
unsuspened_index = suspened_info_df.iloc[:,0]<1
# 得到當日未停牌股票的代碼list:
unsuspened_stocks = suspened_info_df[unsuspened_index].index
# 進一步,篩選出前days天未曾停牌的股票list:
feasible_stocks = []
current_data = get_current_data()
for stock in unsuspened_stocks:
if sum(attribute_history(stock,
days,
unit = '1d',
fields = ('paused'),
skip_paused = False
)
)[0] == 0:
feasible_stocks.append(stock)
return feasible_stocks
#6 根據不同的時間段設置滑點與手續費
def set_slip_fee(context):
# 將滑點設置爲0
set_slippage(FixedSlippage(0))
# 根據不同的時間段設置手續費
dt=context.current_dt
if dt>datetime.datetime(2013,1, 1):
set_commission(PerTrade(buy_cost=0.0003,
sell_cost=0.0013,
min_cost=5))
elif dt>datetime.datetime(2011,1, 1):
set_commission(PerTrade(buy_cost=0.001,
sell_cost=0.002,
min_cost=5))
elif dt>datetime.datetime(2009,1, 1):
set_commission(PerTrade(buy_cost=0.002,
sell_cost=0.003,
min_cost=5))
else:
set_commission(PerTrade(buy_cost=0.003,
sell_cost=0.004,
min_cost=5))
'''
================================================================================
每天交易時
================================================================================
'''
def handle_data(context,data):
# 如果爲交易日
if g.if_trade == True:
#8 獲得買入賣出信號,輸入context,輸出股票列表list
# 字典中對應默認值爲false holding_list篩選爲true,則選出因子得分最大的
holding_list = get_stocks(g.feasible_stocks,
context,
g.factors,
asc = g.sort_rank,
factor_name = g.factor)
#9 重新調整倉位,輸入context,使用信號結果holding_list
rebalance(context, holding_list)
g.if_trade = False
#7 獲得因子信息
# stocks_list調用g.feasible_stocks factors調用字典g.factors
# 輸出所有對應數據和對應排名,DataFrame
def get_factors(stocks_list, context, factors):
# 從可行股票池中生成股票代碼列表
df_all_raw = pd.DataFrame(stocks_list)
# 修改index爲股票代碼
df_all_raw['code'] = df_all_raw[0]
df_all_raw.index = df_all_raw['code']
# 格式調整,沒有一步到位中間有些東西還在摸索,簡潔和效率的一個權衡
del df_all_raw[0]
stocks_list300 = list(df_all_raw.index)
# 每一個指標量都合併到一個dataframe裏
for key,value in g.factors.items():
# 構建一個新的字符串,名字叫做 'get_df_'+ 'key'
tmp='get_df' + '_' + key
# 聲明字符串是個方程
aa = globals()[tmp](stocks_list, context, value)
# 合併處理
df_all_raw = pd.concat([df_all_raw,aa], axis=1)
# 刪除code列
del df_all_raw['code']
# 對於新生成的股票代碼取list
stocks_list_more = list(df_all_raw.index)
# 可能在計算過程中並如的股票剔除
for stock in stocks_list_more[:]:
if stock not in stocks_list300:
df_all_raw.drop(stock)
return df_all_raw
# 8獲得調倉信號
# 原始數據重提取因子打分排名
def get_stocks(stocks_list, context, factors, asc, factor_name):
# 7獲取原始數據
df_all_raw1 = get_factors(stocks_list, context, factors)
# 根據factor生成列名
score = factor_name + '_' + 'sorted_rank'
try:
stocks = list(df_all_raw1.sort(score, ascending = asc).index)
except AttributeError:
stocks = list(df_all_raw1.sort_values(score, ascending = asc).index)
return stocks
# 9交易調倉
# 依本策略的買入信號,得到應該買的股票列表
# 借用買入信號結果,不需額外輸入
# 輸入:context(見API)
def rebalance(context, holding_list):
# 每隻股票購買金額
every_stock = context.portfolio.portfolio_value/g.num_stocks
# 空倉只有買入操作
if len(list(context.portfolio.positions.keys()))==0:
# 原設定重scort始於回報率相關打分計算,回報率是升序排列
for stock_to_buy in list(holding_list)[0:g.num_stocks]:
order_target_value(stock_to_buy, every_stock)
else :
# 不是空倉先賣出持有但是不在購買名單中的股票
for stock_to_sell in list(context.portfolio.positions.keys()):
if stock_to_sell not in list(holding_list)[0:g.num_stocks]:
order_target_value(stock_to_sell, 0)
# 因order函數調整爲順序調整,爲防止先行調倉股票由於後行調倉股票佔金額過大不能一次調整到位,這裏運行兩次以解決這個問題
for stock_to_buy in list(holding_list)[0:g.num_stocks]:
order_target_value(stock_to_buy, every_stock)
for stock_to_buy in list(holding_list)[0:g.num_stocks]:
order_target_value(stock_to_buy, every_stock)
# 因子數據處理函數單獨編號,與系列文章中對應
# 1BP
# 得到一個dataframe:包含股票代碼、賬面市值比BP和對應排名BP_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
# 獲得市淨率pb_ratio
def get_df_BP(stock_list, context, asc):
df_BP = get_fundamentals(query(valuation.code, valuation.pb_ratio
).filter(valuation.code.in_(stock_list)))
# 獲得pb倒數
df_BP['BP'] = df_BP['pb_ratio'].apply(lambda x: 1/x)
# 刪除nan,以備數據中某項沒有產生nan
df_BP = df_BP[pd.notnull(df_BP['BP'])]
# 生成排名序數
df_BP['BP_sorted_rank'] = df_BP['BP'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_BP.index = df_BP.code
# 刪除無用數據
del df_BP['code']
return df_BP
# 2EP
# 得到一個dataframe:包含股票代碼、盈利收益率EP和EP_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
# 獲得動態市盈率pe_ratio
def get_df_EP(stock_list, context, asc):
df_EP = get_fundamentals(query(valuation.code, valuation.pe_ratio
).filter(valuation.code.in_(stock_list)))
# 獲得pe倒數
df_EP['EP'] = df_EP['pe_ratio'].apply(lambda x: 1/x)
# 刪除nan,以備數據中某項沒有產生nan
df_EP = df_EP[pd.notnull(df_EP['EP'])]
# 複製一個dataframe,按對應項目排序
df_EP['EP_sorted_rank'] = df_EP['EP'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_EP.index = df_EP.code
# 刪除無用數據
del df_EP['code']
return df_EP
# 3PEG
# 輸入:context(見API);stock_list爲list類型,表示股票池
# 輸出:df_PEG爲dataframe: index爲股票代碼,data爲相應的PEG值
def get_df_PEG(stock_list, context, asc):
# 查詢股票池裏股票的市盈率,收益增長率
q_PE_G = query(valuation.code, valuation.pe_ratio, indicator.inc_net_profit_year_on_year
).filter(valuation.code.in_(stock_list))
# 得到一個dataframe:包含股票代碼、市盈率PE、收益增長率G
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
df_PE_G = get_fundamentals(q_PE_G)
# 篩選出成長股:刪除市盈率或收益增長率爲負值的股票
df_Growth_PE_G = df_PE_G[(df_PE_G.pe_ratio >0)&(df_PE_G.inc_net_profit_year_on_year >0)]
# 去除PE或G值爲非數字的股票所在行
df_Growth_PE_G.dropna()
# 得到一個Series:存放股票的市盈率TTM,即PE值
Series_PE = df_Growth_PE_G.ix[:,'pe_ratio']
# 得到一個Series:存放股票的收益增長率,即G值
Series_G = df_Growth_PE_G.ix[:,'inc_net_profit_year_on_year']
# 得到一個Series:存放股票的PEG值
Series_PEG = Series_PE/Series_G
# 將股票與其PEG值對應
Series_PEG.index = df_Growth_PE_G.ix[:,0]
# 生成空dataframe
df_PEG = pd.DataFrame(Series_PEG)
# 將Series類型轉換成dataframe類型
df_PEG['PEG'] = pd.DataFrame(Series_PEG)
# 得到一個dataframe:包含股票代碼、盈利收益率PEG和PEG_sorted_rank
# 賦予順序排列PEG數據序數編號
df_PEG['PEG_sorted_rank'] = df_PEG['PEG'].rank(ascending = asc, method = 'dense')
# 刪除不需要列
df_PEG = df_PEG.drop(0, 1)
return df_PEG
# 4DP
# 得到一個dataframe:包含股票代碼、股息率(DP)和DP_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_DP(stock_list, context, asc):
# 獲得dividend_payable和market_cap 應付股利(元)和總市值(億元)
df_DP = get_fundamentals(query(balance.code, balance.dividend_payable, valuation.market_cap
).filter(balance.code.in_(stock_list)))
# 按公式計算
df_DP['DP'] = df_DP['dividend_payable']/(df_DP['market_cap']*100000000)
# 刪除nan
df_DP = df_DP.dropna()
# 生成排名序數
df_DP['DP_sorted_rank'] = df_DP['DP'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_DP.index = df_DP.code
# 刪除無用數據
del df_DP['code']
del df_DP['dividend_payable']
del df_DP['market_cap']
# 改個名字
df_DP.columns = ['DP', 'DP_sorted_rank']
return df_DP
# 5CFP
# 得到一個dataframe:包含股票代碼、現金收益率CFP和CFP_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_CFP(stock_list, context, asc):
# 獲得市現率pcf_ratio cashflow/price
df_CFP = get_fundamentals(query(valuation.code, valuation.pcf_ratio
).filter(valuation.code.in_(stock_list)))
# 獲得pcf倒數
df_CFP['CFP'] = df_CFP['pcf_ratio'].apply(lambda x: 1/x)
# 刪除nan,以備數據中某項沒有產生nan
df_CFP = df_CFP[pd.notnull(df_CFP['CFP'])]
# 生成序列數字排名
df_CFP['CFP_sorted_rank'] = df_CFP['CFP'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_CFP.index = df_CFP.code
# 刪除無用數據
del df_CFP['code']
return df_CFP
# 6PS
# 得到一個dataframe:包含股票代碼、P/SALES(PS市銷率TTM)和PS_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_PS(stock_list, context, asc):
# 獲得市銷率TTMps_ratio
df_PS = get_fundamentals(query(valuation.code, valuation.ps_ratio
).filter(valuation.code.in_(stock_list)))
# 刪除nan,以備數據中某項沒有產生nan
df_PS = df_PS[pd.notnull(df_PS['ps_ratio'])]
# 生成排名序數
df_PS['PS_sorted_rank'] = df_PS['ps_ratio'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_PS.index = df_PS.code
# 刪除無用數據
del df_PS['code']
# 改個名字
df_PS.columns = ['PS', 'PS_sorted_rank']
return df_PS
# 7ALR
# 得到一個dataframe:包含股票代碼、資產負債率(asset-liability ratio, ALR)
# 和ALR_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_ALR(stock_list, context, asc):
# 獲得total_liability和total_assets 負債合計(元)和資產總計(元)
df_ALR = get_fundamentals(query(balance.code, balance.total_liability, balance.total_assets
).filter(balance.code.in_(stock_list)))
# 複製一個dataframe,按對應項目排序
df_ALR['ALR'] = df_ALR['total_liability']/df_ALR['total_assets']
# 刪除nan
df_ALR = df_ALR.dropna()
# 生成排名序數
df_ALR['ALR_sorted_rank'] = df_ALR['ALR'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_ALR.index = df_ALR.code
# 刪除無用數據
del df_ALR['code']
del df_ALR['total_liability']
del df_ALR['total_assets']
return df_ALR
# 8FACR
# 得到一個dataframe:包含股票代碼、固定資產比例(fixed assets to capital ratio, FACR )
# 和FACR_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_FACR(stock_list, context, asc):
# 獲得fixed_assets和total_assets 固定資產(元)和資產總計(元)
df_FACR = get_fundamentals(query(balance.code, balance.fixed_assets, balance.total_assets
).filter(balance.code.in_(stock_list)))
# 根據公式計算
df_FACR['FACR'] = df_FACR['fixed_assets']/df_FACR['total_assets']
# 刪除nan
df_FACR = df_FACR.dropna()
# 生成排名序數
df_FACR['FACR_sorted_rank'] = df_FACR['FACR'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_FACR.index = df_FACR.code
# 刪除無用數據
del df_FACR['code']
del df_FACR['fixed_assets']
del df_FACR['total_assets']
# 改個名字
df_FACR.columns = ['FACR', 'FACR_sorted_rank']
return df_FACR
# 9CMC
# 得到一個dataframe:包含股票代碼、流通市值CMC和CMC_sorted_rank
# 默認date = context.current_dt的前一天,使用默認值,避免未來函數,不建議修改
def get_df_CMC(stock_list, context, asc):
# 獲得流通市值 circulating_market_cap 流通市值(億)
df_CMC = get_fundamentals(query(valuation.code, valuation.circulating_market_cap
).filter(valuation.code.in_(stock_list)))
# 刪除nan
df_CMC = df_CMC.dropna()
# 生成排名序數
df_CMC['CMC_sorted_rank'] = df_CMC['circulating_market_cap'].rank(ascending = asc, method = 'dense')
# 使用股票代碼作爲index
df_CMC.index = df_CMC.code
# 刪除無用數據
del df_CMC['code']
# 改個名字
df_CMC.columns = ['CMC', 'CMC_sorted_rank']
return df_CMC
'''
================================================================================
每天收盤後
================================================================================
'''
# 每日收盤後要做的事情(本策略中不需要)
def after_trading_end(context):
return