From 24276c1ec5459f9047bee99da424e3b27db7498b Mon Sep 17 00:00:00 2001
From: Ammar Siddiqui <ammarsiddiqui@Ammars-MacBook-Pro.local>
Date: Sat, 20 May 2023 16:52:33 -0400
Subject: [PATCH 1/4] Added new ml module folder and time series file

---
 quantpy/ml/__init__.py    |  0
 quantpy/ml/time_series.py | 15 +++++++++++++++
 2 files changed, 15 insertions(+)
 create mode 100644 quantpy/ml/__init__.py
 create mode 100644 quantpy/ml/time_series.py

diff --git a/quantpy/ml/__init__.py b/quantpy/ml/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/quantpy/ml/time_series.py b/quantpy/ml/time_series.py
new file mode 100644
index 0000000..188c27c
--- /dev/null
+++ b/quantpy/ml/time_series.py
@@ -0,0 +1,15 @@
+'''Models.'''
+
+'''
+Ideas:
+    Regression/Time-series:
+        - ARIMA
+        - ARFIMA
+        - Seasonal Models (SARIMA, Winters)
+        - Spectral Analysis
+        - 
+'''
+
+import pandas as pd
+from statsmodels.tsa.arima.model import ARIMA
+

From 50bd233b1a507b271df2b5d93c9a99ba03addffd Mon Sep 17 00:00:00 2001
From: Ammar Siddiqui <ammarsiddiqui@Ammars-MacBook-Pro.local>
Date: Mon, 22 May 2023 18:45:46 -0400
Subject: [PATCH 2/4] added new time series regression model

---
 quantpy/ml/models.py      | 45 +++++++++++++++++++++++++++++++++++++++
 quantpy/ml/time_series.py | 11 ++++++++++
 2 files changed, 56 insertions(+)
 create mode 100644 quantpy/ml/models.py

diff --git a/quantpy/ml/models.py b/quantpy/ml/models.py
new file mode 100644
index 0000000..9eaace8
--- /dev/null
+++ b/quantpy/ml/models.py
@@ -0,0 +1,45 @@
+import numpy as np
+from pandas import DataFrame, Series
+from typing import Union, Optional, Tuple, List
+from sklearn.base import RegressorMixin
+from statsmodels.base.models import Model as StatsModel
+from statsmodels.tsa.arima.model import ARIMA
+
+class BenchmarkRegressor():
+    '''
+    Default Regression Model based on ARIMA (Auto-Regressive Integrated Moving 
+    Averages). Aims to provide a basic compute-efficient time-series model to
+    make predictions. (Aim to beat this model's prediction accuracy)
+    '''
+    def __init__(
+            self,
+            data: Union[DataFrame, Series],
+            param: Tuple[int, int, int] = (5, 2, 0)
+        ) -> None:
+
+        self.model = ARIMA(data, order=param)
+        self.model_fit = self.model.fit(data)
+        self.residuals = DataFrame(self.model_fit.resid)
+
+
+    def describe_residuals(self) -> DataFrame:
+        return self.residuals.describe()
+    
+
+    def plot_residuals(self) -> None:
+        self.residuals().plot()
+
+
+    def cross_validate(
+            self, 
+            comparision_model: Optional[Union[
+                    List,
+                    RegressorMixin,
+                    StatsModel
+                ]]
+        ) -> None:
+        ...
+
+
+    def predict(self) -> np.float64:
+        ...
diff --git a/quantpy/ml/time_series.py b/quantpy/ml/time_series.py
index 188c27c..bb7b310 100644
--- a/quantpy/ml/time_series.py
+++ b/quantpy/ml/time_series.py
@@ -8,8 +8,19 @@
         - Seasonal Models (SARIMA, Winters)
         - Spectral Analysis
         - 
+
+        - Brownian Motion
+        - Martingale + Random Walk
+        - Monte-Carlo
+        - Stochastic Calculus for Finance (Discrete and Continuous)
+        - Implied Volatility
+        - Black Scholes
+        - Merton
+        - Markov
+        - 
 '''
 
 import pandas as pd
 from statsmodels.tsa.arima.model import ARIMA
 
+

From a751c37ac942953a0ff75dd6e5b4ffd159506658 Mon Sep 17 00:00:00 2001
From: Ammar Siddiqui <ammarsiddiqui@Ammars-MacBook-Pro.local>
Date: Thu, 25 May 2023 19:15:45 -0400
Subject: [PATCH 3/4] added garch model and exceptions

---
 quantpy/ml/exceptions.py  |   8 ++
 quantpy/ml/models.py      |  45 ----------
 quantpy/ml/time_series.py | 167 ++++++++++++++++++++++++++++++++------
 setup.py                  |   4 +
 4 files changed, 156 insertions(+), 68 deletions(-)
 create mode 100644 quantpy/ml/exceptions.py
 delete mode 100644 quantpy/ml/models.py

diff --git a/quantpy/ml/exceptions.py b/quantpy/ml/exceptions.py
new file mode 100644
index 0000000..55e66eb
--- /dev/null
+++ b/quantpy/ml/exceptions.py
@@ -0,0 +1,8 @@
+class ModelNotFittedError(Exception):
+    ...
+
+class ModelTypeNotSupportedError(Exception):
+    ...
+
+class DataTypeNotSupportedError(Exception):
+    ...
\ No newline at end of file
diff --git a/quantpy/ml/models.py b/quantpy/ml/models.py
deleted file mode 100644
index 9eaace8..0000000
--- a/quantpy/ml/models.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import numpy as np
-from pandas import DataFrame, Series
-from typing import Union, Optional, Tuple, List
-from sklearn.base import RegressorMixin
-from statsmodels.base.models import Model as StatsModel
-from statsmodels.tsa.arima.model import ARIMA
-
-class BenchmarkRegressor():
-    '''
-    Default Regression Model based on ARIMA (Auto-Regressive Integrated Moving 
-    Averages). Aims to provide a basic compute-efficient time-series model to
-    make predictions. (Aim to beat this model's prediction accuracy)
-    '''
-    def __init__(
-            self,
-            data: Union[DataFrame, Series],
-            param: Tuple[int, int, int] = (5, 2, 0)
-        ) -> None:
-
-        self.model = ARIMA(data, order=param)
-        self.model_fit = self.model.fit(data)
-        self.residuals = DataFrame(self.model_fit.resid)
-
-
-    def describe_residuals(self) -> DataFrame:
-        return self.residuals.describe()
-    
-
-    def plot_residuals(self) -> None:
-        self.residuals().plot()
-
-
-    def cross_validate(
-            self, 
-            comparision_model: Optional[Union[
-                    List,
-                    RegressorMixin,
-                    StatsModel
-                ]]
-        ) -> None:
-        ...
-
-
-    def predict(self) -> np.float64:
-        ...
diff --git a/quantpy/ml/time_series.py b/quantpy/ml/time_series.py
index bb7b310..7936d35 100644
--- a/quantpy/ml/time_series.py
+++ b/quantpy/ml/time_series.py
@@ -1,26 +1,147 @@
-'''Models.'''
-
-'''
-Ideas:
-    Regression/Time-series:
-        - ARIMA
-        - ARFIMA
-        - Seasonal Models (SARIMA, Winters)
-        - Spectral Analysis
-        - 
-
-        - Brownian Motion
-        - Martingale + Random Walk
-        - Monte-Carlo
-        - Stochastic Calculus for Finance (Discrete and Continuous)
-        - Implied Volatility
-        - Black Scholes
-        - Merton
-        - Markov
-        - 
-'''
-
-import pandas as pd
+import numpy as np
+from pandas import DataFrame, Series
+from typing import Union, Optional, Tuple, List
+from sklearn.base import RegressorMixin
+from statsmodels.base.models import Model as StatsModel
 from statsmodels.tsa.arima.model import ARIMA
+from arch import arch_model
+
+from exceptions import (
+    ModelNotFittedError,
+    ModelTypeNotSupportedError,
+    DataTypeNotSupportedError
+)
+
+class BenchmarkRegressor():
+    '''
+    Default Regression Model based on ARIMA (Auto-Regressive Integrated Moving 
+    Averages). Aims to provide a basic compute-efficient time-series model to
+    make predictions. (Aim to beat this model's prediction accuracy)
+    '''
+    def __init__(
+            self,
+            data: Union[DataFrame, Series],
+            param: Tuple[int, int, int] = (5, 2, 0),
+            fit: bool = True,
+            seasonal: bool = False,
+            bias: Optional[float] = None
+        ) -> None:
+        self.data = data
+        self.param = param
+        self.model = ARIMA(data, order=param)
+        self.bias = bias if bias else 0
+        if fit:
+            self.model_fit = self.model.fit(data + bias)
+            self.residuals = DataFrame(self.model_fit.resid)
+
+
+    def get_model(self) -> StatsModel:
+        return self.model
+
+
+    def get_summary(self) -> None:
+        self.model.summary()
+
+
+    def get_data(self) -> DataFrame:
+        return self.data
+    
+
+    def get_params(self) -> Tuple:
+        return self.params
+    
+
+    def fit(self):
+        self.model_fit = self.model.fit(self.data + self.bias)
+        self.residuals = DataFrame(self.model_fit.resid)
+
+
+    def describe_residuals(self) -> DataFrame:
+        if not self.residuals:
+            raise ModelNotFittedError()
+        return self.residuals.describe()
+    
+
+    def plot_residuals(self) -> None:
+        if not self.residuals:
+            raise ModelNotFittedError()
+        self.residuals.plot()
+
+
+    def cross_validate(
+            self, 
+            comparision_model: Optional[Union[
+                    List,
+                    RegressorMixin,
+                    StatsModel
+                ]]
+        ) -> None:
+        '''Validate based on data or compare to other models'''
+        ...
+
+
+    def predict(self, horizon: Union[int, Tuple] = None) -> np.float64:
+        if not self.model_fit:
+            raise ModelNotFittedError()
+        if type(horizon) == int:
+            return self.model_fit.forecast(steps=horizon)[0]
+        elif type(horizon) == tuple:
+            return self.model_fit.predict(start=horizon[0], end=horizon[1])
+        else:
+            return self.model_fit.forecast()[0]
+
+
+class GARCH():
+    '''
+    Default GARCH (Generalized Auto-Regressive Conditional Heteroskedasticity)
+    model to find forecasts based on changes of variances in a time-series. 
+    Useful for Volatility.
+    '''
+    def __init__(
+            self,
+            data: Union[DataFrame, Series],
+            p: int = 1,
+            q: int = 1,
+            fit: bool = True
+        ) -> None:
+        self.data = data
+        self.p, self.q = p, q
+        self.model = arch_model(data, mean='Zero', vol='GARCH', p=p, q=q)
+        if fit:
+            self.model_fit = self.model.fit()
+
+
+    def get_model(self) -> arch_model:
+        return self.model
+
+
+    def get_data(self) -> DataFrame:
+        return self.data
+    
+
+    def get_params(self) -> Tuple:
+        return self.p, self.q
+    
+
+    def fit(self):
+        self.model_fit = self.model.fit()
+
+
+    def cross_validate(
+            self,
+            comparision_model: Optional[Union[
+                    List,
+                    RegressorMixin,
+                    StatsModel
+                ]]
+        ) -> None:
+        '''Validate based on data or compare to other models'''
+        ...
+
+
+    def predict(self, horizon: Union[DataFrame, Series]) -> Series:
+        if not self.model_fit:
+            raise ModelNotFittedError()
+        return self.model_fit.forecast(horizon=horizon)
 
 
diff --git a/setup.py b/setup.py
index b6b0967..bc9a2b1 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,11 @@
     description='A framework for quantitative finance In python',
     long_description=open('README.md').read(),
     install_requires=[
+        "numpy >= 1.23.5",
         "pandas >= 0.10.0",
         "matplotlib >= 1.1.0",
+        "statsmodels >= 0.14.0",
+        "arch >= 6.0.1",
+        "sklearn >= 1.1.0",
     ],
 )

From 5319361eb48dabb5953f7d98201017b7a13a2a63 Mon Sep 17 00:00:00 2001
From: Ammar Siddiqui <ammarsiddiqui@Ammars-MacBook-Pro.local>
Date: Thu, 25 May 2023 19:22:37 -0400
Subject: [PATCH 4/4] updated sklearn version

---
 quantpy/ml/exceptions.py | 2 +-
 setup.py                 | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/quantpy/ml/exceptions.py b/quantpy/ml/exceptions.py
index 55e66eb..d4d9a08 100644
--- a/quantpy/ml/exceptions.py
+++ b/quantpy/ml/exceptions.py
@@ -5,4 +5,4 @@ class ModelTypeNotSupportedError(Exception):
     ...
 
 class DataTypeNotSupportedError(Exception):
-    ...
\ No newline at end of file
+    ...
diff --git a/setup.py b/setup.py
index bc9a2b1..59851f7 100644
--- a/setup.py
+++ b/setup.py
@@ -16,6 +16,6 @@
         "matplotlib >= 1.1.0",
         "statsmodels >= 0.14.0",
         "arch >= 6.0.1",
-        "sklearn >= 1.1.0",
+        "scikit-learn >= 1.1.0",
     ],
 )