The body of the

The stock market cycle is the long-term price pattern of the stock market, usually related to the business cycle. It is key to technical analysis, where the investment approach is based on cyclical or repeating price patterns. If we have a better understanding of the stock market cycle, we can always buy at a relatively low price and sell at a relatively high price each cycle, and will always have a positive return. Of course, no strategy in the stock market will always make money, but we built Python to help us understand the hidden cycles in the stock market more deeply and quickly.


Fbprophet profile

Fbprophet is an open source software released by Facebook that aims to provide some useful guidance for large-scale forecasting. By default, it divides time series into trend and season, which may include annual, weekly, and daily. However, analysts can define their own seasonality. To better understand the library, lead files are useful.


One feature of the library is its simplicity and flexibility. Since the stock market cycles we want to calculate are not limited to annual, weekly, or daily, we should define our own cycles and figure out which ones fit the data better. Also, since there are no weekend trades, we should not use weekly seasonality. We can also define ‘self_define_cycle’ through the add_seasonality function. All it takes is two lines of code.

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


Costco, for example

We can use Costco bid from 2015/10/1 to 2018/10/1, using pandas_datareader, we can quickly read stock prices. The diagram below:

Pandas – the datareader. Readthedocs. IO/en/latest/r…


In the chart below, we can see a strong trend of price growth from 2015. However, there are still many ups and downs in the middle of the cycle, which is where we make our money.

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']. The plot (figsize = (16, 8), color ='# 002699'PLT, alpha = 0.8). The 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()
Copy the code


For prediction models, one way to evaluate them is the mean square error of the sample. We can use 2015/10/1 to 2018/3/31 for training, and retain the data of the last 6 months for testing and calculation of sample mean square error. In each cycle, we can optimize our returns by buying at the lowest price and selling at the highest price. To simplify the process, we use the custom function Cycle_analysis. The output is a list of expected returns and sample mean square error for each period.

  • Data: The Pandas data with the time index
  • Split_date: date of dividing training and test data cycle: interval of each cycle (days)
  • 14. Interval (days) between cycles
  • Mode: seasonal addition or multiplication (optional)
  • Forecast_plot: Whether to print the forecast graph (optional, default is False)
  • Print_ind: Whether to print the expected return for each cycle and whether to sample the mean square error (optional, default 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'PLT, alpha = 0.6). The 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
Copy the code


In the following two figures, we apply two different cycles (30 and 300) to Costco stock price respectively, and take 2018/4/1 as the segmentation date of training and test. As we can see, if we choose a shorter length (e.g. 30 days) the return over a cycle is small and we need to trade frequently, if we choose a longer length it extends our forecast (e.g. 300 days).


We can apply a loop to the Cycle_analysis function to calculate the expected return and sample mean square error for different loop lengths, and we show the results in the figure below. As we can see, the longer the length, the higher the expected return and sample mean square error per period. Taking into account transaction costs, the expected return should be greater than $10 per cycle. Under this constraint, we can choose the period of minimum sample mean square error, and it is 252 days. The expected return for each period is $17.12 with a mean square error of 15.936. Both are good!

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])
Copy the code

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']]Copy the code

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)
Copy the code

Projected Return per cycle: 17.12mse: 15.9358 [17.120216439034987, 15.93576020351612]


To further illustrate the investment strategy, we can see the buy and sell dates between 2015/10/1 and 2018/10/1. The Return_Dates function returns all buy and sell dates as output, entering:

  • Forecast: FBProphet forecast object
  • Stock_data: Pandas data with a time index
  • Cycle: indicates the cycle length
  • Cycle_name: The name of the loop column in the prediction object
  • Time_name: Name of the time column in the prediction object
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]
Copy the code


We bought and sold Costco four times between 2015/10/1 and 2018/10/1. The return over three years was 23.2 per cent. It may not be very attractive, but at least it’s an optimistic return.


Of course, this method can be applied to as many stocks as possible. For Costco, Apple, Microsoft, Home Depot and Nike, we listed the average purchase price, average selling price, cycle length, sample mean square error, number of purchases, number of sales and expected return within each cycle.

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)
Copy the code


For Microsoft and Nike, we couldn’t find a cycle that met our requirement of more than 10 yuan per cycle. For Costco, Apple and Home Depot, we can find cycles of about 250 days and make good forecasts and good returns.

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
Copy the code


conclusion

With Python and the FbProphet package, we can better understand the stock market. Based on the cycles we found, we could make a return of about 23 per cent over three years. This investment strategy may not be for everyone, but you can always set your own approach based on your knowledge and experience. The powerful FBProphet software package makes your analysis of the stock market much deeper and easier.


Read more:

Classic deep learning tutorial and Python code implementation