New experiment. Aim is to explore MLPs in greater depth. I’ve decided to use ETH, specifically the ETH/USDT trading pair, as my data. I’ve downloaded daily data going back more than three years from Binance.
I’ll use daily returns for the output (target, label, whatever) as before, and I plan to use more features than I did for the BTC experiment. I’ll start with a few more lags, and then see if I can improve on that by adding additional features. Not sure at this stage what those will be. Lots of trial and error coming up.
I’ve done some initial exploration. First Linear Regression, using 1 lag and then 5 lags. Next, MLP using 5 lags as inputs and 1 output (should be equivalent to the LR with 5 lags, and then a slightly more complex MLP with a hidden layer with 10 inputs and 1 output. I guess I should do an MLP with just 1 lag as input but the BTC experiment showed me that this is practically identical to LR with one lag as input so I didn’t bother.
| Algorithm | RMSE |
| Linear Regression, 1 lag | 0. 03479 |
| Linear Regression, 5 lags | 0.03506 |
| MLP, 5 lags, 1 output | 0.03617 |
| MLP, 5 lags, 10 nodes in 1 hidden layer, 1 output | 0.03523 |
| MLP, 3 hidden layers, one with 50 nodes | 0.03490 |
Using 5 lags doesn’t seem to make any difference, and using an MLP with a fairly simple structure (1 hidden layer with 10 nodes) doesn’t make much difference either. I tried a more complex MLP, 2 hidden layers, one with 50 nodes. A bit better than the other MLP results but hardly different from the Linear Regression with only 1 lag for input.
import torch
import torch.nn as nn
from torch.autograd import Variable
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error
df = pd.read_csv('data/eth.csv', usecols=['close'])
df_returns = df['close'].to_frame().pct_change()
df_returns.rename(columns={'close': 't'}, inplace=True)
df_returns.insert(0, 't-1', df_returns['t'].shift(1))
df_returns.insert(0, 't-2', df_returns['t'].shift(2))
df_returns.insert(0, 't-3', df_returns['t'].shift(3))
df_returns.insert(0, 't-4', df_returns['t'].shift(4))
df_returns.insert(0, 't-5', df_returns['t'].shift(5))
df_returns.dropna(inplace=True)
y = df_returns['t'].to_numpy(dtype=np.float32).reshape(-1, 1)
X = df_returns.drop('t', axis=1).to_numpy(dtype=np.float32)
train_limit = 1000
X_train, X_test = X[:train_limit], X[train_limit:]
y_train, y_test = y[:train_limit], y[train_limit:]
model = nn.Sequential(
nn.Linear(5,50),
nn.ReLU(),
nn.Linear(50,20),
nn.ReLU(),
nn.Linear(20,1)
)
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
for epoch in range( 1000):
inputs = Variable(torch.from_numpy(X_train))
targets = Variable(torch.from_numpy(y_train))
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
with torch.no_grad():
y_pred = model(Variable(torch.from_numpy(X_test))).data.numpy()
loss = mean_squared_error(y_test, y_pred)
print(np.sqrt(loss))
I find the terminology for MLPs a little confusing. In the above code I think there are 3 hidden layers, not 1 input layer and 2 hidden layers. I think the ‘input layer’ is just the number of inputs to the first hidden layer, and is not actually a discrete entity of it’s own. I think.