LSTM Trading Strategy (机器学习交易策略)
以下代码由ChatGPT生成一个模版,做了不少的改动。
需要配置一下数据,可以从其他网站下载任意感兴趣的相关交易数据。
配置 USER_HOME 这个环境变量,指向你的数据目录,或者任意的本地工作目录。
为节省测试时间:1)总是选取后1500个数据点,可以改,但是注意不要太小,否则不够切分;2)神经网络选了小的,可以增大到比如512,但是速度会更慢。
fit 里的 epochs, batch_size 和 MinMaxScalar(0,1) 都是很关键的基础配置项。
import datetime,random,os
import numpy as np
import pandas as pd
import talib
import tensorflow as tf
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.callbacks import ModelCheckpoint,EarlyStopping
import matplotlib.pyplot as plt
tf.keras.backend.clear_session()
class LossHistory(tf.keras.callbacks.Callback):
def on_train_begin(self, logs={}):
self.losses = []
#self.val_losses = []
def on_batch_end(self, batch, logs={}):
self.losses.append(logs.get('loss'))
#self.val_losses.append(logs.get('val_loss'))
def download_data():
#df = pd.read_csv("data/^GSPC_short.csv")
df = pd.read_csv(os.getenv('USER_HOME','') + '/tmp/btc-usdt_1D.csv')
df = df[-1500:]
df['Adj Close'] = df['close'].astype(float)
df['direction'] = df['close'].astype(float)-df['open'].astype(float)
df['direction'] = df['direction'].apply(lambda v: 1 if v>0 else -1 if v<0 else 0)
df['Volume'] = (df['volume'] * df['direction']).astype(float)
df['Returns'] = (df['close'] - df['open'])/df['open']
df['macd'] = talib.EMA(df['Adj Close'].astype(float), timeperiod=7) \
- talib.EMA(df['Adj Close'].astype(float), timeperiod=14)
data = df.copy()
data = data.dropna()
ts = data[['timestamp']]
return data[['Adj Close','Volume', 'macd']],ts
data, ts = download_data()
print( data )
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data)
y_scaler = MinMaxScaler(feature_range=(0, 1))
_ = y_scaler.fit_transform( data[['Adj Close']])
# Split the data into training and testing sets
train_size = int(len(scaled_data) * 0.7)
train_data, test_data = scaled_data[:train_size], scaled_data[train_size:]
test_ts = ts.to_numpy()[train_size:]
def create_dataset(dataset, time_step=1):
dataX, dataY = [], []
for i in range(len(dataset) - time_step - 1):
a = dataset[i:(i + time_step), :] # All columns, i.e., features are considered
dataX.append(a)
dataY.append(dataset[i + time_step, 0]) # Only the "Adj Close" needs to be fitted
return np.array(dataX), np.array(dataY)
time_step = 120*2
X_train, y_train = create_dataset(train_data, time_step)
X_test, y_test = create_dataset(test_data, time_step)
nfeatures = data.shape[1]
assert X_train.shape[1] == time_step, 'Should be equal.'
# Reshape input to be [samples, time steps, features]
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], nfeatures)
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], nfeatures)
cbs = [
LossHistory(),
ModelCheckpoint("foobar", monitor='loss', verbose=0, save_best_only=True, mode='min'),
EarlyStopping(patience=3)
]
l1 = 51 # 512 # Larger is slower!
l2 = 50 # 256
name = f"{int(datetime.datetime.utcnow().timestamp())}{random.randint(1,10)}"
model = Sequential(name=name)
model.add(LSTM(l1, return_sequences=True, input_shape=(time_step, nfeatures)))
model.add(Dropout(0.2))
model.add(LSTM(l2, stateful=False, recurrent_dropout=0.01 )) #kernel_regularizer=regularizers.l2(0.001),
model.add(Dropout(0.2))
#model.add(Dense(32))
model.add(Dense(1,activation='relu'))
model.compile(optimizer='adam', loss='mean_squared_error')
model.fit(X_train, y_train,
#callbacks=cbs,
validation_split=0 if len(X_train) < 10 else 0.3,
batch_size=1,
epochs=1
)
train_predict = model.predict(X_train)
test_predict = model.predict(X_test)
def trading_strategy(test_data, test_predict, threshold=0.01):
signal = []
for i in range(len(test_predict)):
mkt_pce = test_data[i, 0] # Assumed: 1st colume is the Adj Close!!
if test_predict[i] > mkt_pce * (1 + threshold):
signal.append(1) # Buy
elif test_predict[i] < mkt_pce * (1 - threshold):
signal.append(-1) # Sell
else:
signal.append(0) # Hold
return signal
signals = trading_strategy(test_data[time_step:], test_predict)
# Transform back to original scale
train_predict = y_scaler.inverse_transform(train_predict)
y_train = y_scaler.inverse_transform([y_train])
test_predict = y_scaler.inverse_transform(test_predict)
y_test = y_scaler.inverse_transform([y_test])
test_data = scaler.inverse_transform(test_data)
plt.figure(figsize=(16,8))
plt.plot(data.index, data['Adj Close'], label='Actual Price')
plt.plot(data.index[time_step+1: train_size], train_predict, label='Train Predict')
plt.plot(data.index[len(train_predict)+(time_step*2)+1:len(data)-1], test_predict, label='Test Predict')
plt.legend()
plt.title(f'Look back window width: {time_step}')
fn = os.getenv("USER_HOME",'') + '/tmp/chatgpt_lstm.png'
plt.savefig( fn )
print( 'saved:',fn)
initial_balance = 1_000_000
def backtest_strategy(data, signals, test_ts):
balance = initial_balance
shares = 0
for i in range(len(signals)):
pce = data[i + time_step, 0] # Assumed the first column is the Adj Close price.
if signals[i] == 1 and balance >= pce: # Buy
shares = balance // pce
balance -= shares * pce
print(f'-- [ {test_ts[i]} ] buy ${pce} (+{shares})' )
elif signals[i] == -1 and shares > 0: # Sell
balance += shares * pce
print(f' -- [ {test_ts[i]} ] sell ${pce} (-{shares})' )
shares = 0
# Mark-to-market, final portfolio balance
if shares > 0:
balance += shares * data[-1, 0]
return balance
final_balance = backtest_strategy(test_data, signals,test_ts)
rtn = (final_balance-initial_balance)/initial_balance*100
print(f"{test_ts[0][0]} ~ {test_ts[-1][0]}")
print(f"Final balance: {final_balance:,.2f}, {rtn:.1f}%")