Sitemap
Analytics Vidhya

Analytics Vidhya is a community of Generative AI and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Use Oscillator of RSI to predict Stock Return (accuracy over 70%!)

--

Just learned the Oscillator of Moving average (OsMA) and RSI from the post. RSI is an useful indicator of trend and momentum. Inspired by that, I want to try to use that as the machine learning feature to predict the future stock return.

Calculation of RSI and its OsMA

Relative Strength (RS) and Relative Strength Indicator (RSI) can be computed from the Moving Average (MA) of the historical positive price difference (%) divided by that of negative price difference (%).

RS = (MA of positive difference) / (MA of negative difference)

RSI = 100–100/(1+RS)

Here is the code of Julia of the computation. We use the Julia package TimeSeries.

  • percentchange(price_ts) is the function to compute the price difference in % from the lag-1 price.
  • Time Series “up” and “down” are the positive price difference (%) and negative price difference (%) generated by setting the opposite sign value 0.
  • up_roll and down_roll is the moving average of the positive difference and negative difference for the period “loopback”.
  • f_rsi is the function to apply on each element of the time series to calculate the RSI = 100–(100/1+up_roll/down_roll)
  • TimeSeries.map is used to apply the function f_rsi on each element of the time series. It is common to use map function instead of loop for time series operations.
using TimeSeries
function toRSI(price_ts,loopback)
pct = percentchange(price_ts)
upidx = findall(pct["Close"] .> 0)
downidx = findall(pct["Close"] .< 0)
up = map( (timestamp, values) -> (timestamp, if values < 0 ; 0 ; else ;values ;end), pct)
down = map( (timestamp, values) -> (timestamp, if values < 0 ; abs(values) ; else ;0 ;end), pct)
up_roll = moving(mean, up, loopback)
down_roll = moving(mean, down, loopback)
function f_rsi(values)
x = values[1]
y = values[2]
rsi = 100 - (100 / (1 + x/y) )
[rsi,0]
end
updown = TimeSeries.rename(TimeSeries.merge(up_roll,down_roll), [:up,:down])
rsi_ts = TimeSeries.rename(TimeSeries.map((timestamp, values) -> (timestamp, f_rsi(values)), updown)[:up],Symbol("RSI-",loopback))
return rsi_ts
end

Moving Average of the RSI can be easily be computed by the function “moving(mean, …)”.

rsi_ma = TimeSeries.rename(moving(mean,rsi_ts,j),Symbol("RSIMA-",i,"-",j))

Feature Engineering

The Oscillator of the Moving average of RSI (OsRSI) can be used as the model feature. For each OsRSI calculation, there are 2 moving averages: (1) the MA in the calcation of up_roll and down_roll, and (2) the MA of the RSI. For each MA we need to specify the loopback period, let it be i and j, and defined as below:

rsi_ts = toRSI(price_ts,i)
rsi_ma = moving(mean,rsi_ts,j)
orsi = rsi_ts .- rsi_ma

With the leverage of the computer automation, we can loop over the value of i and j over a range instead of just base on a single value of the loopback in the analysis.

for i in day0:day1
rsi_ts = toRSI(price_ts,i)
result_rsi = merge(result_rsi,rsi_ts,method=:inner)
for j in day0:day1
rsi_ma = TimeSeries.rename(moving(mean,rsi_ts,j),Symbol("RSIMA-",i,"-",j))
orsi = TimeSeries.rename(rsi_ts .- rsi_ma , Symbol("ORSI-",i,"-",j) )
result_orsi = merge(result_orsi,orsi,method=:inner)
end
end

All these OsRMI(i,j) will form a Matrix. That Matrix can be feed into the Convolution Neural Network (CNN) for pattern recognition. I use the OsRSI Matrix of the current day and the previous (seqlen) days to form a sequence of Matrix as the input of the CNN layers. Then, stack over of 2-GRU, and the final Dense layer to get the predicted rate of return of the next day.

function build_reg_model(Nh,seqlen)
a = floor(Int8,Nh)
return Chain(
x -> Flux.unsqueeze(x,4),
# First convolution
Conv((2, 2), seqlen=>a, pad=(1,1), relu),
MaxPool((2,2)),
# Second convolution
Conv((2, 2), a=>Nh, pad=(1,1), relu),
MaxPool((2,2)),
# Third convolution
Conv((2, 2), Nh=>Nh, pad=(1,1), relu),
MaxPool((2,2)),
Flux.flatten,
Dropout(0.1),
(x->transpose(x)),
GRU(1,Nh),
GRU(Nh,Nh),
(x -> x[:,end]),
Dense(Nh, 1),
(x -> x[1])
)
end

Similar to my previous post, I use the 5-day moving average of the rate of return of the close price as the target variable in order to smoothen the flucuation.

function toReturn(price_ts)
pct = TimeSeries.rename(percentchange(price_ts),[:return])
pct = moving(mean,pct,5)
return TimeSeries.map((timestamp, values) -> (timestamp, 100*values), pct)
end

Result Evaluation

I use 3-year data of 2840.HK for training and testing. I hold out the last 150 unseen data points for testing. The number of epoch for training is 25.

@epochs num_epoch Flux.train!(mse_loss,Flux.params(m),train_loader,RMSProp(lr,mm))

Here is the plot of the predicted value in red line (y1), and the actual value in blue line (y2).

Press enter or click to view image in full size

In the actual trading, the accuracy of the prediction of up or down (positive/negative return) is more critical in P/L than the actual fitting of the value. I also calculate the accuracy and the confusion matrix. The accuracy is 77%! which is better than my previous models.

accuracy:0.77Confusion Matrix:
[0.27, 0.093]
[0.13, 0.5]

Here is the hyper-parameters of the above test run:

seqlen=8
Nh=20
lr=0.000129
mm=0.75
day0=8
day1=8+20

In conclusion, OsRSI is a good feature in stock prediction. The full Julia notebook can be found in my Github.

--

--

Analytics Vidhya
Analytics Vidhya

Published in Analytics Vidhya

Analytics Vidhya is a community of Generative AI and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Responses (2)