├── README.md ├── Feature extractors ├── normalization_functions.py └── feature_extractior_functions.py ├── Adaptive filters └── lms.py └── simple_forecast.py /README.md: -------------------------------------------------------------------------------- 1 | # Adaptive-forex-forecast 2 | An adaptive model for prediction of one day ahead foreign currency exchange rates using machine learning algorithms 3 | -------------------------------------------------------------------------------- /Feature extractors/normalization_functions.py: -------------------------------------------------------------------------------- 1 | # This script contains functions that will be helpful for normalizing dataset 2 | 3 | # There are numerous techniques available for normalizing the data in statistics, however I will be implementing 4 | # basic ones for now, later on I will implement other normalization techniques in this script. 5 | 6 | # Simplest normalization technique: Divide each value in the dataset with the maximum value in the dataset. 7 | # TODO: Other normalization technique: (X-mu)/sigma 8 | 9 | def simple_normalize(data): 10 | ''' 11 | Simplest normalization function 12 | 13 | This function takes in a list, finds the maximum element in the list and divides all the elements of the list by 14 | maximum element, and returns the list with maximum element appended to the end of list. As a result, the data is 15 | normalized between 0 and 1. 16 | 17 | :param data: (list) containing features to be normalized 18 | :return: (list) normalized features + maximum value 19 | ''' 20 | max_elem = max(data) 21 | normalized_data = [i / max_elem for i in data] 22 | normalized_data.append(max_elem) 23 | return normalized_data 24 | 25 | -------------------------------------------------------------------------------- /Feature extractors/feature_extractior_functions.py: -------------------------------------------------------------------------------- 1 | # This script contains the functions that will be helpful for extracting features from the dataset 2 | 3 | # Import necessary libraries 4 | 5 | import math 6 | 7 | 8 | # ===================================================================================================================== 9 | # ===== Feature extractor functions =================================================================================== 10 | # ===================================================================================================================== 11 | 12 | def simple_amv_extractor(window_size, data): 13 | ''' 14 | A simple feature extraction function that extracts actual value, mean value, and variance 15 | 16 | This function takes in a list and a window size, and starting from the first member of the list, it calculates the 17 | actual value 'a', mean of the window 'm', and variance of the window 'v', and the target value 't' and then the 18 | window slides, calculation is done again, and so on till the end of the list is reached, and a feature table is 19 | generated. 20 | Illustrative explanation of function working: 21 | eg. dataset = [x1, x2, x3, x4, x5, x6, x7], window size = 4 22 | #1 Iteration: a = x4, m = (x1+x2+x3+x4)/4, v = sqrt(((x1-x2)^2)+((x1-x3)^2)+((x1-x4)^2)), t = x5 23 | #2 Iteration: a = x5, m = (x2+x3+x4+x5)/4, v = sqrt(((x2-x3)^2)+((x2-x4)^2)+((x2-x5)^2)), t = x6 24 | #3 Iteration: a = x6, m = (x3+x4+x5+x6)/4, v = sqrt(((x3-x4)^2)+((x3-x5)^2)+((x3-x6)^2)), t = x7 25 | 26 | :param window_size: (int) # of elements in a window 27 | :param data: (list) Input data, for forex prediction - exchange rates 28 | :return: (list) containing features - a, m, v, t 29 | ''' 30 | 31 | # list to store features 32 | feature_table = [] 33 | # looping through the dataset to extract features 34 | for i in range(len(data) - window_size): 35 | 36 | feature = [] 37 | a = data[i + window_size - 1] 38 | m = 0 39 | v = 0 40 | t = data[i + window_size] 41 | 42 | for j in range(window_size): 43 | 44 | d = data[i + j] 45 | m = m + d 46 | if (j > 0): 47 | v = v + pow((data[i] - d), 2) 48 | 49 | m = m / window_size 50 | v = math.sqrt(v) 51 | 52 | feature.append(a) 53 | feature.append(m) 54 | feature.append(v) 55 | feature.append(t) 56 | 57 | feature_table.append(feature) 58 | 59 | return feature_table 60 | -------------------------------------------------------------------------------- /Adaptive filters/lms.py: -------------------------------------------------------------------------------- 1 | # LMS algorithm belongs to the class of adaptive filters. 2 | # The basic goal of the LMS algorithm is to find the optimum weights so as the solution converges to optimum result. 3 | # My forex prediction model will be using this algorithm in order to optimize its weights and make best prediction. 4 | 5 | # The LMS algorithm can be represented mathematically in one equation: 6 | # W[k+1] = W[k] + 2*mu*e(k)*X[k] 7 | 8 | # ===================================================================================================================== 9 | # ===== Helper functions ============================================================================================== 10 | # ===================================================================================================================== 11 | 12 | def add_lists(a, b): 13 | ''' 14 | This function performs element wise addition of two lists a and b, a naive implementation of vector addition. 15 | 16 | :param a: (list) vector a 17 | :param b: (list) vector b 18 | :return: (list) vector (a+b) 19 | ''' 20 | if len(a) == len(b): 21 | return [i + j for i, j in zip(a, b)] 22 | else: 23 | raise ValueError('Incompatible list widths') 24 | 25 | 26 | def sub_lists(a, b): 27 | ''' 28 | This function performs element wise subtraction of two lists a and b, a naive implementation of vector subtraction. 29 | 30 | :param a: (list) vector a 31 | :param b: (list) vector b 32 | :return: (list) vector(a-b) 33 | ''' 34 | if len(a) == len(b): 35 | return [i + j for i, j in zip(a, b)] 36 | else: 37 | raise ValueError('Incompatible list widths') 38 | 39 | 40 | def scalar_mul(a, b): 41 | ''' 42 | This function performs scalar multiplication of a with vector b. 43 | 44 | :param a: (float) scalar a 45 | :param b: (list) vector b 46 | :return: (list) scalar multiplication of a with b 47 | ''' 48 | return [a * i for i in b] 49 | 50 | 51 | # ===================================================================================================================== 52 | # ===== LMS Algorithm ================================================================================================= 53 | # ===================================================================================================================== 54 | 55 | def lms(current_weights, mu, current_input, current_error): 56 | ''' 57 | A naive implementation of lms algorithm which calculates new weights based on input parameters. 58 | 59 | This function computes new weights W[k+1] using the equation: 60 | W[k+1] = W[k] + 2*mu*e(k)*X[k] 61 | 62 | :param current_weights: (list) W[k] in the above equation, current weights 63 | :param mu: (float) learning parameter, typically 0 < mu < 1 64 | :param current_input: (float) X[k] in the above equation, current input 65 | :param current_error: (float) e(k) in the above equation, current error 66 | :return: (list) updated weights, W[k+1] in the above equation 67 | ''' 68 | delta_weight = scalar_mul((2 * mu * current_error), current_input) 69 | new_weights = add_lists(current_weights, delta_weight) 70 | return new_weights 71 | -------------------------------------------------------------------------------- /simple_forecast.py: -------------------------------------------------------------------------------- 1 | # import necessary libraries 2 | 3 | import sys 4 | 5 | import matplotlib.pyplot as plt 6 | import pandas as pd 7 | 8 | # import necessary modules 9 | 10 | sys.path.append("/home/excviral/Pycharm/PycharmProjects/Adaptive-forex-forecast/Adaptive filters/") 11 | sys.path.append("/home/excviral/Pycharm/PycharmProjects/Adaptive-forex-forecast/Feature extractors/") 12 | 13 | from lms import lms 14 | from feature_extractior_functions import simple_amv_extractor 15 | from normalization_functions import simple_normalize 16 | 17 | # import dataset 18 | dataset = pd.read_csv('data.csv') 19 | data = list(dataset['Price'].values) 20 | 21 | # ===================================================================================================================== 22 | # ===== Helper functions ============================================================================================== 23 | # ===================================================================================================================== 24 | 25 | def predict(inputs, weights): 26 | ''' 27 | Model to predict the exchange rate based on inputs and weights 28 | 29 | This function takes in features as input, in this case the features are actual value, mean value, and variance, and 30 | then the features are multiplied with its respective weight and added up to compute the predicted value. 31 | Mathematically: y_k = a*w0 + m*w1 + v*w2 32 | 33 | :param inputs: (list) containing features of the dataset 34 | :param weights: (list) containing weights for each feature of the dataset 35 | :return: (float) predicted outcome based on input and weights 36 | ''' 37 | return sum([i * j for i, j in zip(inputs, weights)]) 38 | 39 | 40 | def plot_convergence_characteristics(errors): 41 | ''' 42 | Function to plot the convergence characteristics viz. plot of (error)^2 v/s pattern number 43 | 44 | Convergence characteristics shows the rate of convergence of the error of prediction v/s actual value, to 0. 45 | 46 | :param errors: (list) containing errors corresponding to each pattern 47 | :return: none 48 | ''' 49 | errors_squared = [i * i for i in errors] 50 | plt.plot(errors_squared) 51 | plt.xlabel('Pattern number') 52 | plt.ylabel('(Error)^2') 53 | plt.show() 54 | 55 | 56 | # ===================================================================================================================== 57 | # ===== Forecast Algorithm ============================================================================================ 58 | # ===================================================================================================================== 59 | 60 | # first we normalize the data, we use simple normalization technique of dividing each value in data by max value of data 61 | n_data = simple_normalize(data) 62 | 63 | # define window size 64 | window_size = 10 65 | 66 | # extract the feature-patterns (a,m,v,tv) from the data, and store it in a list 67 | feature_table = simple_amv_extractor(window_size, n_data[:(len(n_data) - 1)]) 68 | 69 | # We will be using 80% of the feature-patterns from the feature table for training the model, and the remaining 20% of 70 | # the feature-patters will be used for testing the model 71 | 72 | # separating training data and test data and storing them in respective lists for later use 73 | training_data = feature_table[:int(len(feature_table) * 0.8)] 74 | testing_data = feature_table[int(len(feature_table) * 0.8):] 75 | 76 | 77 | # Now that the data is ready, we train our model to compute optimum weights 78 | 79 | # Model trainer 80 | def train_model(training_data, mu): 81 | ''' 82 | This function trains the prediction model to find the optimum weights for which prediction error is minimum 83 | 84 | Algorithm: Initially the model starts with weights = [0,0,0], the model then predicts some value, then it computes 85 | error vs the actual value/desired value and adjusts the weights accordingly. This is repeated until all patterns in 86 | the training set are exhausted. At the end, the error should have converged to zero, this can be seen in the 87 | convergence characteristics plot generated at the end of training. 88 | NOTE: The convergence of error is highly dependent on the choice of 'mu' - the convergence coefficient. 89 | Theoretically, its value should lie between 0 and 1, when mu is closer towards zero learning rate will be slow, 90 | but accuracy will be more, when it is closer to 1, learning rate will be faster, but accuracy will be poor. 91 | 92 | :param training_data: (list) containing features selected to train the model 93 | :param mu: (float) convergence coeffecient 94 | :return: (list) containing optimized weights, which can be used for prediction 95 | ''' 96 | 97 | # This list will store weights, initially the weights will be zeros 98 | weights = [0, 0, 0] 99 | # This list will store errors corresponding to each pattern 100 | errors = [] 101 | 102 | # This loop optimizes the weights, such that error converges to zero 103 | for i in training_data: 104 | # Inputs to the predictor model [a, m, v] 105 | x_k = i[:len(i) - 1] 106 | # Desired value or target value, that is to be predicted 107 | d_k = i[len(i) - 1] 108 | # Predict the output price based on the input and current weights 109 | y_k = predict(x_k, weights) 110 | # Compare the predicted price to the desired price, and compute the error 111 | e_k = d_k - y_k 112 | # Store the error to the list 113 | errors.append(e_k) 114 | # Compute new weights based on the previous weights, mu, previous input, previous error using lms algorithm 115 | weights = lms(weights, mu, x_k, e_k) 116 | 117 | plot_convergence_characteristics(errors) 118 | return weights 119 | 120 | 121 | weights = train_model(training_data, 0.000195) 122 | 123 | 124 | # Now that we have computed the optimum weights, we test it against the feature-patterns that we had stored earlier 125 | 126 | # start testing 127 | def test_model(testing_data, weights): 128 | ''' 129 | This function tests the performance of the model so that we can know the accuracy of the model 130 | 131 | :param testing_data: (list) containing features that are to be tested 132 | :param weights: (list) containing optimum weights generated during training 133 | :return: none 134 | ''' 135 | 136 | # Lists to store errors, desired/target value, and predicted value for each test-pattern 137 | errors = [] 138 | desired = [] 139 | predicted = [] 140 | 141 | # This loop predicts a value for each pattern, computes error against desired value and stores them in lists above 142 | for i in testing_data: 143 | # Inputs to the predictor model 144 | x_k = i[:len(i) - 1] 145 | # Desired value or target value, that is to be predicted 146 | d_k = i[len(i) - 1] 147 | # Predict the output price based on the input and optimum weights generated during training 148 | y_k = predict(x_k, weights) 149 | # Store the predicted value and desired value to the respectivs lists 150 | predicted.append(y_k) 151 | desired.append(d_k) 152 | # Compute the error of prediction against the desired value 153 | e_k = d_k - y_k 154 | # Store the error to the list 155 | errors.append(e_k) 156 | 157 | # Plot the predicted and desired values to compare the error 158 | plt.plot(desired, 'g-', label="Desired Values") 159 | plt.plot(predicted, 'r-', label="Predicted Values") 160 | plt.xlabel('Pattern number') 161 | plt.ylabel('Normalized Exchange rate') 162 | plt.legend(loc='best') 163 | plt.show() 164 | 165 | 166 | test_model(testing_data, weights) 167 | 168 | # To predict exchange rate against a new feature after training, simply plug the features into 'predict' function 169 | --------------------------------------------------------------------------------