量化白馬股輪動策略
股票資料的獲取見均線策略第一篇,回測的資料分析見均線策略第三篇。
不同的策略在於Strategy類中的買入賣出的判斷。
首先什麼是白馬股,白馬股,是指長期績優、回報率高並具有較高投資價值的股票。因其有關的資訊已經公開,業績較為明朗,同時又兼有業績優良、高成長、低風險的特點,因而具備較高的投資價值,往往為投資者所看好。以上來自百度百科。
關注過公眾號招財大牛貓的朋友一定對白馬股比較熟悉,由於ipo的提速,監管層對概念股的打壓,藍籌績優股從2017年至今都表現較好,白馬股更是漲幅感人。該策略是從大牛貓推薦的50只白馬股作為選股池,每15個交易日調倉一次,調整為這期間漲幅最大的5只,其實也是一種趨勢策略,認為趨勢不會輕易反轉,強者恆強的思路。
找不到股社群的原貼了,50只股票的list如下:
code_list = ['600519', '000858', '002304', '002415', '002236', '600597', '603288', \
'000895', '601318', '601336', '000651', '000333',\
'600036', '600104', '600816', '600674', '600690', \
'002081', '600886', '000625', '600887','000423', '600276', \
'000963', '600535', '600271', '002294', '300072', '000538', '600066', '000002',\
'002271', '600406', '600900', '002027', '000063', '002594', '601238', '600518', \
'600703', '600309', '002310', '002466', '002142', '601111', '601211', '600487', \
'002008', '600383', '600660']
不用回測也知道這個策略效果很好,因為市場已經驗證過了,本篇只是借這個思路,coding下輪動策略的實現方式。
strategy類下的成員變數與其他成員函式見均線策略第二篇,本篇就不贅述了,重點記錄下輪動的實現方式。
def change_position(self, date): #按date之前15天漲幅排序 sorted_yield_df = self.get_sorted_yield(date) #code: open_price,去除停盤的股票,將可買的股票按順序加入到available_buy_dict中,此處全部加入的原因 #而不是隻加入要買的5只,是為了避免購買每隻股票的額度可能都不夠買1手,導致最終的持倉不夠5只股票。 available_buy_dict = OrderedDict() for code in sorted_yield_df['code']: df = self.data_repository.get_onecode_df(code) if df[df['date'] == date].empty: continue else: buy_open_price = float(df[df['date'] == date]['open']) available_buy_dict[code] = buy_open_price #求得持倉股票中是否可賣 available_sell_dict = {} for code in self.position_dict.keys(): df = self.data_repository.get_onecode_df(code) if df[df['date'] == date].empty: available_sell_dict[code] = 0 else: sell_open_price = float(df[df['date'] == date]['open']) available_sell_dict[code] = sell_open_price #sell operation 賣出持倉中不在top5中的 for code in list(self.position_dict.keys()): amount = self.position_dict[code] sell_open_price = available_sell_dict[code] #賣出既不在待買清單的,又可以賣掉的股票 if code not in available_buy_dict and amount > 0 and sell_open_price > 0: commission = self.cal_cost_function(sell_open_price, amount) #更改現金 self.cash += sell_open_price * amount self.cash -= commission #更改持倉 del self.position_dict[code] #加入trade記錄, -1代表賣出操作 self.trade.add_trade(code, sell_open_price, amount, date, -1, commission) #從待買清單中按順序購買滿剩餘倉位,最多持有5只股票 for code in available_buy_dict.keys(): if len(self.position_dict) >= 5: break if code not in self.position_dict: buy_open_price = available_buy_dict[code] amount = self.get_buy_amount(code, buy_open_price) if amount > 0: commission = self.cal_cost_function(buy_open_price, amount) # 更改現金 self.cash -= buy_open_price * amount self.cash -= commission # 更改持倉 self.position_dict[code] = amount # 加入trade記錄,+1為買入標識 self.trade.add_trade(code, buy_open_price, amount, date, 1, commission)
def get_sorted_yield(self, date):
yield_list = []
for code in self.code_list:
df = self.data_repository.get_onecode_df(code)
df = df[df['date'] <= date].tail(self.step_day)['close']
if df.empty:
yield_in_step_day = 0
else:
yield_in_step_day = df.iloc[-1] / df.iloc[0]
yield_list.append(yield_in_step_day)
(1)首先回測日之前的15個交易日內,每隻股票的漲幅值,並從大到小排列;(2)從這些裡面逐一判斷回測日是否停盤(即df[df['date'] == date].empty是否為True),沒有停盤的加入到available_buy_dict中,key為股票code,value為買入的開盤價(因為回測策略是按開盤價買入),因為加入到dict中的順序是按收益率的高低加入的,普通dict是按key的unicode碼排序的,而不是加入的順序,故該處使用的OrderedDict;(3)逐一判斷持倉的股票回測日的賣出價格並加入到available_sell_dict中,如果某隻股票回測日停盤,則賣出價為0,同樣key為股票code,value為賣出的開盤價;(4)先遍歷持倉,並賣出不在available_buy_dict中並且可賣(即sell_dict中對應的值大於0)的股票,賣出後改變cash與持倉;(5)再買入剩餘的位置。