LSTM model prediction is almost constant I am new to RNN and LSTM and currently experimenting with different settings.
When trying to model time series data in absolute terms (predicted close price), I am faced with the following problems:

Validation loss falling while training loss raising
Prediction on test set is almost constant

Any ideas why is it the case?
#df is the sample with features and target values (close+1)

# MAKE LARGE SAMPLE FOR TESTING
df_train = df[:40000]
df_val = df[40000:50000]
df_test = df[50000:]
df_train.reset_index(drop=True,inplace=True)
df_val.reset_index(drop=True,inplace=True)
df_test.reset_index(drop=True,inplace=True)

# Make x_train, x_val sets by dropping target variable
x_train = df_train.drop(['close+1'], axis=1)
x_val = df_val.drop(['close+1'], axis=1)

# Scale the training data first then fit the transform to the test set
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_val)

# Create y_train, y_test, simply target variable for regression
y_train = df_train['close+1']
y_test = df_val['close+1']

# Define Lookback window for LSTM input
sliding_window = 30

# Convert x_train, x_test, y_train, y_test into 3d array (samples, timesteps, features) for LSTM input
dataXtrain = []
for i in range(len(x_train)-sliding_window-1):
        a = x_train[i:(i+sliding_window), 0:(x_train.shape[1])]
        dataXtrain.append(a)

dataXtest = []
for i in range(len(x_test)-sliding_window-1):
        a = x_test[i:(i+sliding_window), 0:(x_test.shape[1])]
        dataXtest.append(a)

dataYtrain = []
for i in range(len(y_train)-sliding_window-1):
        dataYtrain.append(y_train[i + sliding_window])

dataYtest = []
for i in range(len(y_test)-sliding_window-1):
        dataYtest.append(y_test[i + sliding_window])


# Make data the divisible by a variety of batch_sizes for training
dataXtrain = np.array(dataXtrain[:39680])
dataYtrain = np.array(dataYtrain[:39680])
dataXtest = np.array(dataXtest[:9728])
dataYtest = np.array(dataYtest[:9728])

# Checking input shapes
print('dataXtrain size is: {}'.format((dataXtrain).shape))
print('dataXtest size is: {}'.format((dataXtest).shape))
print('dataYtrain size is: {}'.format((dataYtrain).shape))
print('dataYtest size is: {}'.format((dataYtest).shape))


### ACTUAL LSTM MODEL

batch_size = 256
timesteps = dataXtrain.shape[1]
features = dataXtrain.shape[2]

# Model set-up, stacked 4 layer stateful LSTM
model = Sequential()
model.add(LSTM(512, return_sequences=True, stateful=True,
               batch_input_shape=(batch_size, timesteps, features)))
model.add(LSTM(256,stateful=True, return_sequences=True))
model.add(LSTM(256,stateful=True, return_sequences=True))
model.add(LSTM(128,stateful=True))
model.add(Dense(1, activation='linear'))

model.summary()

reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.9, patience=5, min_lr=0.000001, verbose=1)

def coeff_determination(y_true, y_pred):
    from keras import backend as K
    SS_res =  K.sum(K.square( y_true-y_pred ))
    SS_tot = K.sum(K.square( y_true - K.mean(y_true) ) )
    return ( 1 - SS_res/(SS_tot + K.epsilon()) )

model.compile(loss='mse',
              optimizer='nadam',
              metrics=[coeff_determination,'mse','mae','mape'])

history = model.fit(dataXtrain, dataYtrain,validation_data=(dataXtest, dataYtest),
          epochs=100,batch_size=batch_size, shuffle=False, verbose=1, callbacks=[reduce_lr])

predictions = model.predict(dataXtest, batch_size=batch_size)
print(predictions)

plt.plot(history.history[&quot;loss&quot;][5:])
plt.plot(history.history[&quot;val_loss&quot;][5:])
plt.title(&quot;model loss&quot;)
plt.ylabel(&quot;loss&quot;)
plt.xlabel(&quot;epoch&quot;)
plt.legend([&quot;train&quot;, &quot;val&quot;], loc=&quot;best&quot;)
plt.show()

plt.figure(figsize=(20,8))
plt.plot(dataYtest)
plt.plot(predictions)
plt.title(&quot;Prediction&quot;)
plt.ylabel(&quot;Price&quot;)
plt.xlabel(&quot;Time&quot;)
plt.legend([&quot;Truth&quot;, &quot;Prediction&quot;], loc=&quot;best&quot;)
plt.show()
It could be a number of different reasons but when I had that problem in the past it was usually due to too high of a learning rate or the optimizer. I recommended either dropping the initial learning rate or going with vanilla SGD. Occasionally I saw problems with Adam particularly if you have no warmup. You might want to try more general hyperparameter search techniques as well and iterate over things like batch size, forecast length, and lr etc