一、因子選股策略
1、因子
因子:選擇股票的某種標准。因子是能夠預測股票收益的變量。
(1)基本面因子
基本面因子描述了一個公司的財務狀況,最常見的基本面因子是由利潤表,資產負債表以及現金流量表中的數據直接計算出的比率。通過財務報表可以構建出無數的財務比率及財務報表變量的組合,並以此來預測股票的收益率。
一般將基本面因子分為6小類:估值因子、償債能力因子、營運效率因子、盈利能力因子、財務風險因子以及流動性風險因子。
(2)技術面因子
大多數技術面因子是由過去的價格、成交量以及其他可獲得的金融信息所構建的,技術面因子一大優勢是能夠持續更新。新的基本面數據最多只能按季度獲取,相反,最新的技術指標每隔幾秒就可以獲得。
(3)經濟因子
最初的套利定價模型是基於經濟指標來構建的。比較流行的經濟因子包括:GDP增速、失業率以及通貨膨脹率等,它們幾乎會影響到市場的每一個角落。
(4)其他因子
其他因子的類型包括但不限於:分析師預測因子、事件驅動因子。
2、選股策略(策略模型)
對於某個因子,選取表現最好(因子最大或最小)的N支股票持倉。
每隔一段時間調倉一次。
3、小市場策略
選取股票池中市值最小的N只股票持倉。
二、聚寬實現因子選股策略——小市值策略
滬深300中,根據市值最小的20只股票選股:
# 初始化函數,設定基准等等 def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation).filter(valuation.code.in_(g.security)) g.N = 20 # 20只股票 run_monthly(handle, 1) # 第一個參數是對應的函數,第二個參數指第幾個交易日 def handle(context): df = get_fundamentals(g.q)[['code', 'market_cap']] # 花式索引選出股票代碼和市值 df = df.sort_values("market_cap").iloc[:g.N,:] # pandas排序函數,將數據集依照某個字段中的數據進行排序 # 期待持有的股票 to_hold = df['code'].values for stock in context.portfolio.positions: if stock not in to_hold: # 目標股數下單,賣出非標的的股票 order_target(stock, 0) # 期待持有且還未持倉的股票 to_buy = [stock for stock in to_hold if stock not in context.portfolio.positions] if len(to_buy) > 0: # 需要調倉 # 每只股票預計投入的資金 cash_per_stock = context.portfolio.available_cash / len(to_buy) for stock in to_buy: # 按價值下單,買入需買入的股票 order_value(stock, cash_per_stock)
執行效果:
這個策略在短線情況下表現一般,長線情況下效果不錯。
1、查詢財務數據
查詢財務數據,詳細數據字段描述見:財務數據文檔
get_fundamentals(query_object, date=None, statDate=None)
(1)參數介紹
query_object:一個sqlalchemy.orm.query.Query對象, 可以通過全局的 query 函數獲取 Query 對象;
date:查詢日期, 一個字符串(格式類似'2015-10-15')或者[datetime.date]/[datetime.datetime]對象, 可以是None, 使用默認日期. 這個默認日期在回測和研究模塊上有點差別:
- 回測模塊: 默認值會隨着回測日期變化而變化, 等於 context.current_dt 的前一天(實際生活中我們只能看到前一天的財報和市值數據, 所以要用前一天)
- 研究模塊: 使用平台財務數據的最新日期, 一般是昨天。如果傳入的date不是交易日,則使用這個日期之前的最近的一個交易日。
statDate:財報統計的季度或者年份, 一個字符串, 有兩種格式:
- 季度: 格式是: 年 + 'q' + 季度序號, 例如: '2015q1', '2013q4'.
- 年份: 格式就是年份的數字, 例如: '2015', '2016'.
(2)date和statDate參數只能傳入一個
傳入date時, 查詢指定日期date收盤后所能看到的最近(對市值表來說, 最近一天, 對其他表來說, 最近一個季度)的數據, 我們會查找上市公司在這個日期之前(包括此日期)發布的數據, 不會有未來函數.
傳入statDate時, 查詢 statDate 指定的季度或者年份的財務數據.
(3)執行示例
def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation).filter(valuation.code.in_(g.security)) g.N = 20 # 20只股票 run_monthly(handle, 1) # 第一個參數是對應的函數,第二個參數指第幾個交易日 def handle(context): df = get_fundamentals(g.q)[['code', 'market_cap']] # 花式索引選出股票代碼和市值 df = df.sort_values("market_cap").iloc[:g.N,:] # pandas排序函數,將數據集依照某個字段中的數據進行排序 print(df)
每月執行一次找出市值最低的20只股票,執行效果:
2、每30天執行一次
(1)基於handle_data實現
# 初始化函數,設定基准等等 def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation).filter(valuation.code.in_(g.security)) g.days = -1 # 加1后,可第一天就執行 def handle_data(context, data): g.days += 1 if g.days % 30 == 0: # 每30天執行一次 # code
(2)基於定時運行策略實現
三種定時運行策略:run_daily/run_weekly/run_monthly。
# 初始化函數,設定基准等等 def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation).filter(valuation.code.in_(g.security)) run_monthly(handle, 1) # 第一個參數是對應的函數,第二個參數指第幾個交易日 def handle(context): print("Hello")
2019-1-1到2019-6-30執行效果:
2019-01-01 00:00:00 - INFO - 初始函數開始運行且全局只運行一次 2019-01-02 09:30:00 - INFO - Hello 2019-02-01 09:30:00 - INFO - Hello 2019-03-01 09:30:00 - INFO - Hello 2019-04-01 09:30:00 - INFO - Hello 2019-05-06 09:30:00 - INFO - Hello 2019-06-03 09:30:00 - INFO - Hello
三、多因子選股策略
1、如何同時綜合多個因子來選股
多因子選股模型的建立過程主要分為候選因子的選取、選股因子有效性的檢驗、有效但冗余囚子的剔除、綜合評分模型的建立和模型的評價及持續改進5個步驟。
候選因子的選取:候選因子可能是一些基本面指標,如PB、PE、EPS增長率等,也可能是一些技術面指標,如動量、換手率、波動等;或者是其他指標,如預期收益增長、分析師一致預期變化、宏觀經濟變量等。候選因子的選擇主要依賴於經濟邏輯和市場經驗,但選擇更多和更有效的因了無疑是增強模型信息捕獲能力,提高收益的關鍵因素之一。
選股因子有效性檢驗:檢驗方法主要采用排序的方法檢驗候選因子的選股有效性。對於任意一個候選因子,在模型形成期的第一個月初開始計算市場中每只正常交易股票的該因子的大小,按從小到大的順序對樣本股票進行排序,並平均分為n個組合,一直持有到月末,在下月初再按同樣的方法重新構建n個組合並持有到月末,每月如此,一直重復到模型形成期末。組合構建完畢后,計算這n個組合的年化復合收益、相對於業績基准的超出收益、在不同市場狀況下的高收益組合跑贏基准和低收益組合跑輸基准的概率等。
綜合評分模型的建立:綜合評分模型迭取去除冗余后的有效因子,在模型運行期的每個月初對市場中正常交易的個股計算每個因子的最新得分,並按照一定的權重求得所有因子的平均分。如果有的因子在某些月份可能無法取值(例如,有的個股因缺少分析師預期數據無法計算預期相關因子),那么按剩下的因了分值求加權平均。最后,根據模型所得出的綜合平均分對股票進行排序,然后根據需要選擇排名靠前的股票。例如,選取得分最高的前20%股票,或者選取得分最高的50~100只股票等。
模型的評價及持續改進:一方面,由於量選股方法是建立在市場無效或弱有效的前提之下,隨着使用多因子選股模型的投資者數量的不斷增加,有的因子會逐漸失效,而另一些新的因素可能被驗證有效而加入到模型中;另一方面,一些因子可能在過去的市場環境下比較有效,而隨着市場風格的改變,這些因子可能短期內失效,而另外一些以前無效的因子會在當前市場環境下表現較好。另外,計算綜合評分的過程中,各因子得分的權重設計、交易成本考慮和風險控制等都存在進一步改進的空間。因此在綜合評分選股模型的使用過程中,會對選用的因子、模型本身做持續再評價和不斷改進以適應變化的市場環境。
2、評分模型
每個股票針對每個因子進行評分,將評分相加;
選出總評分最大的N只股票持倉;
如何計算股票在某個因子下的評分:歸一化(標准化)。
3、數據預處理——歸一化/標准化/正則化
對於多因子策略,不同因子的量綱和數量級不同,為實現不同指標的可加性,需要對原始指標數據進行標准化處理。
(1)數據標准化方法分類
直線型:極值法、標准差法
折線型:三折線法
曲線型:半正態性分布
(2)數據標准化處理
數據同趨化:主要解決不同性質數據問題,使所有指標對評測方案的作用力同趨化
無量綱化:主要解決數據的可比性
數據標准化原理是將數據按比例縮放,使所有數據落入一個小的特定區間。最常見的就是歸一化,將數據統一映射到[0,1]之間。
歸一化是標准化的特例,標准化是特征縮放的特例。
(3)數據標准化方法
1)最小-最大標准化(Min-max normalization)
min-max標准化又稱為離差標准化,是常見的歸一化處理。將原始數據轉化為一個0到1的數。
- 獲取因子值最大值max,最小值min;
- 對數據進行線性變化
缺點:若有新數據加入,可能導致min和max的變化。
演示示例:
import numpy as np a = np.random.uniform(-10, 20, 100) # 100個-10到20之間的隨機數 print(a) # [ 2.74793518 6.41071562 15.34009849 ... -1.33143778 -7.95168854] b = (a - a.min()) / (a.max() - a.min()) print(b) # [0.1371042 0.04541101 0.14368817 0.35814033 0.27530808 ... 0.76208966 0.41034195]
2)Z-score標准化
將原始數據轉化為 均值為0,標准差為1 的正態分布的隨機變量。
演示示例:
import numpy as np a = np.random.uniform(-10, 20, 100) # 100個-10到20之間的隨機數 print(a) # [ 2.74793518 6.41071562 15.34009849 ... -1.33143778 -7.95168854] c = (a - a.mean()) / a.std() print(c) # [ 0.73462873 -1.2513859 -1.73108227 -1.05090879 ... 0.80783486 1.66651732]
四、多因子選股策略實現——市值+ROE(凈資產收益率)
雙因子評分:市盈率越高越好,市值越小越好。
def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # indicator:財務指標數據 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation, indicator).filter(valuation.code.in_(g.security)) g.N = 20 # 20只股票 run_monthly(handle, 1) # 第一個參數是對應的函數,第二個參數指第幾個交易日 def handle(context): df = get_fundamentals(g.q)[['code', 'market_cap', 'roe']] # 花式索引選出股票代碼和市值、凈資產收益率 # 市值和ROE的數值大小差別很大,先完成歸一化 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()) # 創建新的score列 df['score'] = df['roe'] - df['market_cap'] df = df.sort_values('score').iloc[-g.N:,:] # 根據score排序,選最大的20個,因此獲取最后20個 # 期待持有的股票 to_hold = df['code'].values for stock in context.portfolio.positions: if stock not in to_hold: # 目標股數下單,賣出非標的的股票 order_target(stock, 0) # 期待持有且還未持倉的股票 to_buy = [stock for stock in to_hold if stock not in context.portfolio.positions] if len(to_buy) > 0: # 需要調倉 # 每只股票預計投入的資金 cash_per_stock = context.portfolio.available_cash / len(to_buy) for stock in to_buy: # 按價值下單,買入需買入的股票 order_value(stock, cash_per_stock)
執行效果:
1、花式索引選出股票代碼和市值、凈資產收益率
打印出股票代碼、市值、凈資產收益率信息:
def initialize(context): # 設定滬深300作為基准 set_benchmark('000300.XSHG') # 開啟動態復權模式(真實價格) set_option('use_real_price', True) # 輸出內容到日志 log.info() log.info('初始函數開始運行且全局只運行一次') # 股票類每筆交易時的手續費是:買入時佣金萬分之三,賣出時佣金萬分之三加千分之一印花稅, 每筆交易佣金最低扣5塊錢 set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock') # 獲取指數成份股 g.security = get_index_stocks('000300.XSHG') # valuation:財務數據表,code是對應的股票代碼 # indicator:財務指標數據 # 這里不能使用 in 操作, 要使用in_()函數,找到滬深300股份對應的財務數據 g.q = query(valuation, indicator).filter(valuation.code.in_(g.security)) g.N = 20 # 20只股票 run_monthly(handle, 1) # 第一個參數是對應的函數,第二個參數指第幾個交易日 def handle(context): df = get_fundamentals(g.q)[['code', 'market_cap', 'roe']] # 花式索引選出股票代碼和市值、凈資產收益率 print(df)
執行效果如下:
2019-01-01 00:00:00 - INFO - 初始函數開始運行且全局只運行一次 2019-01-02 09:30:00 - INFO - code market_cap roe 0 000001.XSHE 1610.5846 3.06 1 000002.XSHE 2629.5259 3.54 2 000063.XSHE 821.3444 2.52 3 000069.XSHE 520.9592 5.71 4 000100.XSHE 331.9664 3.04 5 000157.XSHE 277.9839 1.18 6 000166.XSHE 917.2129 1.82 7 000333.XSHE 2455.9929 6.12 8 000338.XSHE 615.7874 4.33 ...
2、數據標准化處理
可以看到市值和roe的數據量級都不同,如果要進行分析肯定要進行數據預處理,使兩者獲得一樣的權重。
def handle(context): df = get_fundamentals(g.q)[['code', 'market_cap', 'roe']] # 花式索引選出股票代碼和市值、凈資產收益率 # 市值和ROE的數值大小差別很大,先完成歸一化 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)
執行效果如下:
2019-01-01 00:00:00 - INFO - 初始函數開始運行且全局只運行一次 2019-01-02 09:30:00 - INFO - code market_cap roe 0 000001.XSHE 0.078971 0.513834 1 000002.XSHE 0.133397 0.537549 2 000063.XSHE 0.036815 0.487154 3 000069.XSHE 0.020771 0.644763 4 000100.XSHE 0.010676 0.512846 5 000157.XSHE 0.007792 0.420949 6 000166.XSHE 0.041936 0.452569 7 000333.XSHE 0.124128 0.665020