Facts about future of LKR, using machine learning aspect

Ravindu Kavishwara
11 min readMar 26, 2021

--

Photo by Michael Longmire on Unsplash

On Thursday, the Sri Lankan rupee closed steady at 199.50/200.50 to the one-week US dollar, though yields remained unchanged in a dull market, dealers said. On Wednesday, the rupee closed at 199.50/200.50 to the US dollar in the one-week forward market. Bandula Gunewardene, Sri Lanka’s trade minister, told parliament this week that the rupee exchange rate is difficult to regulate at 200 levels, and that if allowed to rise, it might reach 300–350 levels based on past patterns. However, he did not give a timeline of 350 rupees.

So, looking at the situation, I decided to use a Time Series Model to predict the LKR rate for the next few months. Identifying trends in financial time series is complicated. This is why researchers are dragged into the mission, and new models are continuously created. Many of these new models in recent years have fallen into the category of machine learning models, in which an iterative algorithm is used to ‘read’ complex patterns without being directly programmed. This models have a large number of parameters, and mathematical proofs of their abilities are often constrained.

Time series forecasting

In the classical data analysis of time series information, making predictions about the future is referred to as extrapolation. Forecasting is the process of using models fit on historical data to forecast future observations. This is one of the most commonly used data analysis analyses, with applications in a broad range of sectors. This methodology will play a major role in assisting firms in identifying and predicting data trends and other events, and the findings will contribute to informed business decisions.

There are various approaches for evaluating time-ordered data points. One method is to enter the data into a spreadsheet and use the built-in functionality to construct a linear trendline and analyze the slope to determine the predicted transition. This is a good place to start because it creates a graph with a smooth line that gives you a general, visual understanding of where things are going. The simplistic linear trend line, on the other hand, attempts to group the data in a way that blends together or leaves out a lot of fascinating and significant information that appear in the individual data.

Creating a time series model in Python helps you to capture all of the data’s complexities and add all of the data elements that might be relevant. It also allows for modifications to various dimensions, theoretically improving the accuracy of the model.

Of course, the predictive power of a model is not really known until we get the actual data to compare it to.

Dataset

I downloaded the dataset for this analysis from here. This dataset includes the exchange rate on the first of each month. from the 1970s to the year 2021 I use a date range of 2015 January to 2021 January for this.

Import Dataset

Using the ‘pandas’ package, I cleaned up the dataset so that it is marginally cleaner than most real-world datasets. I looked for missing data and only included two columns: ‘Date’ and ‘Rate.’ Another critical phase is to consider the time frame. Finally, note to timestamp the data with time so that the rows are identified by a date rather than a regular integer. Since our data is monthly, the values in the first column will be in YYYY-MM-DD format and will appear the first of each month.

import pandas as pdimport numpy as npimport warningsimport matplotlib.pyplot as pltnp.set_printoptions(precision=3, suppress=True)import tensorflow as tffrom tensorflow.keras import layersfrom tensorflow.keras.layers.experimental import preprocessingdf = pd.read_csv("/content/dataset.csv")df['DATE'] = pd.to_datetime(df['DATE'])df = df.set_index('DATE')df.head()

Data Visualisation

The initial move is to actually map the dataset. In this case, I’m going to use the matplotlib package. Since the mean makes it easy to see a general pattern, I use both the original data (blue line) and the monthly average resample data (orange line).

y = df['EXSLUS']fig, ax = plt.subplots(figsize=(10, 6))ax.plot(y,marker='o', linestyle='-', linewidth=0.4, label='Monthly')ax.plot(y.resample('Y').mean(),marker='o', markersize=4, linestyle='-', label='Year')ax.set_ylabel('Rate')ax.legend();

Data Decomposing

Looking at the Rate data graph above, we can see a general growing trend with no specific seasonal or cyclical trends. The following move is to decompose the data in order to see some of the complexities hidden behind the linear visualization.

import statsmodels.api as smdef seasonal_decompose (y):decomposition = sm.tsa.seasonal_decompose(y, model='additive',extrapolate_trend='freq')fig = decomposition.plot()fig.set_size_inches(14,7)plt.show()seasonal_decompose(y)

The next move is to determine whether or not the dataset is stationary. If a dataset’s statistical properties, such as mean, variance, and autocorrelation, do not change over time, it is said to be stationary.

Visualisation

The rolling statistics (mean and variance) are graphed in this approach to demonstrate if the standard deviation varies significantly over time:

### plot for Rolling Statistic for testing Stationaritydef test_stationarity(timeseries, title):#Determing rolling statisticsrolmean = pd.Series(timeseries).rolling(window=12).mean()rolstd = pd.Series(timeseries).rolling(window=12).std()fig, ax = plt.subplots(figsize=(16, 4))ax.plot(timeseries, label= title)ax.plot(rolmean, label='rolling mean');ax.plot(rolstd, label='rolling std (x10)');ax.legend()
pd.options.display.float_format = '{:.8f}'.formattest_stationarity(y,'raw data')

We cannot assume with certainty that our data is stationary merely by looking at the graph above. As a result, we can do another stationarity test.

Augmented Dickey-Fuller Test

The ADF procedure is basically a statistical significance test in which the p-value is compared to the critical values and hypothesis testing is conducted. With varying degrees of trust, we can use this test to assess if the processed data is stationary or not.

from statsmodels.tsa.stattools import adfullerdef ADF_test(timeseries, dataDesc):print(' > Is the {} stationary ?'.format(dataDesc))dftest = adfuller(timeseries.dropna(), autolag='AIC')print('Test statistic = {:.3f}'.format(dftest[0]))print('P-value = {:.3f}'.format(dftest[1]))print('Critical values :')for k, v in dftest[4].items():print('\t{}: {} - The data is {} stationary with {}% confidence'.format(k, v, 'not' if v<dftest[0] else '', 100-int(k[:-1])))ADF_test(y,'raw data')

Make Data Stationary

we must first stationarize the dataset. There are various methods for stationarizing results, but we will use de-trending, differencing, and then a combination of the two.

Detrending

Detrending is the process of eliminating a pattern from a time series; a trend is typically defined as a shift in the mean over time.

y_detrend =  (y - y.rolling(window=12).mean())/y.rolling(window=12).std()test_stationarity(y_detrend,'de-trended data')ADF_test(y_detrend,'de-trended data')

After performing the ADF test again, the findings show that the data is now stationary, as shown by the relative smoothness of the rolling mean and rolling standard deviation.

Differencing

Differencing can help stabilize the mean of a time series by removing changes in its level, thus eliminating (or reducing) trend and seasonality.

y_12lag =  y - y.shift(12)test_stationarity(y_12lag,'12 lag differenced data')ADF_test(y_12lag,'12 lag differenced data')

Detrending & Differencing

y_12lag_detrend =  y_detrend - y_detrend.shift(12)test_stationarity(y_12lag_detrend,'12 lag differenced de-trended data')ADF_test(y_12lag_detrend,'12 lag differenced de-trended data')

We can see from both the visualization and the ADF test that the data is now stationary using the combination of the two techniques. This is the transition we will use in our research moving forward.

Create training Dataset & Testing Dataset

To prepare for evaluating the efficiency of the models you’re considering for time series analysis, divide the dataset into at least two sections. The ‘Training’ dataset will be one component, and the ‘Testing’ dataset will be the other. Sometimes we need to build a third dataset, or a ‘Validation’ dataset, that holds back some data for further testing.

y_to_train = y[:'2021-01-01'] # dataset to trainy_to_val = y['2020-07-01':] # last X months for testpredict_date = len(y) - len(y[:'2020-07-01']) # the number of data points for the test set

Time Series Prediction Models

We need to quantify the variations between expected values and real or observable values to help us assess the success of each forecasting model. I used the widely used estimation parameter root-mean-square error (RMSE), also known as root-mean-square deviation, for the model described below (RMSD). Such widely used measures include forecast error, mean absolute error (MAE), and mean absolute percentage error (MAPE) (MAPE). When testing several models, make sure to use the same efficiency metric for all of them.

In this scenario, I’ll use:

  • Simple Exponential Smoothing (SES) for data without trend or seasonality
  • Holt’s Linear Trend Method for data with a trend but no seasonality
  • SARIMA for data with trend and/or seasonality

Simple Exponential Smoothing

Exponential smoothing is a method for smoothing time series data that uses the exponential window function as a rule of thumb. Whereas the basic moving average weights past measurements similarly, exponential functions use exponentially diminishing weights over time.

import numpy as npfrom statsmodels.tsa.api import SimpleExpSmoothingdef ses(y, y_to_train,y_to_test,smoothing_level,predict_date):y.plot(marker='o', color='black', legend=True, figsize=(14, 7))fit1 = SimpleExpSmoothing(y_to_train).fit(smoothing_level=smoothing_level,optimized=False)fcast1 = fit1.forecast(predict_date).rename(r'$\alpha={}$'.format(smoothing_level))fcast1.plot(marker='.', color='blue', legend=True)fit1.fittedvalues.plot(marker='.',  color='blue')mse1 = ((fcast1 - y_to_test) ** 2).mean()print('The Root Mean Squared Error of our forecasts with smoothing level of {} is {}'.format(smoothing_level,round(np.sqrt(mse1), 2)))fit2 = SimpleExpSmoothing(y_to_train).fit()fcast2 = fit2.forecast(predict_date).rename(r'$\alpha=%s$'%fit2.model.params['smoothing_level'])fcast2.plot(marker='.', color='green', legend=True)fit2.fittedvalues.plot(marker='.', color='green')mse2 = ((fcast2 - y_to_test) ** 2).mean()print('The Root Mean Squared Error of our forecasts with auto optimization is {}'.format(round(np.sqrt(mse2), 2)))plt.show()
ses(y, y_to_train,y_to_val,0.1,predict_date)

The outcome visualization for the simple exponential smoothing (SES) forecast model shows the difference between the defined (blue line) and the auto-optimized (green line). Since the reasoning behind SES uses weighted averages, the graph shows that it can predict a smooth, forecasted axis.

Holt’s Linear Trend Method

where ℓt denotes an estimate of the level of the series at time t, bt denotes an estimate of the trend (slope) of the time series at time t, α is the smoothing parameter for the level, 0≤α≤1, and β∗ is the smoothing parameter for the trend, 0≤β≤1.

from statsmodels.tsa.api import Holtdef holt(y,y_to_train,y_to_test,smoothing_level,smoothing_slope, predict_date):y.plot(marker='.', color='black', legend=True, figsize=(14, 7))fit1 = Holt(y_to_train).fit(smoothing_level, smoothing_slope, optimized=False)fcast1 = fit1.forecast(predict_date).rename("Holt's linear trend")mse1 = ((fcast1 - y_to_test) ** 2).mean()print('The Root Mean Squared Error of Holt''s Linear trend {}'.format(round(np.sqrt(mse1), 2)))fit2 = Holt(y_to_train, exponential=True).fit(smoothing_level, smoothing_slope, optimized=False)fcast2 = fit2.forecast(predict_date).rename("Exponential trend")mse2 = ((fcast2 - y_to_test) ** 2).mean()print('The Root Mean Squared Error of Holt''s Exponential trend {}'.format(round(np.sqrt(mse2), 2)))fit1.fittedvalues.plot(marker=".", color='blue')fcast1.plot(color='blue', marker=".", legend=True)fit2.fittedvalues.plot(marker=".", color='red')fcast2.plot(color='red', marker=".", legend=True)plt.show()holt(y, y_to_train,y_to_val,0.3,0.1,predict_date)

In the Holt method visualization, we can see how the linear trend (blue line) and exponential trend (red line) correspond to each other and to the order volumes. In comparison to SES, Holt catches much of the data’s pattern.

SARIMA

An autoregressive integrated moving average model is a generalization of an autoregressive moving average model in statistics and econometrics, especially in time series analysis. All of these equations was applied to time series data in order to help better interpret the data or forecast future points in the series.

You’ll notice that SARIMA includes several parameters that can be tuned to achieve optimal performance. You can learn more about these parameters here. They are:

Trend Elements:

  • p: Trend autoregression order.
  • d: Trend difference order.
  • q: Trend moving average order.

Seasonal Elements:

  • P: Seasonal autoregressive order.
  • D: Seasonal difference order.
  • Q: Seasonal moving average order.
  • m: The number of time steps for a single seasonal period.

To obtain the best prediction, it is necessary to identify the SARIMA(p,d,q)(P,D,Q)m values that optimize a metric of interest. For the purposes of this brief blog post, we can merely use a “grid scan” to iteratively explore various parameter combinations.

import itertoolsdef sarima_grid_search(y,seasonal_period):p = d = q = range(0, 2)pdq = list(itertools.product(p, d, q))seasonal_pdq = [(x[0], x[1], x[2],seasonal_period) for x in list(itertools.product(p, d, q))]mini = float('+inf')
for param in pdq:for param_seasonal in seasonal_pdq:try:mod = sm.tsa.statespace.SARIMAX(y,order=param,seasonal_order=param_seasonal,enforce_stationarity=False,enforce_invertibility=False)results = mod.fit()if results.aic < mini:mini = results.aicparam_mini = paramparam_seasonal_mini = param_seasonalexcept:continueprint('The set of parameters with the minimum AIC is: SARIMA{}x{} - AIC:{}'.format(param_mini, param_seasonal_mini, mini))sarima_grid_search(y,12)

The grid quest checked all possible variable combinations and printed out the set with the lowest AIC value, and we can see that SARIMA(1, 1, 0)x(1, 1, 0, 12) has the lowest AIC value.

def sarima_eva(y,order,seasonal_order,seasonal_period,pred_date,y_to_test):mod = sm.tsa.statespace.SARIMAX(y,order=order,seasonal_order=seasonal_order,enforce_stationarity=False,enforce_invertibility=False)results = mod.fit()print(results.summary().tables[1])results.plot_diagnostics(figsize=(16, 8))plt.show()pred = results.get_prediction(start=pd.to_datetime(pred_date), dynamic=False)pred_ci = pred.conf_int()y_forecasted = pred.predicted_meanmse = ((y_forecasted - y_to_test) ** 2).mean()print('The Root Mean Squared Error of SARIMA with season_length={} and dynamic = False {}'.format(seasonal_period,round(np.sqrt(mse), 2)))ax = y.plot(label='observed')y_forecasted.plot(ax=ax, label='One-step ahead Forecast', alpha=.7, figsize=(14, 7))ax.fill_between(pred_ci.index,pred_ci.iloc[:, 0],pred_ci.iloc[:, 1], color='k', alpha=.2)ax.set_xlabel('Date')ax.set_ylabel('Sessions')plt.legend()plt.show()pred_dynamic = results.get_prediction(start=pd.to_datetime(pred_date), dynamic=True, full_results=True)pred_dynamic_ci = pred_dynamic.conf_int()y_forecasted_dynamic = pred_dynamic.predicted_meanmse_dynamic = ((y_forecasted_dynamic - y_to_test) ** 2).mean()print('The Root Mean Squared Error of SARIMA with season_length={} and dynamic = True {}'.format(seasonal_period,round(np.sqrt(mse_dynamic), 2)))ax = y.plot(label='observed')y_forecasted_dynamic.plot(label='Dynamic Forecast', ax=ax,figsize=(14, 7))ax.fill_between(pred_dynamic_ci.index,pred_dynamic_ci.iloc[:, 0],pred_dynamic_ci.iloc[:, 1], color='k', alpha=.2)ax.set_xlabel('Date')ax.set_ylabel('Sessions')plt.legend()plt.show()return (results)
sarima_eva(y,(1, 1, 0),(1, 1, 0, 12),12,'2020-05-01',y_to_val)

Here are the SARIMA method visualizations. We can confidently state that the SARIMA model better captures both the seasonality and pattern of our dataset as compared to the performance of all previous models.

The Root Mean Squared Error of SARIMA with season_length=12 and dynamic = False 2.55
The Root Mean Squared Error of SARIMA with season_length=12 and dynamic = True 21.95

Making Predictions

We enter steps=20 to get the rate forecast for the next few months. The output includes a table with the Predicted Mean, Lower Bound, and Upper Bound, as well as projection diagrams.

def forecast(model,predict_steps,y):pred_uc = model.get_forecast(steps=predict_steps)pred_ci = pred_uc.conf_int()ax = y.plot(label='observed', figsize=(14, 7))pred_uc.predicted_mean.plot(ax=ax, label='Forecast')ax.fill_between(pred_ci.index,pred_ci.iloc[:, 0],pred_ci.iloc[:, 1], color='k', alpha=.25)ax.set_xlabel('Date')ax.set_ylabel(y.name)plt.legend()plt.show()pm = pred_uc.predicted_mean.reset_index()pm.columns = ['Date','Predicted_Mean']pci = pred_ci.reset_index()pci.columns = ['Date','Lower Bound','Upper Bound']final_table = pm.join(pci.set_index('Date'), on='Date')return (final_table)
final_table = forecast(model,20,y)final_table.head()

The green line in the graph reflects the anticipated future data based on the forecasting model I developed. The green line reflects the average forecasted value for each week, and we wouldn’t be surprised if the real figures largely tracked this line. However, there is no guarantee of this!

The gray area above and below the green line reflects the 95 percent confidence interval, and as in almost all forecasting formulas, the further into the future the forecasts, the less confidence we have in our beliefs. In this scenario, we are 95% certain that Rates will be within this amount. However, there is a risk that the actuals will fall entirely outside of this range as well. The greater the potential time frame over which we want to estimate, the greater this confidence range (that is, the less precise our forecast is).

Project link — https://colab.research.google.com/drive/13wgwIvBPnyc-YiPSDTtk0sL5y2yowT7H?usp=sharing

Github — https://github.com/A1r33s-thewarman/LKR-usd_machine_learning

--

--

Ravindu Kavishwara
Ravindu Kavishwara

Written by Ravindu Kavishwara

my daily ritual is pushing my limits by learning to see things from a different perspective.

No responses yet