在加密货币的世界,每个人都在寻求赚钱。要么通过采矿、交易,要么完全通过其他方式。对我来说,我想看看是否可以通过交易赚钱。但是,不仅仅是单独交易,还可以使用机器学习进行交易!
使用机器学习来预测市场价格并不是一个新概念。许多人以前这样做过,但我想看看它是什么样的。对我来说,仅仅预测价格是不够的。我想看看如果我以这些预测价格进行交易会发生什么。这就是使用机器学习进行回测的地方。
为了让我开始对加密货币进行回测,我首先需要选择一个机器学习模型。现在,有很多时间序列机器学习模型可供实验,但我想选择一个不那么常见的模型。最后,我选择了 Facebook 自己的时间序列模型——Facebook Prophet。
Prophet 由 Facebook 的数据科学团队开发,它是开源的,最适用于时间序列数据。这使其成为用于加密货币的机器学习模型的绝佳候选者。
在本文中,我们将介绍我如何编写 Facebook Prophet 的预测、围绕 Prophet 构建的交易策略,以及我如何对该策略进行回测。如果您愿意,请随意编写代码,让我们开始吧!
入门
首先,导入以下库:
从 tqdm 导入 tqdm
导入 pandas 作为 pd
从预言家导入 Prophet
导入 yfinance 作为 yf
从日期时间导入 datetime, timedelta
导入 plotly.express 作为 px
导入 numpy 作为 np
如果您正在编码,您可能需要上述pip install任何库。
获取价格历史数据
以下功能允许我们使用 Yahoo Finance 上的有效代码获取任何加密货币的价格历史记录:
def getData(ticker, window, ma_period): """ Grabs price data from a given ticker. Retrieves prices based on the given time window; from now to N days ago. Sets the moving average period for prediction. Returns a preprocessed DF formatted for FB Prophet. """ # Time periods now = datetime.now() # How far back to retrieve tweets ago = now - timedelta(days=window) # Designating the Ticker crypto = yf.Ticker(ticker) # Getting price history df = crypto.history(start=ago.strftime("%Y-%m-%d"), end=now.strftime("%Y-%m-%d"), interval="1d") # Handling missing data from yahoo finance df = df.reindex( [df.index.min()+pd.offsets.Day(i) for i in range(df.shape[0])], fill_value=None ).fillna(method='ffill') # Getting the N Day Moving Average and rounding the values df['MA'] = df[['Open']].rolling(window=ma_period).mean().apply(lambda x: round(x, 2)) # Dropping the NaNs df.dropna(inplace=True) # Formatted for FB Prophet df = df.reset_index().rename(columns={"Date": "ds", "MA": "y"}) return df
在这个函数中,给定一个适当的加密代码、检索天数和移动平均长度,它将返回一个专门为 Facebook Prophet 格式化的数据帧。
处理价格历史
为了获取每日价格历史记录,我使用yfinance检索加密货币的历史数据。然而,在我对 yfinance 和 Yahoo Finance 本身的加密历史数据的实验中,我发现了一些缺失的价格历史——在 730 天中大约缺失了 3 天。但仅仅妥协机器学习模型的整体训练和测试是不够的。
但为了弥补缺失的数据,我使用 pandas 用前一天的值(“ ffill ”)填充缺失值。
检索价格数据的另一种选择是使用财务数据 API,例如 EOD 历史数据。它是免费注册的,您将可以访问大量的财务数据。披露:我从通过上面链接进行的任何购买中赚取少量佣金。
移动平均线
为了帮助平滑价格数据并减少任何异常值的影响,我使用了价格的每日移动平均线。移动平均周期可以设置为您想要的任何值,但我发现 5 或 3 是最好的。
Facebook Prophet 的格式化
由于 Prophet 以特定方式获取价格数据,我必须将日期和移动平均列分别格式化为“ ds ”和“ y ”。格式化后,该函数返回完整的 DataFrame 以供以后在 Facebook Prophet 中使用。
培训 Facebook 先知
接下来,使用上一个函数提供的 DataFrame 训练 Prophet 模型并预测给定时间段的价格:
def fbpTrainPredict(df, forecast_period): """ Uses FB Prophet and fits to a appropriately formatted DF. Makes a prediction N days into the future based on given forecast period. Returns predicted values as a DF. """ # Setting up prophet m = Prophet( daily_seasonality=True, yearly_seasonality=True, weekly_seasonality=True ) # Fitting to the prices m.fit(df[['ds', 'y']]) # Future DF future = m.make_future_dataframe(periods=forecast_period) # Predicting values forecast = m.predict(future) # Returning a set of predicted values return forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
Facebook Prophet 设置和培训
要启动模型,请将所有季节性选项设置为,True因为提供的训练数据最好为一年或更长时间。我已经尝试过其他季节性选项,但这些参数似乎效果最好。
使用 Prophet 进行预测
将模型拟合到数据后,我们需要在未来的给定天数内制作一个未来的数据框。此 DF 将用于根据模型和训练数据进行预测。
做出预测后,将返回一个新的 DF,其中包含一组全新的包含预测数据的列。但是,我们将只关注以下新列:
- yhat
- yhat_upper
- yhat_lower
这些列是 Prophet 基于给定训练数据的预测值。上限值和下限值将构成稍后交易策略的基础。
可视化预测
在以下函数中,我使用Plotly来可视化模型的预测:
def visFBP(df, forecast): """ Given two dataframes: before training df and a forecast df, returns a visual chart of the predicted values and actual values. """ # Visual DF vis_df = df[['ds','Open']].append(forecast).rename( columns={'yhat': 'Prediction', 'yhat_upper': "Predicted High", 'yhat_lower': "Predicted Low"} ) # Visualizing results fig = px.line( vis_df, x='ds', y=['Open', 'Prediction', 'Predicted High', 'Predicted Low'], title='Crypto Forecast', labels={'value':'Price', 'ds': 'Date'} ) # Adding a slider fig.update_xaxes( rangeselector=dict( buttons=list([ dict(count=1, label="1m", step="month", stepmode="backward"), dict(count=3, label="3m", step="month", stepmode="backward"), dict(count=6, label="6m", step="month", stepmode="backward"), dict(count=1, label="YTD", step="year", stepmode="todate"), dict(count=1, label="1y", step="year", stepmode="backward"), dict(step="all") ]) ) ) return fig.show()
给定实际值的原始 DF 和预测/预测 DF,此功能将显示 Prophet 预测的绘图图表,并带有各种查看选项。
使用 Facebook Prophet 预测比特币
现在我们已经拥有了我们需要的所有功能——是时候进行预测了!让我们看看比特币在不久的将来会走向何方。使用之前的三个函数,让我们用过去两年训练模型并创建未来 3 个月的预测:
# 获取和格式化数据 df = getData("BTC-USD", window=730, ma_period=5)# 训练和预测数据 预测 = fbpTrainPredict(df, forecast_period=90)# 可视化数据 visFBP(df, forecast)
运行上述代码后,您将看到 Facebook Prophet 的比特币预测价格:
比特币的预测价格
根据 Facebook Prophet 的说法,比特币未来可能看起来相当看涨。但请记住,这些预测在任何情况下都不是保证。您可以调整参数、提供更多数据或使用完全不同的模型,但即使使用机器学习也无法准确预测未来。
接下来,让我们看看如果我们使用 Facebook Prophet 的预测作为交易信号会发生什么……
回测 Facebook Prophet
为了回测这个模型,我们首先检索两年的数据。第一年将用作训练数据。每十天,模型将在前 365 天进行训练,以预测接下来的 10 天。这个过程将贯穿整个第二年。此过程的预测将与第二年的实际价格值进行比较和测试。
要开始此操作,我们需要在两年内移动时间窗口/间隔上运行 Facebook Prophet。例如:
- Prophet 对2019-12-31到2020-12-31的价格数据进行训练,以预测到 2021-01-10 的价格。
- 接下来,向前跳 10 天并训练从 2020-01-10 到 2021-01-10 的价格数据,以预测到 2021-01-20 的价格。
- 等等等等。
这样我们就可以预测我们正在回测的一年中的每一天。
def runningFBP(ticker, window=730, ma_period=5, days_to_train=365, forecast_period=10): """ Runs the facebook prophet model over the provided ticker. Trains with last N days given by days_to_train. Forecast N days into the future based on given forecast_period. Moving average is applied to the dataset based on given ma_period. Returns the root mean squared error and a DF of the actual values and the predicted values for the same day. """ # Getting and Formatting Data df = getData(ticker, window=window, ma_period=ma_period) # DF for the predicted values pred_df = pd.DataFrame() # Running the model on each day for i in tqdm(range(days_to_train, window-forecast_period, forecast_period)): # Training and Predicting the last day on the forecast forecast = fbpTrainPredict(df[i-days_to_train:i], forecast_period=forecast_period).tail(forecast_period)[['ds', 'yhat', 'yhat_lower', 'yhat_upper']] # Adding the last day predicted pred_df = pred_df.append(forecast, ignore_index=True) # Combining the predicted df and original df comb_df = df[['ds', 'Open']].merge(pred_df, on='ds', how='outer').sort_values(by='ds') # Setting the index to the dates comb_df.set_index('ds', inplace=True) return comb_df
间隔训练和预测
在这个函数中是我们之前创建的 2 个函数 -getData()和fbpTrainPredict(). 如前所述,为了模拟移动时间间隔和明年的日常预测,使用“ for ”循环,将 Prophet 训练和预测添加到整体预测 DataFrame 中。
这个预测 DF 将被合并到原始 DF 并被视为具有预测的新列。然后将其返回以供稍后进行回测。
交易策略
由于 Prophet 返回上限值和下限值预测,我们将使用这些值来告知交易决策。我们的交易策略很简单:
- 如果实际价格高于预期yhat_upper,那么我们将购买并持有该加密货币。
- 如果实际价格低于预期yhat_lower,那么我们将做空加密货币。
- 如果实际价格在yhat_upper和之间yhat_lower,那么我们将不做任何事情或关闭当前头寸。
def get_prophet_positions(df, short=True): """ For these positions, buy when actual value is above the upper bound and short when actual value is below lower bound. Otherwise do nothing. """ if df['Open'] >= df['yhat_upper']: return 1 elif df['Open'] <= df['yhat_lower'] and short: return -1 else: return 0
此功能反映了我们在交易策略中所制定的内容。它还可以选择是否做空加密货币。这样做是为了让我们可以比较完全看涨的策略与看涨和看跌策略的组合。
执行回测
现在我们已经能够得到预测和位置,我们可以正式开始回测了。这里完成的回测方法称为矢量化回测。
def fbpBacktest(df, short=True): """ Performs the final backtest using log returns and the positions function. Returns the performance. """ # Getting positions df['positions'] = df.apply(lambda x: get_prophet_positions(x, short=short), axis=1) # Compensating for lookahead bias df['positions'] = df['positions'].shift(1) # Getting log returns df['log_returns'] = df['Open'].apply(np.log).diff() # Dropping any Nans df.dropna(inplace=True) # Performing the backtest returns = df['positions'] * df['log_returns'] # Inversing the log returns and getting daily portfolio balance performance = returns.cumsum().apply(np.exp) return performance
处理前瞻偏差
回测时总是存在前瞻性偏差的可能性。为了弥补这一点,我们将主要关注开盘价并将头寸提前一天。这两项调整有望减少前瞻偏差的影响。
使用日志返回
为了更恰当地捕捉加密货币的日常回报,使用了日志回报。这些返回值被分配到它们自己的列中,该列将与位置一起使用,最终返回回测的整体性能。
一旦我们通过将日志返回和位置相乘来完成回测,并将值转换回正常值,我们将得到回测的结果!
回测加密结果
对于这个回测,我们将使用 Prophet 和以太坊来看看如果我们在过去一年中有 ETH 预测会发生什么:
# 运行模型并获得预测 bt_df = runningFBP("ETH-USD", window=730, ma_period=5, days_to_train=370, forecast_period=10)# 执行回 测性能 = fbpBacktest(bt_df, short=True)# 可视化结果 px.line(performance, x=performance.index, y=performance, title='Portfolio Performance', labels={"y": "Portfolio Balance", "ds": "Date"})
通过运行上面的代码,如果我们根据 Facebook Prophet 的预测交易 ETH,我们将看到一张图表,显示过去一年我们假设的投资组合余额:
过去一年与 Prophet 交易 ETH 的结果
结果不是太好。尽管我们最终获得了积极的结果,但该策略非常不稳定,这也可能是由于一般加密货币的固有性质。
仅看涨交易
但是,如果我们决定完全不做空并始终保持看涨,会发生什么?我们需要做的就是将回short测函数中的参数更改为False. 让我们看看结果如何……
只在交易 ETH 时看涨的结果
还不错。比之前的回测要好得多,尽管它仍然非常不稳定。我们最终确实得到了更好的投资组合。
这些回测的结果仅来自两个不同的迭代。如果我们在这里和那里改变几个参数,我们最终可能会得到完全不同的结果。随意摆弄其他参数,看看是否可以增加期末投资组合余额并降低波动性。如果需要,甚至可以更改交易策略本身。
结束
在使用 Facebook Prophet 对加密货币进行几次回测后,我们可以看到那里可能存在潜力。但是,这并不完全是决定性的,需要对整体策略进行更多的回测和调整。重要提示——您应该只将本文用作教育参考,而不是作为您自己投资组合的官方交易策略。
Facebook Prophet 适用于加密货币,我们需要继续运行更多测试。因为我们刚刚触及了表面,所以还有很多其他参数需要试验。请参阅下面的 Github 存储库以查看上面的代码。继续试验,如果你得到更好的结果,请告诉我!