Repository: Laurier-Fintech/OpenFintech (language: python)

Issue:
Bollinger bands, SMA, Normalized Prices Classes



Discussion / hints:

Fix patch:
diff --git a/OpenFintech/APIs.py b/OpenFintech/APIs.py
deleted file mode 100644
index 582cd29..0000000
--- a/OpenFintech/APIs.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from . import queries
-import requests
-import pandas as pd
-from datetime import datetime as dt
-
-# TODO: 
-#   - Update the Alphavantage class to work with SQLite3 instead of MySQl
-#   - Handling edge cases (where error occurs when the DB has no data and API fails ??)
-
-class Alphavantage: 
-    def __init__(self, database, key="", keys=[], refresh=30):
-        self.db_handler = database
-    
-        # Setup key/keys
-        self.key = key # Is empty if the user provided a list of keys
-        self.keys = {key: 0 for key in keys} 
-        if len(self.keys)==0 and self.key=="": raise Exception("Please provide an Alphavantage key or a list of Alphavantage keys.")
-        if len(self.keys)==0 and self.key!="": self.keys[self.key]=0 # NOTE: From this point on, only self.keys will be used.
-        
-        self.refresh = refresh # 30 days by default
-        return
-    
-    def overview(self, ticker:str): # NOTE: Currently works for equities only, stores overviews in our DB to reduce key usage 
-        
-        result = self.db_handler.execute(queries.select_ticker_entry, values=(ticker,), query=True) # Check if the given ticker exists in the equities table        
-        
-        if (isinstance(result, list)==True):
-            if (len(result)==0 or ((dt.now() - result[0][2]).days > self.refresh)): # If the data is not available in the equities table (or if the data is outdated)
-                # Request data from Alphavantage API, create new entry, and insert into the DB
-                url = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={self.get_key(self.keys)}"
-                response = self._request(url)
-                try:
-                    self.db_handler.execute(queries.insert_equity_complete, values=
-                        (
-                            response["Symbol"],
-                            response["Name"],response["Description"],response["CIK"],
-                            response["Country"],response["Currency"],response["Exchange"],
-                            response["Address"],response["Industry"],response["Sector"],
-                        )
-                    )
-                except:
-                    self.db_handler.execute(queries.insert_equity_short, values=
-                        (
-                            ticker,
-                        )
-                    )
-                result = self.db_handler.execute(queries.select_ticker_entry, values=(ticker,), query=True)[0]
-            else: result = result[0] # If no refresh is required, grab the latest one to return
-
-        return result
-    
-    # Internal utility functions (can be used externally as well as they are esentially independent from the package (no self parm.))
-    @staticmethod
-    def get_key(keys:dict):
-        if len(keys)==0: raise Exception("No keys given.")
-        key = min(keys, key=keys.get)
-        keys[key]+=1
-        return key
-
-    @staticmethod 
-    def _request(url:str): 
-        # Has error handling for our own request module 
-        response = requests.get(url)
-        # Check if the request's response is valid/if it failed
-        if response.status_code==200: # Check if the request worked 
-            try: response = response.json()
-            except: 
-                raise Exception("Failed to convert response to JSON.")
-            else:
-                if "Note" not in response.keys(): 
-                    return response
-                else: 
-                    raise Exception("Exceeded request limit")
-        raise Exception(f"Request Failed {response.status_code}")
-    
-    @staticmethod
-    def lookup(key:str, ticker:str, check=False, full=False): # Returns either the list of tickers, boolean if given ticker exists, or the full response as is
-        if key==None or ticker==None: raise Exception("Please provide a ticker and a key to use the lookup function")
-        if check==True and full==True: raise Exception("Please avoid having both check and full set to true simultaneously.")
-
-        endpoint = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords={ticker}&apikey={key}"
-        response = Alphavantage._request(endpoint)
-
-        if full==False:
-            response = [ticker['1. symbol'] for ticker in response['bestMatches']] # Extract tickers from the response
-            if check==True: # Set variable to return as a boolean (true if user ticker exists in symbols)
-                response = ticker.lower() in [ticker.lower() for ticker in response]
-
-        return response
-
-    @staticmethod
-    def equity_daily(key:str, ticker:str):
-        response = Alphavantage._request(url=f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol={ticker}&apikey={key}")
-        response = response["Time Series (Daily)"]
-        df = pd.DataFrame.from_dict(response, orient="index", dtype=float).iloc[::-1]
-        return df
-
-    @staticmethod
-    def equity_intraday(key:str, ticker:str, interval:int=5): # Default interval is 5 mins        
-        response = Alphavantage._request(url=f"https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol={ticker}&interval={interval}min&apikey={key}")
-        response = response[f"Time Series ({interval}min)"]
-        df = pd.DataFrame.from_dict(response, orient="index", dtype=float).iloc[::-1]
-        return df
-    
-    @staticmethod
-    def technical_indicator(indicators: dict, df: pd.DataFrame):
-        i = len(df.columns) + 1
-        for indicator in indicators:
-            if "EMA" in indicator:
-                df[f'{i}. {indicator}'] = df["4. close"].ewm(span=int(indicator[3:]), adjust=False).mean()
-            elif "SMA" in indicator:
-                df[f'{i}. {indicator}'] = df["4. close"].rolling(window=int(indicator[3:])).mean()
-            i+=1
-        df.dropna(inplace=True)
-        return df
\ No newline at end of file
diff --git a/OpenFintech/Data.py b/OpenFintech/Data.py
new file mode 100644
index 0000000..fe7709f
--- /dev/null
+++ b/OpenFintech/Data.py
@@ -0,0 +1,173 @@
+from collections import deque 
+from typing import Deque
+import json
+
+import pandas as pd
+import requests
+
+class Candle:
+    def __init__(self, open: float, high: float, low: float, close: float, volume: int, datetime: str, durationSeconds: float):
+        self.open = open
+        self.high = high
+        self.low = low
+        self.close = close
+        self.volume = volume
+        self.datetime = datetime
+        self.duration_seconds = durationSeconds
+
+class CandleContainer:
+    def __init__(self):
+        self.candleList: Deque[Candle] = deque()
+
+    def count(self) -> int:
+        return len(self.candleList)
+    
+    def __len__(self):
+        return self.count()
+    
+    def __getitem__(self, index: int) -> Candle:
+        return self.candleList[index]
+
+    def front(self) -> Candle:
+        if not self.candleList:
+            raise IndexError("The CandleContainer is empty.")
+        return self.candleList[0]
+
+    def back(self) -> Candle:
+        if not self.candleList:
+            raise IndexError("The CandleContainer is empty.")
+        return self.candleList[-1]
+
+    def pushFront(self, candle: Candle):
+        self.candleList.appendleft(candle)
+
+    def pushBack(self, candle: Candle):
+        self.candleList.append(candle)
+
+    def popFront(self) -> Candle:
+        if not self.candleList:
+            raise IndexError("The CandleContainer is empty.")
+        return self.candleList.popleft()
+
+    def popBack(self) -> Candle:
+        if not self.candleList:
+            raise IndexError("The CandleContainer is empty.")
+        return self.candleList.pop()
+
+    def clear(self):
+        self.candleList.clear()
+        
+# NOTE: The data structures below are i.e. a replacement to having the database
+class FinancialInstrument:
+    def __init__(self, ticker: str, candle_container: CandleContainer):
+        self.ticker = ticker
+        self.candle_container = candle_container
+        return
+
+# NOTE: The class below was previously a static method within the Alphavantage API wrapper
+class Indicator:
+    def __init__(self, candle_container: CandleContainer, open_or_close = 'open'):
+        self.candle_container = candle_container
+        self.open_or_close = open_or_close
+        self.calculatedValues = []
+        return
+    
+    def runCalcOnCandleContainer(self):
+        return
+    
+    def returnCalculatedValues(self):
+        return self.calculatedValues
+
+class BollingerBands(Indicator):
+    def __init__(self, candle_container: CandleContainer, periodLength = 20, open_or_close = 'open'):
+        super().__init__(candle_container, open_or_close)
+
+
+        self.sma = SMA(self.candle_container, self.periodLength, self.open_or_close).calculatedValues[-1] # want a 20-day moving average - may need to adjust # of candles depending on candle frequency
+        
+        self.periodLength = periodLength
+
+    def runCalcOnCandleContainer(self):
+
+        self.df = pd.DataFrame({'open_price' : candle.open, 'close_price' : candle.close} for candle in self.candle_container.candleList)
+        sd = self.df.std()[f'{self.open_or_close}_price']
+
+        upper_band = self.sma + 2*sd
+        middle_band = self.sma
+        lower_band = self.sma - 2*sd
+
+        self.calculatedValues = [upper_band, middle_band, lower_band]
+
+class NormalizedPrices(Indicator):
+    def __init__(self, candle_container: CandleContainer, periodLength, open_or_close = 'open'):
+        super().__init__(candle_container, open_or_close)
+
+        self.df = pd.DataFrame({'open_price' : candle.open, 'close_price' : candle.close} for candle in candle_container.candleList)
+
+        self.periodLength = periodLength
+
+    def runCalcOnCandleContainer(self):
+        dfcol = self.df[f'{self.open_or_close}_price']
+        mean = dfcol.mean()
+        sd = dfcol.std()
+        normalizedCol = (dfcol - mean)/sd
+        self.calculatedValues = normalizedCol.to_list()
+        
+class SMA(Indicator):
+    def __init__(self, candle_container: CandleContainer, periodLength: int, open_or_close = 'open'):
+        super().__init__(candle_container, open_or_close)
+        
+        self.periodLength = periodLength
+
+    def runCalcOnCandleContainer(self):
+        self.df = pd.DataFrame({'open_price' : candle.open, 'close_price' : candle.close} for candle in self.candle_container.candleList)
+        self.calculatedValues = self.df.rolling(self.periodLength).mean()[f'{self.open_or_close}_price'].to_list()
+
+
+# NOTE: This class was previously a part of the API wrapper
+class DataAcquisition: 
+    def __init__(self, key: str):
+        self.key = key
+        return
+    
+    def _request(): # Could use an internal request method that simplifies things (check out the old Alphavantage wrapper code)
+        return
+
+    def requestDataFromAPI(self, ticker, interval, outputsize='full'): # declare return type, parameters should be for our functionality
+        params = None
+
+        if interval == 'daily':
+            params = {
+                'function': 'TIME_SERIES_DAILY',
+                'symbol': ticker,
+                'outputsize': 'compact',
+                'apikey': self.key,
+            }
+        else:
+            params = {
+                'function': 'TIME_SERIES_INTRADAY',
+                'symbol': ticker,
+                'interval': f'{interval}', # '1min', '5min', '15min', '30min', '60min
+                'outputsize': outputsize,
+                'apikey': self.key,
+            }
+        
+        response = requests.get(url="https://www.alphavantage.co/query", params=params)
+        response.raise_for_status()
+        return response.json()
+    
+    def convertDataToFinancialInstrument(self, jsonDictFromAPI) -> FinancialInstrument:
+        candleContainer = CandleContainer()
+        
+        # Items need to be pushed in reverse order since the API returns the most recent data first
+        for key, value in jsonDictFromAPI['Time Series (Daily)'].items():
+            candleContainer.pushFront(Candle(
+                open = float(value['1. open']),
+                high = float(value['2. high']),
+                low = float(value['3. low']),
+                close = float(value['4. close']),
+                volume = int(value['5. volume']),
+                datetime = key,
+                durationSeconds = 86400
+            ))
+        return FinancialInstrument(jsonDictFromAPI['Meta Data']['2. Symbol'], candleContainer)
\ No newline at end of file
diff --git a/OpenFintech/Databases.py b/OpenFintech/Databases.py
deleted file mode 100644
index 2e17cb0..0000000
--- a/OpenFintech/Databases.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import sqlite3
-
-class SQLite3:
-    def __init__(self, name=":memory:"):
-        self.conn:sqlite3.Connection = sqlite3.connect(name)
-        self.curr = self.conn.cursor()
-        return
-    
-    def execute(self,statement, query=False, values:dict={}):
-        if len(values)>0: self.curr.execute(statement,values)
-        else: self.curr.execute(statement)
-        self.conn.commit()
-        if query: result = self.curr.fetchall() 
-        else: result = self.curr.lastrowid
-        return result
-    
-    def download(self):
-        return 
-    
-    def disconnect(self):
-        try: self.conn.commit()
-        except: pass
-        self.curr.close()
-        self.conn.close()
-        return 
\ No newline at end of file
diff --git a/OpenFintech/Model.py b/OpenFintech/Model.py
index 968a5bf..da47d7d 100644
--- a/OpenFintech/Model.py
+++ b/OpenFintech/Model.py
@@ -1,135 +1,170 @@
-from .Databases import SQLite3
-from . import Alphavantage
-from . import queries
-import pandas as pd
-
-# TODO: Develop the __str__ function
-
-class Model:
-    def __init__(self, database=':memory:'):
-        self.handler = SQLite3(name=database)
-        for statement in [queries.user_tbl_create, queries.settings_tbl_create, queries.trades_tbl_create, queries.performance_tbl_create]: self.handler.execute(statement)
-        self.handler.execute(queries.create_user, values={
-            'username': 'Test',
-            'email': None,
-            'password': None,
-            'year': None,
-            'major': None
-        })
+class Algorithm:
+    def __init__(self):
+        pass
+
+    def runAlgorithmOnCandle(self):
+        pass
+
+    def runAlgorithmOnCandleContainer(self):
+        pass
+
+class MeanReversion(Algorithm):
+    def __init__(self):
+        return
+    
+    def runAlgorithmOnCandle(self):
         return
     
-    def create(self,values:dict): # Function used to create a settings entry
-        lastrowid = self.handler.execute(queries.create_setting, values=values)
-        return lastrowid
-
-    def order(self, values:dict): # NOTE: Can add aditional features later on
-        lastrowid = self.handler.execute(queries.create_trade, values=values)
-        if values["type"]==0: print(f"(#{lastrowid})",values["trade_dt"], ": Buy @", values["price"])
-        else: print(f"(#{lastrowid})",values["trade_dt"], ": Sell @", values["price"])
-        return lastrowid
-
-    def backtest(self, setting_values:dict, ALPHAVANTAGE_KEY) -> dict:
-        print("\nModel.backtest():")
-
-        # Enter the settings into the database
-        setting_id = self.create(setting_values)
-        print(f"\tCreated setting with the ID {setting_id}")
-
-        # Get additional required info from settings
-        aum = float(setting_values["starting_aum"])
-        stop_loss = float(setting_values["stop_loss"])
-        take_profit = float(setting_values["take_profit"])
-        indicators = [''.join(setting_values["short"].split(" ")),''.join(setting_values["long_"].split(" "))]
-        # Get the price data for the setting values using the OpenFintech Alphvantage Package
-        df = Alphavantage.equity_daily(key=ALPHAVANTAGE_KEY,ticker=setting_values["ticker"])
-        # Modify the price_data_df based on the given config values indicators section
-        df = Alphavantage.technical_indicator(indicators,df) # Add the tehcnical indicators data to the dataframe
-        # Import the data for given the setting using the given api_handler (Alphavantage object)
-        print("\tPrice Data + Indicator Data:")
-        print(df)
-
-        # Intiailize variables to store temp values to help the algorithm perform calculations
-        open = False 
-        quantity, purchase_price, sale_price = 0, 0, 0
-        
-        # Setup variables for the trade history df
-        trade_data = []
-
-
-        # Iterate over the data frame and perform the checks
-        for i, r in df.iterrows(): # TODO: CRUD to the trades table
-            # Get data from the current row (unit of time)
-            date, close, short, long = i, r["4. close"], r[f"6. {indicators[0]}"], r[f"7. {indicators[1]}"]
-
-            if open==False: # At the current unit of time, if there are no open positions....
-
-                if short>long: # Check if the short term mean (base) has passed the long term mean (upper), indicating a upwards change in the price action and a buy signal
-                    purchase_price = close # store the purchase price in a variable initialized outside the loop (for referencing in future iterations)
-                    quantity = aum / purchase_price # Calculate the maximum purchaseable shares (NOTE: This is a limitation of the current system by design)
-                    total = purchase_price * quantity # Calculate the total cost of the purchase 
-                    aum -= total # remove cost from balance (NOTE: leaving formula in although this would always be zero due to the limitation highlighted above)
-                    
-                    # Create buy trade entry
-                    data = {"setting_id": setting_id, "type": 0, "trade_dt": i, 
-                              "price": purchase_price, "quantity":quantity, "total": total}
-                    trade_data.append(data)
-                    self.order(data)
-                    
-                    open = True # Update variable to indicate that a purchase has been made, i.e. position opened.
-
-            else: # When there is an open position.... 
-                
-                sell = False #NOTE: This boolean variable is used as a trigger to avoid creating a sell function and to avoid writing redundant code
-
-                if short<long: sell = True # If the short term mean has fell underneath the long term mean, indicating a downwards change in the price action, triger the sale of all open positions (NOTE: "all" due to the limitation of the system as discussed earlier)
-
-                else: # When holding, check if the current price, relative to the purchase price, triggers a stop loss or take profit
-                    if (close <= (purchase_price - (purchase_price*stop_loss))): sell = True # Conditional statement for stopping loss
-                    if (close >= (purchase_price + (purchase_price*take_profit))): sell = True # Conditional statement for taking profit
-
-                if sell: # Code to sell open positions/current holdings (transaction)
-                    sale_price = close
-                    total = quantity * sale_price # Get the total gained from the sale
-                    quantity = 0 # Update the quantity NOTE: Since this is our hypothetical market, when we sell, we assume we find a perfect buyer for all the shares we own at the closing price
-                    aum += total # add the total gained from the sale to the aum 
-                    # Differentitate between sales that lead to profits vs losses and handle each case differently (NOTE: Potential room for a future project)
-                    profitable = False if sale_price<purchase_price else True
-                    
-                    # Create sale trade entry
-                    data = {"setting_id": setting_id, "type": 1, "trade_dt": i, 
-                              "price": purchase_price, "quantity":quantity, "total": total}
-                    trade_data.append(data)
-                    self.order(data)
-
-
-                    if profitable: print("\tProfit Captured Per Share Sold: ", sale_price-purchase_price) # If profitable, output the profit captured per share sold
-                    open = False
-
-        # Create response package
-        response = {}
-        response["ending_aum"] = aum
-        response["dollar_change"] = response["ending_aum"] - setting_values["starting_aum"]
-        response["percent_change"] = (response["dollar_change"]/setting_values["starting_aum"])*100
-        response["setting_id"] = setting_id
-        self.handler.execute(queries.create_performance, values=response)
-        del response['setting_id']
-        response["price_data"] = df
-        # Add the trade data to the response package
-        response["trade_data"] = pd.DataFrame(trade_data)
-
-        return response
+    def runAlgorithmOnCandleContainer(self, candle_container, short_ma, long_ma, stop_loss, take_profit, assets):
+        signals = []
+        open_position = False
+        position_type = None  # 'Buy' or 'Sell'
+        purchase_price = None
+        quantity = 0
+        aum = assets
+
+        for i in range(len(candle_container)):
+            current_candle = candle_container[i]
+            current_price = current_candle.close
+
+            # Check if enough data is available for MA calculations
+            if i < max(short_ma.periodLength, long_ma.periodLength):
+                signals.append(None)
+                continue
+
+            short_ma_value = short_ma.calculatedValues[i]
+            long_ma_value = long_ma.calculatedValues[i]
+            sell = False  # Declare sell here
+
+            if not open_position:
+                if short_ma_value > long_ma_value:
+                    purchase_price = current_price
+                    quantity = aum / purchase_price
+                    total = purchase_price * quantity
+                    aum -= total
+
+                    data = {"type": "Buy", "price": purchase_price, "quantity": quantity, "total": total}
+                    signals.append(data)
+                    open_position = True
+                    position_type = "Buy"
+            else:
+                stop_loss_point = purchase_price * (1 - stop_loss)
+                take_profit_point = purchase_price * (1 + take_profit)
+                if short_ma_value < long_ma_value or (current_price <= stop_loss_point) or (current_price >= take_profit_point):
+                    sell = True
+                    sale_price = current_price
+                    total = quantity * sale_price
+                    aum += total
+
+                    data = {"type": "Close " + position_type, "price": sale_price, "quantity": quantity, "total": total}
+                    signals.append(data)
+                    open_position = False
+                    position_type = None
+                    quantity = 0
+
+            if not sell and open_position:
+                signals.append({"type": "Hold " + position_type})
+
+        return signals, aum
+
+class TrendFollowing(Algorithm):
+    def __init__(self):
+        super().__init__()
     
-    def simulate(self): # NOTE: We can worry about this after we build backtest! This is for realtime simulation!
-        # Similar to “test_configuration” but for real-time (simulated market) testing. 
-        # This would use realtime price data which would be added to (or retrived from) the appropriate collection or database or dataframe in market
-        # It would perform the required calculations based on the confiurations
-        
-        # Realtime (simualted) testing code here
-
-        # This would take a setting and a configuration as well
-        # It should output performance and market data (including price data) as pandas df or a list of dictionaries?
+    def runAlgorithmOnCandle(self, candle, short_ma, long_ma):
         return
 
-    # Prints an overview of the market/performances etc. (can use complex MySQL queries and create visualizations now)
-    def __str__(self):
-        return("working")
\ No newline at end of file
+    def runAlgorithmOnCandleContainer(self, candle_container, short_ma, long_ma, stop_loss, take_profit, assets):
+        """
+        candle_container: CandleContainer
+        short_ma: Indicator
+        long_ma: Indicator
+
+        returns: list of all buy and sell signals
+
+        This function will run the trend following algorithm on a candle container.
+        The trend following algorithm does the following:
+            - Assuming a position is not currently held, if the short moving average is
+                above the long moving average and the long moving average has a positive
+                derivative, then buy
+            - Assuming a position is not currently held, if the short moving average is
+                below the long moving average and the long moving average has a negative derivative
+                then sell
+            - If neither, hold
+            - If already in a position, hold until stop loss or take profit is hit
+        """
+        signals = []
+        position = None  # None, 'Buy', or 'Sell'
+        entry_price = None
+        quantity = 0
+        aum = assets
+
+        for i in range(len(candle_container)):
+            current_candle = candle_container[i]
+            current_price = current_candle.close
+
+            # Check if enough data is available for MA calculations
+            if i < max(short_ma.periodLength, long_ma.periodLength):
+                signals.append(None)
+                continue
+
+            short_ma_value = short_ma.calculatedValues[i]
+            long_ma_value = long_ma.calculatedValues[i]
+            long_ma_prev_value = long_ma.calculatedValues[i - 1]
+            long_ma_derivative = long_ma_value - long_ma_prev_value
+
+            # Buy Logic
+            if short_ma_value > long_ma_value and long_ma_derivative > 0 and (position is None or position == 'Sell'):
+                if position == 'Sell':
+                    # Close Sell position
+                    sale_price = current_price
+                    total = quantity * sale_price
+                    aum += total
+                    signals.append({"type": "Close Sell", "price": sale_price, "quantity": quantity, "total": total})
+                    quantity = 0
+
+                # Open Buy position
+                entry_price = current_price
+                quantity = aum / entry_price
+                total = entry_price * quantity
+                aum -= total
+                signals.append({"type": "Buy", "price": entry_price, "quantity": quantity, "total": total})
+                position = 'Buy'
+
+            # Sell Logic
+            elif short_ma_value < long_ma_value and long_ma_derivative < 0 and (position is None or position == 'Buy'):
+                if position == 'Buy':
+                    # Close Buy position
+                    sale_price = current_price
+                    total = quantity * sale_price
+                    aum += total
+                    signals.append({"type": "Close Buy", "price": sale_price, "quantity": quantity, "total": total})
+                    quantity = 0
+
+                # Open Sell position
+                entry_price = current_price
+                quantity = aum / entry_price  # Assuming short selling is allowed
+                total = entry_price * quantity
+                aum -= total  # Assuming margin trading or short selling
+                signals.append({"type": "Sell", "price": entry_price, "quantity": quantity, "total": total})
+                position = 'Sell'
+
+            # Check for stop loss or take profit
+            elif position is not None:
+                if (position == 'Buy' and (current_price <= entry_price * (1 - stop_loss) or current_price >= entry_price * (1 + take_profit))) or \
+                   (position == 'Sell' and (current_price >= entry_price * (1 + stop_loss) or current_price <= entry_price * (1 - take_profit))):
+                    # Close position
+                    sale_price = current_price
+                    total = quantity * sale_price
+                    aum += total
+                    signals.append({"type": "Close " + position, "price": sale_price, "quantity": quantity, "total": total})
+                    position = None
+                    entry_price = None
+                    quantity = 0
+                else:
+                    signals.append({"type": "Hold " + position})
+
+            else:
+                signals.append(None)
+
+        return signals, aum
diff --git a/OpenFintech/__init__.py b/OpenFintech/__init__.py
index 8e922e8..5c03edf 100644
--- a/OpenFintech/__init__.py
+++ b/OpenFintech/__init__.py
@@ -1,3 +1,2 @@
-from OpenFintech.Databases import SQLite3
-from OpenFintech.APIs import Alphavantage
-from OpenFintech.Model import Model
\ No newline at end of file
+from OpenFintech.Data import *
+from OpenFintech.Model import *
\ No newline at end of file
diff --git a/OpenFintech/queries.py b/OpenFintech/queries.py
deleted file mode 100644
index dfdbdb6..0000000
--- a/OpenFintech/queries.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# NOTE: SQLITE3 QUERIES
-user_tbl_create = """
-CREATE TABLE IF NOT EXISTS users (
-    user_id INTEGER PRIMARY KEY AUTOINCREMENT,
-    date_created DATETIME DEFAULT CURRENT_TIMESTAMP,
-    username TEXT UNIQUE NOT NULL,
-    email TEXT UNIQUE DEFAULT NULL,
-    password TEXT DEFAULT NULL,
-    year INTEGER DEFAULT NULL,
-    major TEXT DEFAULT NULL,
-    CHECK (year >= 1 OR year IS NULL)
-);
-"""
-create_user = """INSERT INTO users(username, email, password, year, major) VALUES(:username, :email, :password, :year, :major)"""
-
-settings_tbl_create = """
-CREATE TABLE IF NOT EXISTS settings ( 
-    setting_id INTEGER PRIMARY KEY AUTOINCREMENT,
-    user_id INTEGER NOT NULL,
-    date_created DATETIME DEFAULT CURRENT_TIMESTAMP,
-
-    ticker TEXT NOT NULL,
-
-    short TEXT DEFAULT NULL,
-    long_ TEXT DEFAULT NULL,
-
-    stop_loss REAL DEFAULT 0,
-    starting_aum REAL DEFAULT 0,
-    take_profit REAL DEFAULT 0,
-
-    FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE
-);
-"""
-create_setting = """INSERT INTO settings(user_id, ticker, short, long_, stop_loss, starting_aum, take_profit) VALUES(:user_id, :ticker, :short, :long_, :stop_loss, :starting_aum, :take_profit)"""
-
-trades_tbl_create = """
-CREATE TABLE IF NOT EXISTS trades ( 
-    trade_id INTEGER PRIMARY KEY AUTOINCREMENT,
-    setting_id INTEGER NOT NULL,
-    date_created DATETIME DEFAULT CURRENT_TIMESTAMP,
-
-    type INTEGER NOT NULL,
-    trade_dt DATETIME NOT NULL,
-
-    price REAL NOT NULL,
-    quantity REAL NOT NULL,
-    total REAL NOT NULL,
-
-    FOREIGN KEY (setting_id) REFERENCES settings (setting_id) ON DELETE CASCADE ON UPDATE CASCADE
-);
-"""
-create_trade = """INSERT INTO trades(setting_id, type, trade_dt, price, quantity, total) VALUES(:setting_id, :type, :trade_dt, :price, :quantity, :total)"""
-
-performance_tbl_create = """
-CREATE TABLE IF NOT EXISTS performances ( 
-    performance_id INTEGER PRIMARY KEY AUTOINCREMENT,
-    setting_id INTEGER NOT NULL,
-    date_created DATETIME DEFAULT CURRENT_TIMESTAMP,
-
-    dollar_change REAL DEFAULT 0,
-    percent_change REAL DEFAULT 0,
-    ending_aum REAL DEFAULT 0,
-
-    FOREIGN KEY (setting_id) REFERENCES settings (setting_id) ON DELETE CASCADE ON UPDATE CASCADE
-);
-"""
-create_performance = """INSERT INTO performances(setting_id, dollar_change, percent_change, ending_aum) VALUES(:setting_id, :dollar_change, :percent_change, :ending_aum)"""
\ No newline at end of file
diff --git a/README.md b/README.md
index 857c896..5b8882d 100644
--- a/README.md
+++ b/README.md
@@ -1,133 +1,69 @@
-# **📦 OpenFintech: Opensource Fintech Library**
+# OpenFintech
 
-### **1. Introduction**
+## Introduction
+OpenFintech is a financial analysis library designed for Python developers and financial analysts. It provides powerful tools for conducting both trend following and mean reversion analyses, utilizing financial market data. This project aims to make complex financial algorithms accessible and easy to use.
 
-OpenFintech is a pioneering project designed with the primary objective of allowing users to create custom high-frequency trading algorithms and introducting them to financial technology as a field 💻. This project also includes wrappers for SQLite3 and the Alphavantage API (a financial information API)*.*
-
-#### **1.1. Who Is OpenFintech For?**
-
-OpenFintech is tailored for:
-
-- **Students** who have a keen interest in:
-  - Delving deeper into trading algorithms.
-  - Grasping the basics of algorithmic trading and fintech.
-  - Undertaking both backtesting and simulated live testing of their algorithmic trading strategy.
-
----
-
-### **2. The Heart of OpenFintech: Mean Reversion Strategy**
-
-#### **2.1. Conceptualizing Mean Reversion**
-
-Mean reversion is the cornerstone of the strategies offered by OpenFintech. It is premised on the theory that asset prices, volatility, and historical returns will eventually revert to the long-run mean of the entire dataset. In the financial realm, this mean can manifest in various contexts, such as a stock's historical price data, P/E ratio or the average return of an industry sector.
-
-#### **2.2. Potential Risks**
-
-It's crucial to note that a return to the norm isn't always guaranteed. Unexpected market highs or lows could signal a shift in the usual trend. Events that might trigger such shifts include product launches or, on the flip side, recalls and lawsuits.
-
----
-
-### **3. The Brain Behind the Operations: Model.py and Backtesting**
-
-At the core of OpenFintech's algorithmic strategies is the `Model.py` file, which focuses on backtesting the strategies to provide users with realistic and insightful simulations.
-
-#### **3.1. Algorithmic Trading Strategies our system offers (for now at least 👀)**
-
-1. Moving Average (SMA) with Different Periods:
-   - In this strategy, the system considers two distinct moving averages: one short-term (like the 50-day moving average) and one long-term (like the 200-day moving average).
-   - Long Signal: When the short-term moving average crosses above the long-term moving average, this is a potential sign that the asset is in an upward momentum, thus generating a buy (or "long") signal.
-   - Short Signal: Conversely, when the short-term moving average crosses below the long-term moving average, the asset might be experiencing a downturn, thus generating a sell (or "short") signal.
-   - Customizable parameters:
-     - Moving averages (e.x. SMA and EMA) and their associated periods
-
----
-
-### **4. How To Use**
-
-Here's a step-by-step guide to using the OpenFintech package:
-
-1. **Installation**:
-   Begin by installing the OpenFintech package using pip.
+## Installation
+To install OpenFintech, follow these steps:
 
+1. Ensure you have Python installed on your system.
+2. Clone the repository:
    ```bash
-   pip install OpenFintech
+   git clone https://github.com/your-username/OpenFintech.git
    ```
-2. **Setting Up The File**:
-   After installation, create a Python script and import the required classes from the OpenFintech package.
-
-   ```python
-   from OpenFintech import Model
-   ```
-3. **Variable Configuration**:
-   Before proceeding, set up the necessary variables for your database and API configurations. It's recommended to use environment variables to store these values securely.
-
-   ```python
-   ALPHAVANTAGE_KEY = "RANDOM KEY" # get your own key here https://www.alphavantage.co/support/#api-key
+3. Install required dependencies:
+   ```bash
+   pip install -r requirements.txt
    ```
-4. **Initialization**:
-   With the above variables, initiate the various handlers:
 
-   - Model handler
+## Usage
+Here's a quick example of how to use OpenFintech to run financial algorithms:
 
-   ```python
-   handler = Model() # or provide a db_name.db
-   ```
-5. **Setting Backtesting Parameters**:
-   Define a dictionary for backtesting settings. This dictionary will contain parameters like starting amount, short and long strategies, stock ticker, and more.
-
-   ```python
-   setting_values = {
-       "user_id": 1,
-       "starting_aum": 100000,
-       "short": "EMA 5",
-       "long": "SMA 15",
-       "ticker": "NIO",
-       "stop_loss": 0.10, #%
-       "take_profit": 0.5,#%
-       "chart_freq_mins": 0
-   }
-   ```
-6. **Backtesting**:
-   Call the backtest function from the model handler with the previously defined settings and the alphavantage key
+```python
+from OpenFintech import *
 
-   ```python
-   response = handler.backtest(setting_values, ALPHAVANTAGE_KEY)
-   print(response)
-   ```
-7. **Clean Up**:
-   After completing all operations, make sure to close the connection to the database.
+# Initialize and get data
+data_acq = DataAcquisition("your-api-key")
+tickerData = data_acq.requestDataFromAPI('AAPL', 'daily')
 
-```python
-   handler.disconnect()
-   print("Disconnected database connection.")
-```
+# Convert to FinancialInstrument
+ticker_finInst = data_acq.convertDataToFinancialInstrument(tickerData)
 
-Note: Always ensure that sensitive data such as passwords and API keys are stored securely and are not hard-coded directly into the script.
+# Run algorithms
+tr_algo = TrendFollowing()
+tr_backtest_data = tr_algo.runAlgorithmOnCandleContainer(...)
+mr_algo = MeanReversion()
+mr_backtest_data = mr_algo.runAlgorithmOnCandleContainer(...)
 
----
+print(tr_backtest_data)
+print(mr_backtest_data)
+```
 
-### **5. Output**
+Replace `"your-api-key"` with your actual API key.
 
-The output of Model.backtest contains the price data and the trade data as a pandas df along with the performance of the settings provided to the algorithm.
+## Components
+### Model.py
+- `Algorithm`: Base class for trading algorithms.
+- `MeanReversion`: Implements the mean reversion strategy.
+- `TrendFollowing`: Implements the trend following strategy.
 
-- {'price_data':             1. open  2. high    3. low  4. close    5. volume     6. EMA5    7. SMA15
-  2023-04-18  187.150   187.69  183.5775    184.31   92067016.0  185.355453  189.188667
-  2023-04-19  179.100   183.50  177.6500    180.59  125732687.0  183.766969  188.615333
-  2023-04-20  166.165   169.70  160.5600    162.99  210970819.0  176.841313  186.556000
-  2023-04-21  164.800   166.00  161.3208    165.08  123538954.0  172.920875  184.542667
-  2023-04-24  164.650   165.65  158.6100    162.55  140006559.0  169.463917  181.548667
-  ...             ...      ...       ...       ...          ...         ...         ...
-  2023-08-14  235.700   240.66  233.7500    239.76   98595331.0  244.249615  254.577333
-  2023-08-15  238.730   240.50  232.6100    232.96   88197599.0  240.486410  252.422667
-  2023-08-16  228.020   233.97  225.3800    225.60  112484520.0  235.524273  249.839333
-  2023-08-17  226.060   226.74  218.8300    219.22  120718417.0  230.089516  247.406667
-  2023-08-18  214.120   217.58  212.3600    215.49  136276584.0  225.223010  244.010000
-- [86 rows x 7 columns], 'ending_aum': 152490.91942089135, 'dollar_change': 52490.919420891354, 'percent_change': 52.49091942089136}
+### Data.py
+- `Candle`, `CandleContainer`: Represent market data.
+- `FinancialInstrument`: Represents a financial instrument with associated market data.
+- `Indicator`, `BollingerBands`, `NormalizedPrices`, `SMA`: Various financial indicators.
+- `DataAcquisition`: Handles data acquisition from external sources.
 
----
+## Contributing
+Contributions are welcome! Please read our contributing guidelines to get started.
 
-### **6. Conclusion**
+## License
+This project is licensed under the Apache License - see the LICENSE file for details.
 
-OpenFintech, with its emphasis on the mean reversion strategy, provides an unparalleled platform for budding financial analysts and enthusiasts to test, refine, and understand the nuances of algorithmic trading. Whether you're a student or an expert, OpenFintech's extensive features ensure that you're equipped with the tools needed to delve deep into the world of algorithmic trading.
+## Authors and Acknowledgment
+- Harri Sivakumar
+- Ken Wu
+- Basil Ahmad
+- Acknowledgments to other contributors and organizations.
 
----
+## FAQs/Contact Information
+For any queries, please reach out to us at team@wlufintech.com.
\ No newline at end of file
diff --git a/Test.db b/Test.db
deleted file mode 100644
index c3d96d2..0000000
Binary files a/Test.db and /dev/null differ
diff --git a/main.py b/main.py
index 1631c7c..1757225 100644
--- a/main.py
+++ b/main.py
@@ -1,48 +1,47 @@
-import os 
-from dotenv import load_dotenv
-from OpenFintech import Model
-import pandas as pd
-import matplotlib.pyplot as plt
-
-load_dotenv() # Load ENV variables and set them down below
-# Populate settings dictionary to be passed into Model.backtest() (NOTE: Will be user input fields on the form) 
-setting_values={"user_id": 1,
-                "starting_aum": 100000, 
-                "short": "EMA 5",
-                "long_": "SMA 15",
-                "ticker": "GOOGL",
-                "stop_loss": 0.05, #%
-                "take_profit": 0.5,#%
-                "chart_freq_mins": 0} 
-handler = Model()
-# Call the backtest function with the setting along with the configuration
-response = handler.backtest(setting_values, os.getenv('ALPHAVANTAGE_KEY'))
-handler.handler.disconnect() # Closes the database
-
-
-print(response)
-
-df: pd.DataFrame = response["price_data"]
-
-print(df)
-
-# Set general graph properties (the dates are used as the default X domain)
-plt.figure(figsize=(12, 6))
-
-# Add the data to the graph
-plt.plot(df['4. close'], label='Close', marker='o', color='blue')
-plt.plot(df[f'6. {setting_values["short"].replace(" ", "")}'], label=setting_values["short"].replace(" ", ""), linestyle='--', color='red')
-plt.plot(df[f'7. {setting_values["long_"].replace(" ", "")}'], label=setting_values["long_"].replace(" ", ""), linestyle='--', color='green')
-
-# Finish adding general graph properties (that may act on price data) and ..
-plt.xticks(rotation=45)
-plt.tight_layout()
-plt.grid(True)
-plt.xlabel('Date')
-plt.ylabel('Price')
-plt.legend()
-plt.show() # Display the graph 
-
-print(response["trade_data"])
-
-# TODO: Save the graph as a JPEG (as this system will be used to simplify the graph displaying process)
\ No newline at end of file
+from OpenFintech import *
+
+# Example of how to use the OpenFintech library
+
+# Initialize the API wrapper
+apiKey = "6HZIS76ZQIS16EIG"
+data_acq = DataAcquisition(apiKey)
+
+# Request data from the API
+ticker_symbol = 'AAPL'
+timeframe = 'daily' # options: 'daily' or {time}min, e.g. '1min', '5min', '15min', '30min', '60min
+tickerData = data_acq.requestDataFromAPI(ticker_symbol, timeframe)
+
+# Convert the data to a FinancialInstrument
+ticker_finInst = data_acq.convertDataToFinancialInstrument(tickerData)
+
+# Run calculations on the FinancialInstrument
+shortMA = SMA(candle_container = ticker_finInst.candle_container, periodLength = 5)
+longMA = SMA(candle_container = ticker_finInst.candle_container, periodLength = 10)
+
+shortMA.runCalcOnCandleContainer()
+longMA.runCalcOnCandleContainer()
+
+# Run an algorithm on the FinancialInstrument
+tr_algo = TrendFollowing()
+tr_backtest_data = tr_algo.runAlgorithmOnCandleContainer(
+                                    candle_container = ticker_finInst.candle_container,
+                                    short_ma = shortMA,
+                                    long_ma = longMA,
+                                    stop_loss = 0.05,
+                                    take_profit = 0.1,
+                                    assets = 10000
+                                    )
+
+print(tr_backtest_data)
+
+mr_algo = MeanReversion()
+mr_backtest_data = mr_algo.runAlgorithmOnCandleContainer(
+                                    candle_container = ticker_finInst.candle_container,
+                                    short_ma = shortMA,
+                                    long_ma = longMA,
+                                    stop_loss = 0.05,
+                                    take_profit = 0.1,
+                                    assets = 10000
+                                    )
+
+print(mr_backtest_data)
\ No newline at end of file
diff --git a/setup.py b/setup.py
index a5bccf1..afd2526 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 
 setup(
     name="OpenFintech",
-    version="0.1.6",
+    version="0.1.9",
     author = 'Laurier Fintech',
     author_email = 'team@wlufintech.com',
     url = 'https://github.com/Laurier-Fintech/OpenFintech',


Test patch:
diff --git a/testing.ipynb b/testing.ipynb
new file mode 100644
index 0000000..be0b470
--- /dev/null
+++ b/testing.ipynb
@@ -0,0 +1,32 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "openfintech",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}