diff --git a/main.py b/main.py index 4fee1c7..46ea7cb 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,8 @@ "TSLA": "Tesla, Inc.", } -print(''' +print( + r""" /$$$$$$ /$$ /$$ /$$$$$$$ /$$__ $$| $$$ /$$$| $$__ $$ | $$ \ $$| $$$$ /$$$$| $$ \ $$ @@ -24,62 +25,71 @@ | $$$$$$/| $$ \/ | $$| $$ \____ $$$|__/ |__/|__/ \__/ -''') - +""" +) + # Display the list of popular ticker symbols to the user. -print('Popular Ticker Symbols:\n') +print("Popular Ticker Symbols:\n") for symbol, company in popular_ticker_symbols.items(): # Iterate through the dictionary of ticker symbols and print each one with its corresponding company name. print(f"{symbol} - {company}") # Prompt to guide users where they can find more ticker symbols. -print('\nFind more ticker symbols at https://stockanalysis.com/stocks/\n') +print("\nFind more ticker symbols at https://stockanalysis.com/stocks/\n") -while (True): +while True: # Request user input for the ticker symbol they're interested in. - ticker_symbol = input('Enter the ticker symbol: ') - if yf.Ticker(ticker_symbol).history(period='20y').empty: # Check if the ticker symbol is invalid + ticker_symbol = input("Enter the ticker symbol: ").strip().upper() + if not ticker_symbol: + print("Ticker symbol cannot be empty. Please try again.") continue - # Request user input for the period in years for which they want the data, with a constraint between 1 and 20 years. - period = input('How far back do you want data from, choose a period in years from 1 to 20: ') + if yf.Ticker(ticker_symbol).history(period="20y").empty: + print(f"Invalid ticker symbol: {ticker_symbol}. Please try again.") + continue - # Validate the period input to ensure it's a digit, and within the allowed range (1 to 20 years). - if (period.isdigit() == False) or (int(period) < 1) or (int(period) > 20): + # Request user input for the period in years for which they want the data, with a constraint between 1 and 20 years. + while True: + # Validate the period input to ensure it's a digit, and within the allowed range (1 to 20 years). + period = input("How far back do you want data from (1 to 20 years)? ") + if period.isdigit() and 1 <= int(period) <= 20: + period = int(period) + break # If the input is invalid, notify the user and terminate the program. - print('Invalid period') - continue + print("Invalid input. Please enter a number between 1 and 20.") - while (True): + while True: # Present the user with options for the functionality they wish to use. - print('1. Predict the future stock price') - print('2. Measure Stock Risk in Volatility') - print('3. Plot the historical stock data') - print('4. Change Ticker Symbol and/or Period') - print('5. Exit') + print("1. Predict the future stock price") + print("2. Measure Stock Risk in Volatility") + print("3. Plot the historical stock data") + print("4. Change Ticker Symbol and/or Period") + print("5. Exit") # Capture the user's choice. - option = input('Choose an option: ') + option = input("Choose an option: ") # Based on the user's choice, either predict future stock prices or plot historical data. - if option == '1': + if option == "1": # If option 1, request the number of days into the future for which the prediction is desired. - x_days = int(input('Enter the number of days into the future for the prediction: ')) + x_days = int( + input("Enter the number of days into the future for the prediction: ") + ) # Call the function to predict future price based on the inputs. predict_future_price(ticker_symbol, x_days, period) - elif option == '2': + elif option == "2": # If option 2, call the function to measure stock risk based on the ticker symbol and period. risk_score(ticker_symbol, period) - elif option == '3': + elif option == "3": # If option 2, call the function to plot historical data based on the ticker symbol and period. plot_data(ticker_symbol, period) - elif option == '4': + elif option == "4": # If option 3, break out of the current loop and prompt the user for a new ticker symbol and period. break - elif option == '5': + elif option == "5": # If option 4, terminate the program. exit() else: # If the user enters an invalid option, notify them. - print('Invalid option') + print("Invalid option") diff --git a/requirements.txt b/requirements.txt index ad0ebb6..690f532 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -numpy==1.24.4 -pandas==2.0.3 -scikit-learn==1.3.2 -yfinance==0.2.36 -matplotlib==3.7.4 +numpy>=1.24.4 +pandas>=2.0.3 +scikit-learn>=1.3.2 +yfinance>=0.2.36 +matplotlib>=3.7.4 \ No newline at end of file diff --git a/scripts/__init__.py b/scripts/__init__.py index 52fc70f..1957f05 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -1,3 +1,3 @@ from .predict_future_price import predict_future_price from .risk_score import risk_score -from .plot_data import plot_data \ No newline at end of file +from .plot_data import plot_data diff --git a/scripts/__pycache__/__init__.cpython-313.pyc b/scripts/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e77d618 Binary files /dev/null and b/scripts/__pycache__/__init__.cpython-313.pyc differ diff --git a/scripts/__pycache__/plot_data.cpython-313.pyc b/scripts/__pycache__/plot_data.cpython-313.pyc new file mode 100644 index 0000000..1a47ae4 Binary files /dev/null and b/scripts/__pycache__/plot_data.cpython-313.pyc differ diff --git a/scripts/__pycache__/predict_future_price.cpython-313.pyc b/scripts/__pycache__/predict_future_price.cpython-313.pyc new file mode 100644 index 0000000..8fb0093 Binary files /dev/null and b/scripts/__pycache__/predict_future_price.cpython-313.pyc differ diff --git a/scripts/__pycache__/risk_score.cpython-313.pyc b/scripts/__pycache__/risk_score.cpython-313.pyc new file mode 100644 index 0000000..e636237 Binary files /dev/null and b/scripts/__pycache__/risk_score.cpython-313.pyc differ diff --git a/scripts/plot_data.py b/scripts/plot_data.py index ea08b4c..1c4fe2e 100644 --- a/scripts/plot_data.py +++ b/scripts/plot_data.py @@ -1,32 +1,40 @@ import yfinance as yf import matplotlib.pyplot as plt + def plot_data(ticker_symbol, period) -> bool: """ Plots the historical stock data for a given stock based on its ticker symbol and the period in years. - + :param ticker_symbol: The stock's ticker symbol as a string. :param period: The period over which to calculate the risk score ('1mo', '3mo', '6mo', '1y', '2y', etc.). :return: True if the plot is successful, False otherwise. """ # Fetch historical stock data ticker = yf.Ticker(ticker_symbol) - ticker_data = ticker.history(period=f'{period}y') + ticker_data = ticker.history(period=f"{period}y") # Calculate Average - ticker_data['Average'] = (ticker_data['High'] + ticker_data['Low']) / 2 + ticker_data["Average"] = (ticker_data["High"] + ticker_data["Low"]) / 2 # Plot the historical stock data plt.figure(figsize=(14, 7)) - plt.plot(ticker_data.index, ticker_data['Average'], label='Daily Average Price', linewidth=1) + plt.plot( + ticker_data.index, + ticker_data["Average"], + label="Daily Average Price", + linewidth=1, + ) - plt.title(f'Daily Average Price of {ticker.info["longName"]} Stock Over the Last {period} Years') - plt.xlabel('Date') - plt.ylabel('Average Price (USD)') + plt.title( + f'Daily Average Price of {ticker.info["longName"]} Stock Over the Last {period} Years' + ) + plt.xlabel("Date") + plt.ylabel("Average Price (USD)") plt.legend() plt.grid(True) plt.tight_layout() plt.show() - return True; \ No newline at end of file + return True diff --git a/scripts/predict_future_price.py b/scripts/predict_future_price.py index 91c5987..cc13e8a 100644 --- a/scripts/predict_future_price.py +++ b/scripts/predict_future_price.py @@ -8,7 +8,7 @@ def predict_future_price(ticker_symbol, x_days, period) -> float: """ Predicts the future stock price for a given stock based on its ticker symbol and the number of days into the future. - + :param ticker_symbol: The stock's ticker symbol as a string. :param x_days: The number of days into the future for the prediction as an integer. :param period: The period over which to calculate the risk score ('1mo', '3mo', '6mo', '1y', '2y', etc.). @@ -16,18 +16,22 @@ def predict_future_price(ticker_symbol, x_days, period) -> float: """ # Fetch historical stock data ticker = yf.Ticker(ticker_symbol) - ticker_data = ticker.history(period=f'{period}y') + ticker_data = ticker.history(period=f"{period}y") # Preparing the data - ticker_data['Target'] = ticker_data['Close'].shift(-x_days) - ticker_data = ticker_data.dropna() # Dropping NA values to avoid issues in model training + ticker_data["Target"] = ticker_data["Close"].shift(-x_days) + ticker_data = ( + ticker_data.dropna() + ) # Dropping NA values to avoid issues in model training # Features and Labels - X = ticker_data[['Close']] - y = ticker_data['Target'] + X = ticker_data[["Close"]] + y = ticker_data["Target"] # Split the data - X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42 + ) # Linear Regression Model model = LinearRegression() @@ -38,16 +42,18 @@ def predict_future_price(ticker_symbol, x_days, period) -> float: # MSE mse = mean_squared_error(y_test, predictions) - print(f'Mean Squared Error: {mse}') + print(f"Mean Squared Error: {mse}") # Predicting the future price based on the latest price # Ensure the input for prediction matches the training data format - latest_price_df = pd.DataFrame({'Close': [ticker_data['Close'].iloc[-1]]}) + latest_price_df = pd.DataFrame({"Close": [ticker_data["Close"].iloc[-1]]}) # Predicting a single future price using DataFrame to match training format future_price = model.predict(latest_price_df) # Print the predicted future price - print(f'Predicted stock price for {ticker.info["longName"]} {x_days} days into the future: {future_price[0]:.2f}') + print( + f'Predicted stock price for {ticker.info["longName"]} {x_days} days into the future: {future_price[0]:.2f}' + ) return round(future_price[0], 2) diff --git a/scripts/risk_score.py b/scripts/risk_score.py index 8d430d4..5aa5053 100644 --- a/scripts/risk_score.py +++ b/scripts/risk_score.py @@ -1,24 +1,25 @@ import yfinance as yf import numpy as np + def risk_score(ticker_symbol, period) -> float: """ Calculates a risk score for a given stock based on its ticker symbol and history period. - + :param ticker_symbol: The stock's ticker symbol as a string. :param history_period: The period over which to calculate the risk score ('1mo', '3mo', '6mo', '1y', '2y', etc.). :return: The risk score as a float. """ # Fetch historical stock data ticker = yf.Ticker(ticker_symbol) - ticker_data = ticker.history(period=f'{period}y') - + ticker_data = ticker.history(period=f"{period}y") + # Calculate daily returns - daily_returns = ticker_data['Close'].pct_change().dropna() - + daily_returns = ticker_data["Close"].pct_change().dropna() + # Calculate the standard deviation of daily returns (volatility) volatility = daily_returns.std() - + # Annualize the volatility to get the risk score # Assuming 252 trading days in a year risk_score = volatility * np.sqrt(252) @@ -41,6 +42,6 @@ def risk_score(ticker_symbol, period) -> float: print("Risk Level: Very High Risk") elif 0.6 < risk_score: print("Risk Level: Extreme Risk") - + # Return the risk score return round(risk_score, 4) diff --git a/setup.py b/setup.py index 42f8832..b44c374 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ from setuptools import setup, find_packages setup( - name='Quant-Market-Predictor', - version='0.1', + name="Quant-Market-Predictor", + version="0.1", packages=find_packages(), install_requires=[], ) diff --git a/tests/__pycache__/test_plot_data.cpython-313.pyc b/tests/__pycache__/test_plot_data.cpython-313.pyc new file mode 100644 index 0000000..7dc8c5b Binary files /dev/null and b/tests/__pycache__/test_plot_data.cpython-313.pyc differ diff --git a/tests/__pycache__/test_predict_future_price.cpython-313.pyc b/tests/__pycache__/test_predict_future_price.cpython-313.pyc new file mode 100644 index 0000000..f06e8b8 Binary files /dev/null and b/tests/__pycache__/test_predict_future_price.cpython-313.pyc differ diff --git a/tests/__pycache__/test_risk_score.cpython-313.pyc b/tests/__pycache__/test_risk_score.cpython-313.pyc new file mode 100644 index 0000000..f01a975 Binary files /dev/null and b/tests/__pycache__/test_risk_score.cpython-313.pyc differ diff --git a/tests/test_plot_data.py b/tests/test_plot_data.py index cdcab38..fb3f636 100644 --- a/tests/test_plot_data.py +++ b/tests/test_plot_data.py @@ -6,11 +6,14 @@ from scripts import plot_data + class test_plot_data(unittest.TestCase): - def test_plot_data(self): - self.assertTrue(plot_data('aapl', 10)) - self.assertTrue(plot_data('msft', 5)) - self.assertTrue(plot_data('amzn', 17)) + def test_plot_data_runs(self): + """Ensure plot_data runs without errors and returns True.""" + self.assertTrue(plot_data("aapl", 10)) + self.assertTrue(plot_data("msft", 5)) + self.assertTrue(plot_data("amzn", 17)) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_predict_future_price.py b/tests/test_predict_future_price.py index 8e0c26b..e76a269 100644 --- a/tests/test_predict_future_price.py +++ b/tests/test_predict_future_price.py @@ -6,11 +6,18 @@ from scripts import predict_future_price + class test_predict_future_price(unittest.TestCase): - def test_predict_future_price(self): - self.assertIsNotNone(predict_future_price('aapl', 50, 10)) - self.assertIsNotNone(predict_future_price('msft', 75, 5)) - self.assertIsNotNone(predict_future_price('amzn', 300, 17)) + def test_prediction_returns_float(self): + """Ensure predict_future_price returns a float.""" + prediction = predict_future_price("AAPL", 30, 5) + self.assertIsInstance(prediction, float) + + def test_prediction_is_positive(self): + """Ensure predicted stock price is greater than zero.""" + prediction = predict_future_price("MSFT", 30, 5) + self.assertGreater(prediction, 0) + -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_risk_score.py b/tests/test_risk_score.py index 26c5a84..a453ddf 100644 --- a/tests/test_risk_score.py +++ b/tests/test_risk_score.py @@ -6,11 +6,19 @@ from scripts import risk_score + class test_risk_score(unittest.TestCase): - def test_risk_score(self): - self.assertIsNotNone(risk_score('aapl', 10)) - self.assertIsNotNone(risk_score('msft', 5)) - self.assertIsNotNone(risk_score('amzn', 17)) + def test_risk_score_returns_float(self): + """Ensure risk_score returns a float.""" + risk = risk_score("AAPL", 5) + self.assertIsInstance(risk, float) + + def test_risk_score_is_within_range(self): + """Ensure risk score is within reasonable bounds (0-2).""" + risk = risk_score("MSFT", 5) + self.assertGreaterEqual(risk, 0) + self.assertLessEqual(risk, 2) + -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main()