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

步入量化學習艾莉絲發表於2019-03-04

正文

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

fbprophet簡介

Fbprophet是Facebook釋出的一個開源軟體,旨在為大規模預測提供一些有用的指導。 預設情況下,它會將時間序列劃分為趨勢和季節性,可能包含年度,周度和每日。 但是,分析師可以定義自己的季節性。 為了更好地理解該庫,先導檔案是非常有用的。

該庫的一個特點是簡單性、靈活性。 由於我們想要計算的股票市場週期不限於每年,每週或每日,我們應該定義自己的週期,找出哪些更適合資料。 此外,由於週末沒有交易,我們不應該使用每週季節性。 我們還可以通過add_seasonality函式定義`self_define_cycle`。 所有設定只需兩行程式碼即可完成。

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,我們可以快速讀取股票價格。如下圖:

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

地址:pandas-datareader.readthedocs.io/en/latest/r…

在下圖中,我們可以看到從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()
複製程式碼
股市風起雲湧,我用Python分析週期之道

對於預測模型,評估它們的一種方法是樣本均方誤差。 我們可以使用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天)。

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

我們可以在cycle_analysis函式上應用一個迴圈來計算不同迴圈長度的預計回報和樣本均方誤差,並且我們在下圖中顯示了結果。正如我們所看到的,長度越長,每個週期的預計回報和樣本均方誤差會增加。 考慮到交易成本,每個週期內的預計回報應該大於10元。 在這種約束下,我們可以選擇最小樣本均方誤差的週期,並且它是252天。 每個週期的預計回報為17.12元,樣本均方誤差為15.936。 兩者都很不錯!

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])
複製程式碼
股市風起雲湧,我用Python分析週期之道
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`])]
複製程式碼
股市風起雲湧,我用Python分析週期之道
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)
複製程式碼
股市風起雲湧,我用Python分析週期之道

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

為了進一步說明投資策略,我們可以看到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%。 可能不是很吸引人,但至少它是比較樂觀的回報。

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

更多股票的應用
當然,這種方法可以應用於儘可能多的股票。 我們列出了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分析週期之道

總結

藉助Python和fbprophet包,我們可以更好地瞭解股市。 根據我們發現的週期,我們可以在3年內獲得大約23%的回報。 也許這種投資策略無法滿足所有人的需求,但你始終可以根據自己的知識和經驗設定自己的方法。 強大的fbprophet軟體包可以讓你對股票市場的分析更加深入和輕鬆。

延伸閱讀:

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

相關文章