股市风起云涌,我用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代码实现

相关文章
相关标签/搜索