[文献精汇]使用 LSTM Networks 的均值回归交易策略

server/2025/1/14 22:16:57/

 Backtrader 策略实例

  • [Backtrader]实例:均线策略[Backtrader] 实例:MACD策略[Backtrader] 实例:KDJ 策略
  • [Backtrader] 实例:RSI 与 EMA 结合
  • [Backtrader] 实例:SMA自定义数据源
  • [Backtrader] 实例:海龟策略[Backtrader] 实例:网格交易[Backtrader] 实例: 配对交
  • [Backtrader] 机器学习预测市场走势与回测

[介绍]([文献精汇]使用 LSTM Networks 的均值回归交易策略如何将均值回归理论与 LSTM 神经网络相结合,以创建一个交易策略。将讨论数据预处理、模型训练、策略实施和性能评估,从而避免数据泄露并使用有效的风险管理。

为什么选择均值回归和 LSTM?

了解均值回归

均值回归是一种金融策略,表明资产价格随着时间的推移趋向于其历史均值。市场参与者通过以下方式利用此策略:

  • 在价格低于平均值时购买资产。
  • 当价格超过均值时卖出。
    这个策略假设与平均值的偏差是暂时的,随着时间的推移价格会恢复到平均值(Poterba & Summers, 1988)。这种现象在各种市场和资产类别中都观察到(Balvers et al., 2000)。

了解长短期记忆(LSTM)

长短期记忆(LSTM)网络是专为序列数据设计的神经网络,例如时间序列金融数据(Hochreiter & Schmidhuber),1997)。他们擅长通过“记住”数据中的长期依赖关系来识别模式和预测未来的价格走势。以前的研究已经证明了LSTM在预测金融市场趋势方面的有效性(Fischer & Krauss,2018;Bao et al., 2017)。

准备数据

下载加密货币数据

获取过去 7 年比特币的每日价格数据,确保我们的模型有足够的历史数据。

import datetimeticker = 'BTC-USD'end_date = datetime.datetime.now()# set start_date 7 years back
start_date = end_date - datetime.timedelta(days=7*365)start_date_str = start_date.strftime('%Y-%m-%d')
end_date_str = end_date.strftime('%Y-%m-%d')# download data using yfinance with daily intervals
data = yf.download(tickers=ticker,start=start_date_str,end=end_date_str,interval='1d'
)
data.dropna(inplace=True)if data.empty:raise ValueError("No data downloaded. Please check the ticker symbol and internet connection.")
else:print("Data downloaded successfully.")

数据预处理

计算了几个捕捉价格趋势和波动性的技术指标。包括MA20、MA50、Bollinger Band、RSI、MACD、Momentum、TR、ATR。

  # calculating moving averagesdata['MA20'] = data['Close'].rolling(window=20).mean()data['MA50'] = data['Close'].rolling(window=50).mean()# calculating Bollinger Bandsdata['STD'] = data['Close'].rolling(window=20).std()data['Upper_Band'] = data['MA20'] + (data['STD'] * 2.5)data['Lower_Band'] = data['MA20'] - (data['STD'] * 2.5)# calculating %B (Bollinger Band %)data['%B'] = (data['Close'] - data['Lower_Band']) / (data['Upper_Band'] - data['Lower_Band'])# calculating RSIdelta = data['Close'].diff()up = delta.clip(lower=0)down = -1 * delta.clip(upper=0)roll_up = up.rolling(14).mean()roll_down = down.rolling(14).mean()RS = roll_up / roll_downdata['RSI'] = 100.0 - (100.0 / (1.0 + RS))# calculating MACD and Signal Lineexp1 = data['Close'].ewm(span=12, adjust=False).mean()exp2 = data['Close'].ewm(span=26, adjust=False).mean()data['MACD'] = exp1 - exp2data['Signal_Line'] = data['MACD'].ewm(span=9, adjust=False).mean()# calculating Momentumdata['Momentum'] = data['Close'] - data['Close'].shift(10)# calculating Average True Range (ATR)data['TR'] = data[['High', 'Close']].max(axis=1) - data[['Low', 'Close']].min(axis=1)data['ATR'] = data['TR'].rolling(window=14).mean()# drop rows if they have NaN valuesdata.dropna(inplace=True)

我们计算移动平均线、布林带、RSI、MACD、动量和 ATR 等技术指标。这些指标有助于捕捉趋势、动量和波动性,这对于价格预测和信号生成至关重要。

可视化技术指标

图片

Visualizing Technical Indicators

该图显示了比特币的收盘价以及 20 天和 50 天移动平均线和布林带。这有助于可视化价格趋势和波动性。

准备技术指标数据

features = ['Close', '%B', 'RSI', 'MACD', 'Signal_Line', 'Momentum', 'ATR']# feature scaling 
scaler = StandardScaler()
scaled_data = scaler.fit_transform(data[features])def create_sequences(data, seq_length):X = []y = []for i in range(seq_length, len(data)):X.append(data[i - seq_length:i])y.append(data[i, 0])  return np.array(X), np.array(y)seq_length = 60 X, y = create_sequences(scaled_data, seq_length)

我们使用 60 天的序列长度来捕获每个预测的两个月的历史数据,从而平衡足够的历史背景和计算效率。

将数据拆分为训练集和测试集

我们将数据分为训练集(5 年)和测试集(2 年),确保不会发生数据泄露。

train_size = int(5 * 365)  # 5 years for training
test_size = int(2 * 365)   # 2 years for testingX_train = X[:train_size]
y_train = y[:train_size]
X_test = X[train_size:train_size + test_size]
y_test = y[train_size:train_size + test_size]

通过将数据分为 5 年用于训练和 2 年用于测试,我们确保我们的模型在过去数据上进行训练,并在未来数据上进行测试,从而避免数据泄漏。

构建 LSTM 模型

model = Sequential()
model.add(LSTM(units=128, return_sequences=True, input_shape=(X_train.shape[1], X_train.shape[2]), kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(LSTM(units=64, return_sequences=False, kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(Dense(1))model.compile(optimizer='adam', loss='mean_squared_error')# early Stopping
early_stopping = EarlyStopping(monitor='val_loss', patience=5)history = model.fit(X_train,y_train,epochs=100,batch_size=32,validation_data=(X_test, y_test),callbacks=[early_stopping]
)

我们的模型从一个具有 128 个单元的 LSTM 层开始,并使用 L2 正则化来防止过拟合。我们以 0.3 的速率对正则化应用 dropout。第二个具有 64 个单元的 LSTM 层紧随其后,捕获更抽象的模式。最后,我们有一个 dense 层来生成输出。

预测评估模型

我们对测试集进行预测,并使用 MAE、MSE 和 RMSE 评估模型的性能。

predictions = model.predict(X_test)# error metrics
mae = mean_absolute_error(y_test, predictions)
mse = mean_squared_error(y_test, predictions)
rmse = np.sqrt(mse)print(f"MAE: {mae}")
print(f"MSE: {mse}")
print(f"RMSE: {rmse}")

图片

png

  • 该模型在测试集上实现了 0.1483 的平均绝对误差 (MAE) 和 0.2217 的均方根误差 (RMSE),表明预测性能合理。
  • 我们还将预测转换回原始比例。

实施交易策略

根据模型的预测和技术指标实施均值回归交易策略。

test_data = data.iloc[train_size + seq_length:train_size + seq_length + test_size].copy()
test_data['Predicted_Close'] = predicted_close
test_data['Actual_Close'] = actual_close# predicted changes calculations
test_data['Predicted_Change'] = (test_data['Predicted_Close'] - test_data['Actual_Close']) / test_data['Actual_Close']# genereate trading signals based on adjusted strategy
test_data['Signal'] = 0# adjust thresholds based on percentiles
rsi_buy_threshold = test_data['RSI'].quantile(0.4)
rsi_sell_threshold = test_data['RSI'].quantile(0.6)
predicted_change_buy_threshold = test_data['Predicted_Change'].quantile(0.6)
predicted_change_sell_threshold = test_data['Predicted_Change'].quantile(0.4)# buy signal
test_data.loc[(test_data['Predicted_Change'] > predicted_change_buy_threshold) &(test_data['RSI'] < rsi_buy_threshold),'Signal'
] = 1# sell signal
test_data.loc[(test_data['Predicted_Change'] < predicted_change_sell_threshold) &(test_data['RSI'] > rsi_sell_threshold),'Signal'
] = -1# count the number of buy and sell signals
num_buy_signals = (test_data['Signal'] == 1).sum()
num_sell_signals = (test_data['Signal'] == -1).sum()print(f"Number of Buy Signals: {num_buy_signals}")
print(f"Number of Sell Signals: {num_sell_signals}")
Number of Buy Signals: 104
Number of Sell Signals: 135

我们的策略在测试期间产生了 133 个买入信号和 142 个卖出信号。这表明该模型确定了资产相对于其预测均值被低估或高估的几个机会。

模拟交易

以 500 美元的初始资本模拟交易,包括交易成本、止损和止盈机制。

initial_capital = 500.0
positions = []
cash = initial_capital
holdings = 0
portfolio_value = []
transaction_cost = 0.0005  # let's assume 0.05% trading fee per trade
stop_loss_percent = 0.1    # 10% stop-loss
take_profit_percent = 0.2  # 20% take-profit
entry_price = Nonefor index, row in test_data.iterrows():price = row['Actual_Close']signal = row['Signal']if signal == 1 and cash > 0:# buy with a portion of cash (e.g., 50%)amount_to_buy = (cash * 0.5) * (1 - transaction_cost)holdings += amount_to_buy / pricecash -= amount_to_buyentry_price = pricepositions.append({'Date': index, 'Position': 'Buy', 'Price': price})elif signal == -1 and holdings > 0:# sell all holdingsamount_to_sell = holdings * price * (1 - transaction_cost)cash += amount_to_sellholdings = 0entry_price = Nonepositions.append({'Date': index, 'Position': 'Sell', 'Price': price})elif holdings > 0:# check for stop-loss or take-profitif price <= entry_price * (1 - stop_loss_percent):# trigger stop-lossamount_to_sell = holdings * price * (1 - transaction_cost)cash += amount_to_sellholdings = 0positions.append({'Date': index, 'Position': 'Stop Loss Sell', 'Price': price})entry_price = Noneelif price >= entry_price * (1 + take_profit_percent):# trigger take-profitamount_to_sell = holdings * price * (1 - transaction_cost)cash += amount_to_sellholdings = 0positions.append({'Date': index, 'Position': 'Take Profit Sell', 'Price': price})entry_price = Nonetotal_value = cash + holdings * priceportfolio_value.append(total_value)# musst ensure portfolio_value matches test_data length
test_data['Portfolio_Value'] = portfolio_value[:len(test_data)]

绩效指标

# calculate daily returns and cumulative returns
test_data['Daily_Return'] = test_data['Portfolio_Value'].pct_change()
test_data['Cumulative_Return'] = (1 + test_data['Daily_Return']).cumprod()# calculate annualized return
total_days = (test_data.index[-1] - test_data.index[0]).days
if total_days == 0:total_days = 1  # Avoid division by zeroannualized_return = (test_data['Cumulative_Return'].iloc[-1]) ** (365 / total_days) - 1# calculate Sharpe Ratio
returns = test_data['Daily_Return'].dropna()
if returns.std() != 0:sharpe_ratio = (returns.mean() / returns.std()) * np.sqrt(252)  # Annualized Sharpe Ratio
else:sharpe_ratio = 0.0# calculate Max Drawdown
rolling_max = test_data['Portfolio_Value'].cummax()
drawdown = test_data['Portfolio_Value'] / rolling_max - 1
max_drawdown = drawdown.min()# Print performance metrics
total_return = ((test_data['Portfolio_Value'].iloc[-1] - initial_capital) / initial_capital) * 100
print(f"Total Return: {total_return:.2f}%")
print(f"Annualized Return: {annualized_return * 100:.2f}%")
print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
print(f"Max Drawdown: {max_drawdown * 100:.2f}%")
Total Return: 49.05%
Annualized Return: 26.49%
Sharpe Ratio: 0.80
Max Drawdown: -19.67%

我们的策略在两年的测试期内实现了 60.20% 的总回报率,将最初的 500 美元增长到大约 800 美元。年化回报率为 32.09%,这是可观的。0.94 的夏普比率表明相对于所承担的风险而言,回报良好。-16.88% 的最大回撤显示了在此期间最大的峰谷下跌,考虑到回报,这是可以接受的。

可视化交易信号

在 K 线图上绘制买入和卖出信号,以便更好地可视化。

# candlestick plotting
plot_data = data.loc[test_data.index][['Open', 'High', 'Low', 'Close']].copy()
plot_data.index.name = 'Date'# buy and sell signal markers for plotting
buy_signals = test_data[test_data['Signal'] == 1]
sell_signals = test_data[test_data['Signal'] == -1]

图片

png

投资组合值随时间的变化

可视化投资组合价值随时间的变化,以观察策略的表现。

plt.figure(figsize=(12, 6))
plt.plot(test_data.index, test_data['Portfolio_Value'], label='Portfolio Value')
plt.title('Portfolio Value Over Time')
plt.xlabel('Date')
plt.ylabel('Portfolio Value in USD')
plt.legend()
plt.show()

图片

png

评估性能

# performance Periods analysis
test_data['Strategy_Return'] = test_data['Portfolio_Value'].pct_change()
test_data['Rolling_Return'] = test_data['Strategy_Return'].rolling(window=30).sum()# periods of good performance
good_performance = test_data[test_data['Rolling_Return'] > 0.02]# Periods of poor performance
poor_performance = test_data[test_data['Rolling_Return'] < -0.02]# Compare our strategy with Buy-and-Hold Strategy
test_data['Buy_and_Hold'] = initial_capital * (test_data['Actual_Close'] / test_data['Actual_Close'].iloc[0])

图片

png

图片

png

结论

在本文中,我们探讨了使用应用于比特币价格数据的 LSTM 模型实现均值回归交易策略。通过在预处理数据、防止数据泄露和整合风险管理方面采取谨慎的预防措施,我们制定了一项策略,在两年内将 500 美元的初始投资转换为大约 800 美元。


http://www.ppmy.cn/server/158389.html

相关文章

C 语言的待解之题与前行之路:探寻那些显而易见的改进方向

在编程语言的历史长河中&#xff0c;C 语言一直占据着重要的地位&#xff0c;历经多次标准更新&#xff0c;如今已发展到 C23 版本。然而&#xff0c;令人困惑的是&#xff0c;一些明显的问题却始终未得到妥善解决。与此同时&#xff0c;D 语言社区在其编译器中嵌入了 C 编译器…

uniapp小程序分包路由跳转+二级页面详情跳转保留当前页方法教程

uniapp小程序分包路由跳转二级页面详情跳转保留当前页&#xff0c;进入二级页面&#xff0c;可以返回上一级页面。也就是保留当前页&#xff0c;这里用的是vue3uniappuv-ui组件库 步骤一&#xff1a; 新建文件夹目录。 代码&#xff1a; "subPackages": [{// 动态详…

HarmonyOS应用开发者初级认证最新版– 2025/1/13号题库新版

1.欢迎各位读者&#xff0c;本文档来自鸿蒙开发学员亲测&#xff0c;最新版。&#xff08;考试时直接Ctrlf进行搜索&#xff0c;一定要认真比对答案&#xff0c;有的答案相似度很高&#xff09;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 欢迎…

nexus搭建maven私服

说到maven私服每个公司都有&#xff0c;比如我上一篇文章介绍的自定义日志starter&#xff0c;就可以上传到maven私服供大家使用&#xff0c;每次更新只需deploy一下就行&#xff0c;以下就是本人搭建私服的步骤 使用docker安装nexus #拉取镜像 docker pull sonatype/nexus3:…

HarmonyOS鸿蒙-@State@Prop装饰器限制条件

一、组件Components级别的状态管理&#xff1a; State组件内状态限制条件 1.State装饰的变量必须初始化&#xff0c;否则编译期会报错。 // 错误写法&#xff0c;编译报错 State count: number;// 正确写法 State count: number 10; 2.嵌套属性的赋值观察不到。 // 嵌套的…

vue3之router路由

路由 1、对路由的理解 2、基本使用 安装路由器扩展包 npm i vue-router 创建组件&#xff1a; Home.vue <template><div class"home"><img src"https://oss.fmy90.cn/fmy/public/4db8dec4d2eb31b8b0456cb42a907941.png" alt""…

python milvus 如何检查有多少个collection 以及多少个index,多少个database

在 Milvus 中,可以通过 Python 客户端(`pymilvus`)来检查当前有多少个集合(Collection)、索引(Index)和数据库(Database)。以下是具体的方法: --- ### 1. 检查有多少个集合(Collection) 使用 `list_collections()` 方法可以列出当前连接的所有集合。 ```python…

【Python】Python之Selenium基础教程+实战demo:提升你的测试+测试数据构造的效率!

这里写目录标题 什么是Selenium&#xff1f;Selenium基础用法详解环境搭建编写第一个Selenium脚本解析脚本脚本执行结果常用的元素定位方法常用的WebDriver方法等待机制 Selenium高级技巧详解页面元素操作处理弹窗和警告框截图和日志记录多窗口和多标签页操作 一个实战的小demo…