股市風起雲涌,我用Python分析週期之道

正文

股票市場週期是股票市場長期的價格模式,一般與商業週期有關。 它是技術分析的關鍵,其中投資方法基於週期或重複的價格模式。 若是咱們對股市週期有了更好的理解,咱們總能以相對低的價格買入並在每一個週期以相對較高的價格賣出,將始終得到正的回報。固然,股票市場沒有什麼策略能夠永遠賺錢,但咱們基於Python,能夠幫助咱們更深刻、快速地瞭解隱藏在股市中的週期。html

 

fbprophet簡介

Fbprophet是Facebook發佈的一個開源軟件,旨在爲大規模預測提供一些有用的指導。 默認狀況下,它會將時間序列劃分爲趨勢和季節性,可能包含年度,周度和每日。 可是,分析師能夠定義本身的季節性。 爲了更好地理解該庫,先導文件是很是有用的。python

 

該庫的一個特色是簡單性、靈活性。 因爲咱們想要計算的股票市場週期不限於每一年,每週或每日,咱們應該定義本身的週期,找出哪些更適合數據。 此外,因爲週末沒有交易,咱們不該該使用每週季節性。 咱們還能夠經過addseasonality函數定義'selfdefine_cycle'。 全部設置只需兩行代碼便可完成。app

 

m = Prophet(weekly_seasonality=False,yearly_seasonality=False)
m.add_seasonality('self_define_cycle',period=8,fourier_order=8,mode='additive')

 

以Costco爲例

咱們可使用Costco標的從2015/10/1到2018/10/1, 使用pandas_datareader,咱們能夠快速讀取股票價格。以下圖:函數

地址:https://pandas-datareader.readthedocs.io/en/latest/remote_data.htmloop

 

在下圖中,咱們能夠看到從2015年開始有一個強勁的價格增加趨勢。然而,在中途仍然存在不少上下週期波動,這些週期都是咱們的賺錢點。學習

ticker = "COST"
start_date = '2015-10-01'
end_date = '2018-10-01'
stock_data = data.DataReader(ticker, 'iex', start_date, end_date)
stock_data['close'].plot(figsize=(16,8),color='#002699',alpha=0.8)
plt.xlabel("Date",fontsize=12,fontweight='bold',color='gray')
plt.ylabel('Price',fontsize=12,fontweight='bold',color='gray')
plt.title("Stock price for Costco",fontsize=18)
plt.show()

 

對於預測模型,評估它們的一種方法是樣本均方偏差。 咱們可使用2015/10/1至2018/3/31進行訓練,並保留最後6個月的數據進行測試和計算樣本均方偏差。 在每一個週期內,咱們能夠經過以最低價格買入並以最高價格賣出的方式來優化咱們的回報。 爲了簡化過程,咱們使用自定義函數cycle_analysis。 輸出是一個列表,其中包含每一個週期的預計回報和樣本均方偏差。測試

  • data:帶有時間索引的Pandas數據
  • split_date:分割訓練和測試數據的日期cycle:每一個週期的間隔(天)
  • cycle:每一個週期的間隔(天)
  • mode:季節性的加法或乘法(可選)
  • forecast_plot:是否打印預測圖(可選,默認爲False)
  • print_ind:是否打印每一個週期的預計回報和是否採樣均方偏差(可選,默認爲False)

 

def cycle_analysis(data,split_date,cycle,mode='additive',forecast_plot = False,print_ind=False):
   training = data[:split_date].iloc[:-1,]
   testing = data[split_date:]
   predict_period = len(pd.date_range(split_date,max(data.index)))
   df = training.reset_index()
   df.columns = ['ds','y']
   m = Prophet(weekly_seasonality=False,yearly_seasonality=False,daily_seasonality=False)
   m.add_seasonality('self_define_cycle',period=cycle,fourier_order=8,mode=mode)
   m.fit(df)
   future = m.make_future_dataframe(periods=predict_period)
   forecast = m.predict(future)
   if forecast_plot:
       m.plot(forecast)
       plt.plot(testing.index,testing.values,'.',color='#ff3333',alpha=0.6)
       plt.xlabel('Date',fontsize=12,fontweight='bold',color='gray')
       plt.ylabel('Price',fontsize=12,fontweight='bold',color='gray')
       plt.show()
   ret = max(forecast.self_define_cycle)-min(forecast.self_define_cycle)
   model_tb = forecast['yhat']
   model_tb.index = forecast['ds'].map(lambda x:x.strftime("%Y-%m-%d"))
   out_tb = pd.concat([testing,model_tb],axis=1)
   out_tb = out_tb[~out_tb.iloc[:,0].isnull()]
   out_tb = out_tb[~out_tb.iloc[:,1].isnull()]
   mse = mean_squared_error(out_tb.iloc[:,0],out_tb.iloc[:,1])
   rep = [ret,mse]
   if print_ind:
       print "Projected return per cycle: {}".format(round(rep[0],2))
       print "MSE: {}".format(round(rep[1],4))
   return rep

 

在下面兩個圖中,咱們將兩種不一樣cycle(30和300)分別應用於Costco股票價格,並將2018/4/1做爲訓練和測試的分割日期。 正如咱們所看到的,若是咱們選擇一個較短的長度(例如30天),則一個週期內的回報是很小的,咱們須要常常進行交易,若是咱們選擇較長的長度,它會延長咱們的預測(例如300天)。優化

 

 

 

咱們能夠在cycle_analysis函數上應用一個循環來計算不一樣循環長度的預計回報和樣本均方偏差,而且咱們在下圖中顯示告終果。正如咱們所看到的,長度越長,每一個週期的預計回報和樣本均方偏差會增長。 考慮到交易成本,每一個週期內的預計回報應該大於10元。 在這種約束下,咱們能夠選擇最小樣本均方偏差的週期,而且它是252天。 每一個週期的預計回報爲17.12元,樣本均方偏差爲15.936。 二者都很不錯!spa

testing_box = range(10,301)
return_box = []
mse_box = []
for c in testing_box:
f = cycle_analysis(stock_data['close'],'2018-04-01',c)
return_box.append(f[0])
mse_box.append(f[1])

 

report = pd.DataFrame({'cycle':testing_box,'return':return_box,'mse':mse_box})
possible_choice = report[report['return'] >10]
possible_choice[possible_choice['mse']==min(possible_choice['mse'])]

 

c = possible_choice[possible_choice['mse']==min(possible_choice['mse'])]['cycle'].values[0]
ycle_analysis(stock_data['close'],'2018-04-01',c,forecast_plot=True,print_ind=True)

Projected return per cycle: 17.12 MSE: 15.9358 [17.120216439034987, 15.93576020351612]code

 

爲了進一步說明投資策略,咱們能夠看到2015/10/1和2018/10/1之間的買入和賣出日期。 Return_Dates函數能夠將全部買入和賣出日期做爲輸出返回,輸入:

  • forecast:fbprophet預測對象
  • stock_data:帶有時間索引的Pandas數據
  • cycle:週期長度
  • cycle_name:預測對象中循環列的名稱
  • time_name:預測對象中時間列的名稱
def Return_Dates(forecast,stock_data,cycle,cycle_name = 'self_define_cycle',time_name = 'ds'):
   # find out the highest and lowest dates in the first cycle 
   # We cannot simply search for all highest and lowest point since there is slightly difference for high and low values in different cycles
   high = forecast.iloc[:cycle,]
   high = high[high[cycle_name]==max(high[cycle_name])][time_name]
   high = datetime.strptime(str(high.values[0])[:10],"%Y-%m-%d")
   low = forecast.iloc[:cycle,]
   low = low[low[cycle_name]==min(low[cycle_name])][time_name]
   low = datetime.strptime(str(low.values[0])[:10],"%Y-%m-%d")
   end_dt = datetime.strptime(stock_data.index[-1],"%Y-%m-%d")
   find_list = stock_data.index.map(lambda x:datetime.strptime(x,"%Y-%m-%d"))
   # Finding selling and buying dates with loop
   sell_dt = []
   sell_dt.append(high)
   # Looking for new cycle until it goes beyond the last date in stock_data
   while high<end_dt:
       high = high+timedelta(days=cycle)
       dif = (find_list-high).days
       high = find_list[abs(dif)==min(abs(dif))][0] # In order to avoid the non-trading dates
       sell_dt.append(high)
   buy_dt = []
   buy_dt.append(low)
   # Looking for new cycle until it goes beyond the last date in stock_data
   while low<end_dt:
       low = low+timedelta(days=cycle)
       dif = (find_list-low).days
       low = find_list[abs(dif)==min(abs(dif))][0] # In order to avoid the non-trading dates
       buy_dt.append(low)
   if buy_dt[0] > sell_dt[0]:
       sell_dt = sell_dt[1:]
   buy_dt = buy_dt[:-1]
   sell_dt = sell_dt[:-1]
   return [buy_dt,sell_dt]

 

在2015/10/1和2018/10/1期間,咱們買賣Costco四次。3年內的回報率爲23.2%。 可能不是很吸引人,但至少它是比較樂觀的回報。
 

更多股票的應用,固然,這種方法能夠應用於儘量多的股票。 咱們列出了Costco,Apple,Microsoft,Home Depot和Nike的平均購買價格,平均銷售價格,週期長度,樣本均方偏差,購買數量,銷售數量和每一個週期內的預計回報。

Analysis_ticks = ['COST','AAPL','MSFT','HD','NKE']
start_date = '2015-10-01'
end_date = '2018-10-01'
opt_cycle = []
prot_return = []
MSE = []
buy_times = []
sell_times = []
avg_buy_price = []
avg_sell_price = []
# Loop over each stock
for ticker in Analysis_ticks:
   stock_data = data.DataReader(ticker, 'iex', start_date, end_date)
   testing_box = range(50,301)
   return_box = []
   mse_box = []
   for cc in testing_box:
       f = cycle_analysis(stock_data['close'],'2018-04-01',cc)
       return_box.append(f[0])
       mse_box.append(f[1])
   report = pd.DataFrame({'cycle':testing_box,'return':return_box,'mse':mse_box})
   possible_choice = report[report['return'] >10]
   # If we cannot find a cycle with return greater than 10, give 0
   if possible_choice.shape[0]>0:
       c = possible_choice[possible_choice['mse']==min(possible_choice['mse'])]['cycle'].values[0]
       rp = possible_choice[possible_choice['mse']==min(possible_choice['mse'])]['return'].values[0]
       mse = possible_choice[possible_choice['mse']==min(possible_choice['mse'])]['mse'].values[0]
       df = stock_data[:'2018-04-01'].iloc[:-1,]['close'].reset_index()
       df.columns = ['ds','y']
       predict_period = len(pd.date_range('2018-04-01','2018-10-01'))
       m = Prophet(weekly_seasonality=False,yearly_seasonality=False,daily_seasonality=False)
       m.add_seasonality('self_define_cycle',period=c,fourier_order=8,mode='additive')
       m.fit(df)
       future = m.make_future_dataframe(periods=predict_period)
       forecast = m.predict(future)
       dt_list = Return_Dates(forecast,stock_data,c)
       buy_price = stock_data.loc[map(lambda x: x.strftime("%Y-%m-%d"),dt_list[0])]['close']
       sell_price = stock_data.loc[map(lambda x: x.strftime("%Y-%m-%d"),dt_list[1])]['close']
       bt = buy_price.shape[0]
       st = sell_price.shape[0]
       bp = np.mean(buy_price)
       sp = np.mean(sell_price)
   else:
       c = 0
       rp = 0
       mse = 0
       bt = 0
       st = 0
       bp = 0
       sp = 0
   opt_cycle.append(c)
   prot_return.append(rp)
   MSE.append(mse)
   buy_times.append(bt)
   sell_times.append(st)
   avg_buy_price.append(bp)
   avg_sell_price.append(sp)
   print "{} Finished".format(ticker)

 

對於微軟和耐克,咱們找不到符合咱們要求每一個週期超過10元回報的週期。 對於Costco,Apple和Home Depot,咱們能夠找到大約250天的週期,並作出良好的預測和良好的回報。

stock_report = pd.DataFrame({'Stock':Analysis_ticks,'Cycle':opt_cycle,'Projected_Return_per_Cycle':prot_return,
                        'MSE':MSE,'Num_of_Buy':buy_times,'Num_of_Sell':sell_times,
                        'Average_Buy_Price':avg_buy_price,'Average_Sell_Price':avg_sell_price})
stock_report

 

 

總結

藉助Python和fbprophet包,咱們能夠更好地瞭解股市。 根據咱們發現的週期,咱們能夠在3年內得到大約23%的回報。 也許這種投資策略沒法知足全部人的需求,但你始終能夠根據本身的知識和經驗設定本身的方法。 強大的fbprophet軟件包可讓你對股票市場的分析更加深刻和輕鬆。

 

延伸閱讀:

經典的深度學習教程及Python代碼實現

相關文章
相關標籤/搜索