#多因子策略入門 # 2015-01-01 到 2016-03-08, ¥2000000, 每天 ''' ================================================================================ 總體回測前 ================================================================================ ''' #總體回測前要做的事情 def initialize(context): set_params() #1設置策參數 set_variables() #2設置中間變量 set_backtest() #3設置回測條件 #1 #設置策參數 def set_params(): g.tc=15 # 調倉頻率 g.yb=63 # 樣本長度 g.N=20 # 持倉數目 g.factors=["market_cap","roe"] # 用戶選出來的因子 # 因子等權重裏1表示因子值越小越好,-1表示因子值越大越好 g.weights=[[1],[-1]] #2 #設置中間變量 def set_variables(): g.t=0 #記錄回測運行的天數 g.if_trade=False #當天是否交易 #3 #設置回測條件 def set_backtest(): set_option('use_real_price', True)#用真實價格交易 log.set_level('order', 'error') ''' ================================================================================ 每天開盤前 ================================================================================ ''' #每天開盤前要做的事情 def before_trading_start(context): if g.t%g.tc==0: #每g.tc天,交易一次行 g.if_trade=True # 設置手續費與手續費 set_slip_fee(context) # 設置可行股票池:獲得當前開盤的滬深300股票池並剔除當前或者計算樣本期間停牌的股票 g.all_stocks = set_feasible_stocks(get_index_stocks('000300.XSHG'),g.yb,context) # 查詢所有財務因子 g.q = query(valuation,balance,cash_flow,income,indicator).filter(valuation.code.in_(g.all_stocks)) g.t+=1 #4 # 設置可行股票池 # 過濾掉當日停牌的股票,且篩選出前days天未停牌股票 # 輸入:stock_list爲list類型,樣本天數days爲int類型,context(見API) # 輸出:list 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 #5 # 根據不同的時間段設置滑點與手續費 def set_slip_fee(context): # 將滑點設置爲0 set_slippage(FixedSlippage(0)) # 根據不同的時間段設置手續費 dt=context.current_dt log.info(type(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: # 計算現在的總資產,以分配資金,這裏是等額權重分配 g.everyStock=context.portfolio.portfolio_value/g.N # 獲得今天日期的字符串 todayStr=str(context.current_dt)[0:10] # 獲得因子排序 a,b=getRankedFactors(g.factors,todayStr) # 計算每個股票的得分 points=np.dot(a,g.weights) # 複製股票代碼 stock_sort=b[:] # 對股票的得分進行排名 points,stock_sort=bubble(points,stock_sort) # 取前N名的股票 toBuy=stock_sort[0:g.N].values # 對於不需要持倉的股票,全倉賣出 order_stock_sell(context,data,toBuy) # 對於不需要持倉的股票,按分配到的份額買入 order_stock_buy(context,data,toBuy) g.if_trade=False #6 #獲得賣出信號,並執行賣出操作 #輸入:context, data,toBuy-list #輸出:none def order_stock_sell(context,data,toBuy): #如果現有持倉股票不在股票池,清空 list_position=context.portfolio.positions.keys() for stock in list_position: if stock not in toBuy: order_target(stock, 0) #7 #獲得買入信號,並執行買入操作 #輸入:context, data,toBuy-list #輸出:none def order_stock_buy(context,data,toBuy): # 對於不需要持倉的股票,按分配到的份額買入 for i in range(0,len(g.all_stocks)): if indexOf(g.all_stocks[i],toBuy)>-1: order_target_value(g.all_stocks[i], g.everyStock) #8 #查找一個元素在數組裏面的位置,如果不存在,則返回-1 #輸入:元素,對應數組 #輸出:-1 def indexOf(e,a): for i in range(0,len(a)): if e==a[i]: return i return -1 #9 #取因子數據 #輸入:f-全局通用的查詢,d-str #輸出:因子數據,股票的代碼-dataframe def getRankedFactors(f,d): # 獲得股票的基本面數據,這個API裏面有,g.q是一個全局通用的查詢 df = get_fundamentals(g.q,d) # 爲了防止Python裏面的淺複製現象,採用循環來定義二維數組 res = [([0] * len(f)) for i in range(len(df))] # 把數據填充到剛纔定義的數組裏面 for i in range(0,len(df)): for j in range(0,len(f)): res[i][j]=df[f[j]][i] # 用均值填充NaN值 fillNan(res) # 將數據變成排名 getRank(res) # 返回因子數據和股票的代碼(這個是因爲滬深300指數成分股一直在變,如果用未來的滬深300指數成分股在之前可能有一些股票還沒上市) return res,df['code'] #10 #把每列原始數據變成排序的數據 #輸入:r-list #輸出:r-list def getRank(r): # 定義一個臨時數組記住一開始的順序 indexes=list(range(0,len(r))) # 對每一列進行冒泡排序 for k in range(len(r[0])): for i in range(len(r)): for j in range(i): if r[j][k] < r[i][k]: # 交換所有的列以及用於記錄一開始的順序的數組 indexes[j], indexes[i] = indexes[i], indexes[j] for l in range(len(r[0])): r[j][l], r[i][l] = r[i][l], r[j][l] # 將排序好的因子順序變成排名 for i in range(len(r)): r[i][k]=i+1 # 再進行一次冒泡排序恢復一開始的股票順序 for i in range(len(r)): for j in range(i): if indexes[j] > indexes[i]: indexes[j], indexes[i] = indexes[i], indexes[j] for k in range(len(r[0])): r[j][k], r[i][k] = r[i][k], r[j][k] # 因爲Python是引用傳遞,所以其實這個可以不用返回值也行,當然如果你想用另外一個變量來存儲排序結果的話可以考慮返回值的方法 return r #11 #用均值填充Nan #輸入:m-list #輸出:m-list def fillNan(m): # 計算出因子數據有多少行(行是不同的股票) rows=len(m) # 計算出因子數據有多少列(列是不同的因子) columns=len(m[0]) # 這個循環是對每一列進行操作 for j in range(0,columns): # 定義一個臨時變量,用來存儲每列加總的值 sum=0.0 # 定義一個臨時變量,用來計算非NaN值的個數 count=0.0 # 計算非NaN值的總和和個數 for i in range(0,rows): if not(isnan(m[i][j])): sum+=m[i][j] count+=1 # 計算平均值,爲了防止全是NaN,如果當整列都是NaN的時候認爲平均值是0 avg=sum/max(count,1) for i in range(0,rows): # 這個for循環是用來把NaN值填充爲剛纔計算出來的平均值的 if isnan(m[i][j]): m[i][j]=avg return m #12 #定義一個冒泡排序的函數 #輸入:numbers是股票的綜合得分-list #輸出:indexes是股票列表-list def bubble(numbers,indexes): for i in range(len(numbers)): for j in range(i): if numbers[j][0] < numbers[i][0]: # 在進行交換的時候同時交換得分以記錄哪些股票得分比較高 numbers[j][0], numbers[i][0] = numbers[i][0], numbers[j][0] indexes[j], indexes[i] = indexes[i], indexes[j] return numbers,indexes ''' ================================================================================ 每天收盤後 ================================================================================ ''' # 每日收盤後要做的事情(本策略中不需要) def after_trading_end(context): return
來源聚寬文章:https://www.joinquant.com/post/1399
# 標題:【量化課堂】多因子策略入門
# 作者:JoinQuant量化課堂