diff --git a/requirements.txt b/requirements.txt index 1f56006..2b13acd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ pydantic==2.7.0 numpy~=1.24 scipy==1.13.1 TA-Lib -matplotlib \ No newline at end of file +matplotlib +yfinance~=0.2 \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index f9985c9..c5154f7 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -8,4 +8,5 @@ invoke==2.2.0 black==24.4.0 flake8==7.0.0 blacken-docs==1.16.0 -setuptools==71.1.0 \ No newline at end of file +setuptools==71.1.0 +pandas-stubs \ No newline at end of file diff --git a/requirements_lock.txt b/requirements_lock.txt index 2b827cd..cb6bd30 100644 --- a/requirements_lock.txt +++ b/requirements_lock.txt @@ -1,9 +1,10 @@ -alembic==1.13.1 -annotated-types==0.6.0 +annotated-types==0.7.0 autoflake==2.3.1 +beautifulsoup4==4.12.3 black==24.4.0 blacken-docs==1.16.0 -certifi==2024.2.2 +build==1.2.1 +certifi==2024.7.4 charset-normalizer==3.3.2 click==8.1.7 contourpy==1.2.1 @@ -11,8 +12,10 @@ cycler==0.12.1 docutils==0.21.2 flake8==7.0.0 fonttools==4.53.1 +frozendict==2.4.4 +html5lib==1.1 idna==3.7 -importlib_metadata==7.1.0 +importlib_metadata==8.2.0 iniconfig==2.0.0 invoke==2.2.0 isort==5.13.2 @@ -21,44 +24,53 @@ jaraco.context==5.3.0 jaraco.functools==4.0.1 keyring==25.2.1 kiwisolver==1.4.5 -Mako==1.3.5 +lxml==5.2.2 markdown-it-py==3.0.0 -MarkupSafe==2.1.5 matplotlib==3.9.1 mccabe==0.7.0 mdurl==0.1.2 -more-itertools==10.2.0 +more-itertools==10.3.0 +multitasking==0.0.11 mypy==1.9.0 mypy-extensions==1.0.0 -nh3==0.2.17 +nh3==0.2.18 numpy==1.26.4 packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240603 pathspec==0.12.1 +peewee==3.17.6 pillow==10.4.0 -pkginfo==1.10.0 +pip-tools==7.4.1 +pkginfo==1.11.1 platformdirs==4.2.2 pluggy==1.5.0 -psycopg2-binary==2.9.9 pycodestyle==2.11.1 pydantic==2.7.0 pydantic_core==2.18.1 pyflakes==3.2.0 Pygments==2.18.0 pyparsing==3.1.2 +pyproject_hooks==1.1.0 pytest==8.1.1 python-dateutil==2.9.0.post0 -readme_renderer==43.0 -requests==2.31.0 +pytz==2024.1 +readme_renderer==44.0 +requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==13.7.1 scipy==1.13.1 setuptools==71.1.0 six==1.16.0 -SQLAlchemy==2.0.30 +soupsieve==2.5 TA-Lib==0.4.32 twine==5.0.0 -typing_extensions==4.11.0 -urllib3==2.2.1 +types-pytz==2024.1.0.20240417 +typing_extensions==4.12.2 +tzdata==2024.1 +urllib3==2.2.2 +webencodings==0.5.1 wheel==0.43.0 -zipp==3.18.2 +yfinance==0.2.41 +zipp==3.19.2 diff --git a/typings/talib/abstract/__init__.pyi b/typings/talib/abstract/__init__.pyi index 05147c2..49ede77 100644 --- a/typings/talib/abstract/__init__.pyi +++ b/typings/talib/abstract/__init__.pyi @@ -1,4 +1,7 @@ from typing import Any -from numpy import ndarray, dtype, floating, float64 +from numpy import ndarray, dtype, float64 -def OBV(close: list[float] | ndarray[Any, dtype[float64]], volume: list[float] | ndarray[Any, dtype[float64]]) -> list[float]: ... +def OBV( + close: list[float] | ndarray[Any, dtype[float64]], + volume: list[float] | ndarray[Any, dtype[float64]], +) -> list[float]: ... diff --git a/typings/yfinance/__init__.pyi b/typings/yfinance/__init__.pyi new file mode 100644 index 0000000..26741eb --- /dev/null +++ b/typings/yfinance/__init__.pyi @@ -0,0 +1,24 @@ +from typing import Any +import pandas as pd + +def download( + tickers: str, + start: str | None = None, + end: str | None = None, + actions: bool = False, + threads: bool = True, + ignore_tz: bool | None = None, + group_by: str = "column", + auto_adjust: bool = False, + back_adjust: bool = False, + repair: bool = False, + keepna: bool = False, + progress: bool = True, + period: str = "max", + interval: str = "1d", + prepost: bool = False, + proxy: str | None = None, + rounding: bool = False, + timeout: int | float = 10, + session: Any | None = None, +) -> pd.DataFrame: ... diff --git a/xarizmi/opendata/__init__.py b/xarizmi/opendata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/xarizmi/opendata/yahoo_finance.py b/xarizmi/opendata/yahoo_finance.py new file mode 100644 index 0000000..fdf0872 --- /dev/null +++ b/xarizmi/opendata/yahoo_finance.py @@ -0,0 +1,75 @@ +"""A client to download Yahoo Finance data +""" + +from datetime import datetime + +import pandas as pd +import yfinance as yf + +from xarizmi.candlestick import CandlestickChart + + +class YahooFinanceDailyDataClient: + + def __init__( + self, + symbol: str, + start_date: str = "1900-01-01", + end_date: str | None = None, + ) -> None: + self.symbol = symbol + self.start_date = start_date + self.end_date = end_date + + def extract(self) -> list[dict[str, str | float | pd.Timestamp]] | None: + stock_data: pd.DataFrame = yf.download( + self.symbol, start=self.start_date, end=self.end_date + ) + + if stock_data.empty: + print("Error fetching data for symbol:", self.symbol) + return None + + # Reset the index to get 'Date' as a column + stock_data.reset_index(inplace=True) + + return stock_data.to_dict(orient="records") # type: ignore + + def transform( + self, data_list: list[dict[str, str | float | pd.Timestamp]] + ) -> list[dict[str, str | float | datetime | dict[str, dict[str, str]]]]: + candles_data = [] + for single_candle_data in data_list: + temp = {} + temp["open"] = single_candle_data["Open"] + temp["high"] = single_candle_data["High"] + temp["low"] = single_candle_data["Low"] + temp["close"] = single_candle_data["Close"] + temp["volume"] = single_candle_data["Volume"] + temp["datetime"] = single_candle_data["Date"].to_pydatetime() # type: ignore # noqa:E501 + temp["interval_type"] = "1day" + temp["symbol"] = { + "base_currency": {"name": self.symbol}, + "quote_currency": {"name": "CAD"}, + "fee_currency": {"name": "CAD"}, + } # type: ignore + candles_data.append(temp) + return candles_data # type: ignore + + def save_file( + self, + candles_data: list[ + dict[str, str | float | datetime | dict[str, dict[str, str]]] + ], + filepath: str, + indent: int = 4, + ) -> None: + candles = CandlestickChart.model_validate({"candles": candles_data}) + with open(filepath, "w") as f: + f.write(candles.model_dump_json(indent=indent)) + + def etl(self, filepath: str) -> None: + data_list = self.extract() + if data_list: + candles_data = self.transform(data_list=data_list) + self.save_file(candles_data=candles_data, filepath=filepath)