diff --git a/paperone/database.py b/paperone/database.py index 7c76137..a20a4f5 100644 --- a/paperone/database.py +++ b/paperone/database.py @@ -33,7 +33,8 @@ class TradingDataCRUD: @contextmanager def get_session(self): """Context manager for database sessions with automatic cleanup.""" - session = Session(self.engine) + session = Session(self.engine, expire_on_commit=False) + try: yield session session.commit() diff --git a/paperone/indicators.py b/paperone/indicators.py new file mode 100644 index 0000000..3482063 --- /dev/null +++ b/paperone/indicators.py @@ -0,0 +1,360 @@ +import talib +import numpy as np +from typing import List +from datetime import datetime, timedelta +from .models import ( + TickerOHLCV, + IndicatorsData, + MarketRegimeIndicators, + MomentumIndicators, + VolatilityIndicators, + VolumeIndicators, + SupportResistanceIndicators, + TrendIndicators, + OHLCVArrays, +) +from .database import TradingDataCRUD + + +class IndicatorService: + """ + Middleware service for calculating technical indicators from stored OHLCV data. + + This service: + 1. Fetches historical OHLCV data from the database + 2. Calculates all 29 technical indicators using TA-Lib + 3. Returns strongly-typed IndicatorsData ready to be saved + + All calculations are done locally without external API calls. + """ + + def __init__(self, crud: TradingDataCRUD): + """ + Initialize the indicator service with database access. + + Args: + crud: TradingDataCRUD instance for database operations + """ + self._crud: TradingDataCRUD = crud + + def calculate_indicators_for_date( + self, ticker: str, target_date: datetime, lookback_days: int = 50 + ) -> IndicatorsData | None: + """ + Calculate all indicators for a ticker on a specific date using stored OHLCV data. + + Args: + ticker: Stock ticker symbol + target_date: Date to calculate indicators for + lookback_days: Number of historical days needed for calculations (default: 50) + + Returns: + IndicatorsData instance ready to save to database, or None if insufficient data + """ + # Fetch historical OHLCV data from database + start_date: datetime = target_date - timedelta( + days=lookback_days * 2 + ) # Buffer for weekends + ohlcv_records: List[TickerOHLCV] = self._crud.get_ohlcv_range( + ticker=ticker, start_date=start_date, end_date=target_date + ) + + # Verify we have enough data + if len(ohlcv_records) < 30: + return None + + # Convert to numpy arrays for TA-Lib + arrays: OHLCVArrays = self._convert_to_arrays(ohlcv_records) + + # Calculate all indicator categories + momentum: MomentumIndicators = self._calculate_momentum(arrays) + volatility: VolatilityIndicators = self._calculate_volatility(arrays) + trend: TrendIndicators = self._calculate_trend(arrays) + volume: VolumeIndicators = self._calculate_volume(arrays) + support_resistance: SupportResistanceIndicators = ( + self._calculate_support_resistance(arrays) + ) + market_regime: MarketRegimeIndicators = self._calculate_market_regime(arrays) + + # Build and return strongly-typed IndicatorsData + return IndicatorsData( + ticker=ticker, + date=target_date, + + # Momentum (7 indicators) + + rsi_14=momentum.rsi_14, + rsi_20=momentum.rsi_20, + macd_line=momentum.macd_line, + macd_signal=momentum.macd_signal, + macd_histogram=momentum.macd_histogram, + stoch_k=momentum.stoch_k, + stoch_d=momentum.stoch_d, + + # Volatility (6 indicators) + + bb_upper=volatility.bb_upper, + bb_middle=volatility.bb_middle, + bb_lower=volatility.bb_lower, + bb_width=volatility.bb_width, + bb_percent=volatility.bb_percent, + atr_14=volatility.atr_14, + + # Trend (4 indicators) + + adx_14=trend.adx_14, + di_plus=trend.di_plus, + di_minus=trend.di_minus, + sar=trend.sar, + + # Volume (3 indicators) + + obv=volume.obv, + obv_sma_20=volume.obv_sma_20, + volume_roc_5=volume.volume_roc_5, + + # Support/Resistance (6 indicators) + + fib_236=support_resistance.fib_236, + fib_382=support_resistance.fib_382, + fib_618=support_resistance.fib_618, + pivot_point=support_resistance.pivot_point, + resistance_1=support_resistance.resistance_1, + support_1=support_resistance.support_1, + + # Market Regime (2 indicators) + + cci_20=market_regime.cci_20, + williams_r_14=market_regime.williams_r_14, + ) + + def calculate_and_save_indicators( + self, ticker: str, target_date: datetime + ) -> IndicatorsData | None: + """ + Calculate indicators and immediately save them to the database. + + Args: + ticker: Stock ticker symbol + target_date: Date to calculate indicators for + + Returns: + Saved IndicatorsData instance or None if calculation failed + """ + indicators: IndicatorsData | None = self.calculate_indicators_for_date( + ticker=ticker, target_date=target_date + ) + + if indicators is None: + return None + + # Upsert (insert or update if exists) + return self._crud.upsert_indicators(indicators) + + def bulk_calculate_indicators( + self, ticker: str, dates: List[datetime] + ) -> List[IndicatorsData]: + """ + Calculate indicators for multiple dates efficiently. + + Args: + ticker: Stock ticker symbol + dates: List of dates to calculate indicators for + + Returns: + List of IndicatorsData instances (may be less than input if some failed) + """ + results: List[IndicatorsData] = [] + + for date in dates: + indicators: IndicatorsData | None = self.calculate_indicators_for_date( + ticker=ticker, target_date=date + ) + if indicators is not None: + results.append(indicators) + + return results + + def bulk_calculate_and_save_indicators( + self, ticker: str, dates: List[datetime] + ) -> int: + """ + Calculate and save indicators for multiple dates efficiently. + + Args: + ticker: Stock ticker symbol + dates: List of dates to calculate indicators for + + Returns: + Number of indicator records successfully saved + """ + indicators_list: List[IndicatorsData] = self.bulk_calculate_indicators( + ticker=ticker, dates=dates + ) + + if not indicators_list: + return 0 + + return self._crud.create_indicators_bulk(indicators_list) + + # ======================================================================== + # PRIVATE: Data Conversion + # ======================================================================== + + @staticmethod + def _convert_to_arrays(ohlcv_records: List[TickerOHLCV]) -> "OHLCVArrays": + """Convert list of TickerOHLCV records to numpy arrays for TA-Lib.""" + return OHLCVArrays( + open=np.array([r.open for r in ohlcv_records], dtype=np.float64), + high=np.array([r.high for r in ohlcv_records], dtype=np.float64), + low=np.array([r.low for r in ohlcv_records], dtype=np.float64), + close=np.array([r.close for r in ohlcv_records], dtype=np.float64), + volume=np.array([r.volume for r in ohlcv_records], dtype=np.float64), + ) + + # ======================================================================== + # PRIVATE: Indicator Calculations + # ======================================================================== + + @staticmethod + def _calculate_momentum(arrays: "OHLCVArrays") -> "MomentumIndicators": + """Calculate momentum indicators (RSI, MACD, Stochastic).""" + rsi_14: float = float(talib.RSI(arrays.close, timeperiod=14)[-1]) + rsi_20: float = float(talib.RSI(arrays.close, timeperiod=20)[-1]) + + macd_line_arr, macd_signal_arr, macd_hist_arr = talib.MACD( + arrays.close, fastperiod=12, slowperiod=26, signalperiod=9 + ) + + stoch_k_arr, stoch_d_arr = talib.STOCH( + arrays.high, + arrays.low, + arrays.close, + fastk_period=14, + slowk_period=3, + slowd_period=3, + ) + + return MomentumIndicators( + rsi_14=round(rsi_14, 2), + rsi_20=round(rsi_20, 2), + macd_line=round(float(macd_line_arr[-1]), 4), + macd_signal=round(float(macd_signal_arr[-1]), 4), + macd_histogram=round(float(macd_hist_arr[-1]), 4), + stoch_k=round(float(stoch_k_arr[-1]), 2), + stoch_d=round(float(stoch_d_arr[-1]), 2), + ) + + @staticmethod + def _calculate_volatility(arrays: "OHLCVArrays") -> "VolatilityIndicators": + """Calculate volatility indicators (Bollinger Bands, ATR).""" + bb_upper_arr, bb_middle_arr, bb_lower_arr = talib.BBANDS( + arrays.close, timeperiod=20, nbdevup=2, nbdevdn=2 + ) + + bb_upper: float = float(bb_upper_arr[-1]) + bb_middle: float = float(bb_middle_arr[-1]) + bb_lower: float = float(bb_lower_arr[-1]) + bb_width: float = bb_upper - bb_lower + bb_percent: float = ( + (arrays.close[-1] - bb_lower) / (bb_upper - bb_lower) + if bb_width > 0 + else 0.5 + ) + + atr_14: float = float( + talib.ATR(arrays.high, arrays.low, arrays.close, timeperiod=14)[-1] + ) + + return VolatilityIndicators( + bb_upper=round(bb_upper, 2), + bb_middle=round(bb_middle, 2), + bb_lower=round(bb_lower, 2), + bb_width=round(bb_width, 2), + bb_percent=round(bb_percent, 4), + atr_14=round(atr_14, 2), + ) + + @staticmethod + def _calculate_trend(arrays: "OHLCVArrays") -> "TrendIndicators": + """Calculate trend indicators (ADX, DI+, DI-, SAR).""" + adx_14: float = float( + talib.ADX(arrays.high, arrays.low, arrays.close, timeperiod=14)[-1] + ) + di_plus: float = float( + talib.PLUS_DI(arrays.high, arrays.low, arrays.close, timeperiod=14)[-1] + ) + di_minus: float = float( + talib.MINUS_DI(arrays.high, arrays.low, arrays.close, timeperiod=14)[-1] + ) + sar: float = float( + talib.SAR(arrays.high, arrays.low, acceleration=0.02, maximum=0.2)[-1] + ) + + return TrendIndicators( + adx_14=round(adx_14, 2), + di_plus=round(di_plus, 2), + di_minus=round(di_minus, 2), + sar=round(sar, 2), + ) + + @staticmethod + def _calculate_volume(arrays: "OHLCVArrays") -> "VolumeIndicators": + """Calculate volume indicators (OBV, Volume ROC).""" + obv_arr: np.ndarray = talib.OBV(arrays.close, arrays.volume) + obv: float = float(obv_arr[-1]) + obv_sma_20: float = float(talib.SMA(obv_arr, timeperiod=20)[-1]) + volume_roc_5: float = float(talib.ROC(arrays.volume, timeperiod=5)[-1]) + + return VolumeIndicators( + obv=round(obv, 0), + obv_sma_20=round(obv_sma_20, 0), + volume_roc_5=round(volume_roc_5, 2), + ) + + @staticmethod + def _calculate_support_resistance( + arrays: "OHLCVArrays", + ) -> "SupportResistanceIndicators": + """Calculate support/resistance levels (Fibonacci, Pivot Points).""" + # Fibonacci retracements (last 30 days) + lookback: int = min(30, len(arrays.high)) + max_price: float = float(np.max(arrays.high[-lookback:])) + min_price: float = float(np.min(arrays.low[-lookback:])) + diff: float = max_price - min_price + + fib_236: float = max_price - (diff * 0.236) + fib_382: float = max_price - (diff * 0.382) + fib_618: float = max_price - (diff * 0.618) + + # Pivot points (previous day's data) + high_prev: float = float(arrays.high[-1]) + low_prev: float = float(arrays.low[-1]) + close_prev: float = float(arrays.close[-1]) + + pivot_point: float = (high_prev + low_prev + close_prev) / 3 + resistance_1: float = (2 * pivot_point) - low_prev + support_1: float = (2 * pivot_point) - high_prev + + return SupportResistanceIndicators( + fib_236=round(fib_236, 2), + fib_382=round(fib_382, 2), + fib_618=round(fib_618, 2), + pivot_point=round(pivot_point, 2), + resistance_1=round(resistance_1, 2), + support_1=round(support_1, 2), + ) + + @staticmethod + def _calculate_market_regime(arrays: "OHLCVArrays") -> "MarketRegimeIndicators": + """Calculate market regime indicators (CCI, Williams %R).""" + cci_20: float = float( + talib.CCI(arrays.high, arrays.low, arrays.close, timeperiod=20)[-1] + ) + williams_r_14: float = float( + talib.WILLR(arrays.high, arrays.low, arrays.close, timeperiod=14)[-1] + ) + + return MarketRegimeIndicators( + cci_20=round(cci_20, 2), williams_r_14=round(williams_r_14, 2) + ) diff --git a/paperone/models.py b/paperone/models.py index f3e8aa8..ff4edb4 100644 --- a/paperone/models.py +++ b/paperone/models.py @@ -1,6 +1,8 @@ from sqlmodel import SQLModel, Field, Relationship, ForeignKeyConstraint +from dataclasses import dataclass from datetime import datetime from typing import Optional +import numpy as np class TickerOHLCV(SQLModel, table=True): @@ -84,3 +86,78 @@ class IndicatorsData(SQLModel, table=True): # One-to-one relationship back to TickerOHLCV ohlcv: TickerOHLCV = Relationship(back_populates="indicators") + + +@dataclass(frozen=True) +class OHLCVArrays: + """Numpy arrays of OHLCV data for TA-Lib calculations.""" + + open: np.ndarray + high: np.ndarray + low: np.ndarray + close: np.ndarray + volume: np.ndarray + + +@dataclass(frozen=True) +class MomentumIndicators: + """Momentum indicator values (RSI, MACD, Stochastic).""" + + rsi_14: float + rsi_20: float + macd_line: float + macd_signal: float + macd_histogram: float + stoch_k: float + stoch_d: float + + +@dataclass(frozen=True) +class VolatilityIndicators: + """Volatility indicator values (Bollinger Bands, ATR).""" + + bb_upper: float + bb_middle: float + bb_lower: float + bb_width: float + bb_percent: float + atr_14: float + + +@dataclass(frozen=True) +class TrendIndicators: + """Trend indicator values (ADX, DI+, DI-, SAR).""" + + adx_14: float + di_plus: float + di_minus: float + sar: float + + +@dataclass(frozen=True) +class VolumeIndicators: + """Volume indicator values (OBV, Volume ROC).""" + + obv: float + obv_sma_20: float + volume_roc_5: float + + +@dataclass(frozen=True) +class SupportResistanceIndicators: + """Support/Resistance levels (Fibonacci, Pivot Points).""" + + fib_236: float + fib_382: float + fib_618: float + pivot_point: float + resistance_1: float + support_1: float + + +@dataclass(frozen=True) +class MarketRegimeIndicators: + """Market regime indicators (CCI, Williams %R).""" + + cci_20: float + williams_r_14: float diff --git a/poetry.lock b/poetry.lock index d074b26..4122d00 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -70,6 +70,27 @@ charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "build" +version = "1.3.0" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, + {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +packaging = ">=19.1" +pyproject_hooks = "*" + +[package.extras] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.11) ; python_version < \"3.10\"", "virtualenv (>=20.17) ; python_version >= \"3.10\" and python_version < \"3.14\"", "virtualenv (>=20.31) ; python_version >= \"3.14\""] + [[package]] name = "certifi" version = "2025.10.5" @@ -290,7 +311,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +markers = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -326,6 +347,88 @@ dev = ["charset_normalizer (>=3.3.2,<4.0)", "coverage (>=6.4.1,<7.0)", "cryptogr extra = ["lxml_html_clean", "markdownify (>=1.1.0)", "readability-lxml (>=0.8.1)"] test = ["charset_normalizer (>=3.3.2,<4.0)", "cryptography (>=42.0.5,<43.0)", "fastapi (==0.110.0)", "httpx (==0.23.1)", "proxy.py (>=2.4.3,<3.0)", "pytest (>=8.1.1,<9.0)", "pytest-asyncio (>=0.23.6,<1.0)", "pytest-trio (>=0.8.0,<1.0)", "python-multipart (>=0.0.9,<1.0)", "trio (>=0.25.0,<1.0)", "trustme (>=1.1.0,<2.0)", "typing_extensions", "uvicorn (>=0.29.0,<1.0)", "websockets (>=12.0,<13.0)"] +[[package]] +name = "cython" +version = "3.1.4" +description = "The Cython compiler for writing C extensions in the Python language." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cython-3.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:523110241408ef6511d897e9cebbdffb99120ac82ef3aea89baacce290958f93"}, + {file = "cython-3.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd34f960c3809fa2a7c3487ce9b3cb2c5bbc5ae2107f073a1a51086885958881"}, + {file = "cython-3.1.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90842e7fb8cddfd173478670297f6a6b3df090e029a31ea6ce93669030e67b81"}, + {file = "cython-3.1.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c88234303e2c15a5a88ae21c99698c7195433280b049aa2ad0ace906e6294dab"}, + {file = "cython-3.1.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f06b037f7c244dda9fc38091e87a68498c85c7c27ddc19aa84b08cf42a8a84a"}, + {file = "cython-3.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1aba748e9dcb9c0179d286cdb20215246c46b69cf227715e46287dcea8de7372"}, + {file = "cython-3.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:297b6042d764f68dc6213312578ef4b69310d04c963f94a489914efbf44ab133"}, + {file = "cython-3.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2ecf927e73bde50043d3a9fe3159f834b0e642b97e60a21018439fd25d344888"}, + {file = "cython-3.1.4-cp310-cp310-win32.whl", hash = "sha256:3d940d603f85732627795518f9dba8fa63080d8221bb5f477c7a156ee08714ad"}, + {file = "cython-3.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:1e0671be9859bb313d8df5ca9b9c137e384f1e025831c58cee9a839ace432d3c"}, + {file = "cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e"}, + {file = "cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e"}, + {file = "cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2"}, + {file = "cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0"}, + {file = "cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e"}, + {file = "cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc"}, + {file = "cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601"}, + {file = "cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf"}, + {file = "cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5"}, + {file = "cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2"}, + {file = "cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9"}, + {file = "cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b"}, + {file = "cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc"}, + {file = "cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677"}, + {file = "cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9"}, + {file = "cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d"}, + {file = "cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97"}, + {file = "cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8"}, + {file = "cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8"}, + {file = "cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12"}, + {file = "cython-3.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ab549d0fc187804e0f14fc4759e4b5ad6485ffc01554b2f8b720cc44aeb929cd"}, + {file = "cython-3.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52eae5d9bcc515441a436dcae2cbadfd00c5063d4d7809bd0178931690c06a76"}, + {file = "cython-3.1.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f06345cfa583dd17fff1beedb237853689b85aa400ea9e0db7e5265f3322d15"}, + {file = "cython-3.1.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5d915556c757212cb8ddd4e48c16f2ab481dbb9a76f5153ab26f418c3537eb5"}, + {file = "cython-3.1.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f3bb603f28b3c1df66baaa5cdbf6029578552b458f1d321bae23b87f6c3199"}, + {file = "cython-3.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7aff230893ee1044e7bc98d313c034ead70a3dd54d4d22e89ca1734540d94084"}, + {file = "cython-3.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e83f114c04f72f85591ddb0b28f08ab2e40d250c26686d6509c0f70a9e2ca34"}, + {file = "cython-3.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8096394960d38b793545753b73781bc0ec695f0b8c22454431704b297e296045"}, + {file = "cython-3.1.4-cp313-cp313-win32.whl", hash = "sha256:4e7c726ac753ca1a5aa30286cbadcd10ed4b4312ea710a8a16bb908d41e9c059"}, + {file = "cython-3.1.4-cp313-cp313-win_amd64.whl", hash = "sha256:f2ee2bb77943044f301cec04d0b51d8e3810507c9c250d6cd079a3e2d6ba88f2"}, + {file = "cython-3.1.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7258739d5560918741cb040bd85ba7cc2f09d868de9116a637e06714fec1f69"}, + {file = "cython-3.1.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b2d522ee8d3528035e247ee721fb40abe92e9ea852dc9e48802cec080d5de859"}, + {file = "cython-3.1.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4e0560baeb56c29d7d8d693a050dd4d2ed922d8d7c66f5c5715c6f2be84e903"}, + {file = "cython-3.1.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4223cacc81cba0df0f06f79657c5d6286e153b9a9b989dad1cdf4666f618c073"}, + {file = "cython-3.1.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff4d1f159edee6af38572318681388fbd6448b0d08b9a47494aaf0b698e93394"}, + {file = "cython-3.1.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2537c53071a9a124e0bc502a716e1930d9bb101e94c26673016cf1820e4fdbd1"}, + {file = "cython-3.1.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:85416717c529fb5ccf908464657a5187753e76d7b6ffec9b1c2d91544f6c3628"}, + {file = "cython-3.1.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:18882e2f5c0e0c25f9c44f16f2fb9c48f33988885c5f9eae2856f10c6f089ffa"}, + {file = "cython-3.1.4-cp314-cp314-win32.whl", hash = "sha256:8ef8deadc888eaf95e5328fc176fb6c37bccee1213f07517c6ea55b5f817c457"}, + {file = "cython-3.1.4-cp314-cp314-win_amd64.whl", hash = "sha256:acb99ddec62ba1ea5de0e0087760fa834ec42c94f0488065a4f1995584e8e94e"}, + {file = "cython-3.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:949074a2445c8fe2b84e81b6cf9c23b30b92e853ad05689457d6735acb2ca738"}, + {file = "cython-3.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:533970de5d96ca9ba207aa096caa883e2365ce7d41f0531f070292842b4ba97a"}, + {file = "cython-3.1.4-cp38-cp38-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078e4e48d3867abf1ba11ca6bd2d62974a326ef2c0d139daa6691f5e01a691c5"}, + {file = "cython-3.1.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c162d63c2a7636864bcee0f333d974ece325d4fbc33e75d38886926e1cc997a1"}, + {file = "cython-3.1.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10357ea1acc27b42d83aaeb14cd94587b515806a41227a0100028d45140d753"}, + {file = "cython-3.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:71435feecca14f4de4b3a6b3828d8b1ae4a3b535e53597c32d5f22bc87f3d2bb"}, + {file = "cython-3.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d24c8a4fb10c089ec45fcc1c44f73df5b4228c95f20539cc4aade7a8933be31c"}, + {file = "cython-3.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ccbd6db72f864c6717dcc62e44673e81cfe38984277748818cbe427f461573c"}, + {file = "cython-3.1.4-cp38-cp38-win32.whl", hash = "sha256:94fd9a7807fdd7cffe82c0b7167a9f5fcf0e7c6ef518d89bed66430bd9107854"}, + {file = "cython-3.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:b47a82cfbc8e6a87143f02e1b33b7a0332bd1b83b668cb41ebcb5e4f9a39bc09"}, + {file = "cython-3.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95d2303ee54cf469f7c61aa94ef46c195d42e75a76881b9e33770bcf6c0a5c6"}, + {file = "cython-3.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fc723ffca257beebe42c8e2dcb2b9771957b752d6f42684f17ca1d2a4346b21"}, + {file = "cython-3.1.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1fb5060dd0c89f5c64002a0c44c2fcb5d066d119e2ae4d1bfa2c6482333dd42a"}, + {file = "cython-3.1.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:077cc5881da9b48cc7b7f7952faedcea0ad9c3fcb9ba9f5fb89fdb5ded07dd70"}, + {file = "cython-3.1.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33da3c03797f7b7fde371a05f9e0d63eca679875c1c75e01066893eff2ec2f12"}, + {file = "cython-3.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daf1bde7af8202f52cfd51625a5c48de55de27786392e38a429bfe8b9a16161f"}, + {file = "cython-3.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3371af4af1aae0d46c10f8e3c1edff0361f03a5687081c8b36f61678545caff3"}, + {file = "cython-3.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d4e1115c6b4b848ade9d76a4e3f72c7bb2b7e7a935fcbda56f6032f50e079999"}, + {file = "cython-3.1.4-cp39-cp39-win32.whl", hash = "sha256:1b59709bcec2f38e447e53c51a20caee8c30911d4025dd3d102835f3f10b7bef"}, + {file = "cython-3.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:006e2a175ba9898a7f1551e6c7c4cafd8eb9b42e53d1dfe4308caba9e8453cc3"}, + {file = "cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6"}, + {file = "cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683"}, +] + [[package]] name = "decorator" version = "5.2.1" @@ -734,6 +837,18 @@ files = [ {file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, ] +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + [[package]] name = "pandas" version = "2.3.3" @@ -1169,6 +1284,18 @@ files = [ doc = ["sphinx (>=6.1.3,<6.2.0)", "sphinx_rtd_theme (>=1.2.0,<1.3.0)"] test = ["beautifulsoup4", "flake8", "pytest", "pytest-cov"] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, + {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1420,6 +1547,67 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +[[package]] +name = "ta-lib" +version = "0.6.7" +description = "Python wrapper for TA-Lib" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "ta_lib-0.6.7-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:98ca46298595196976d685e3628d28b866bece556c1a11cc1a950260931748f2"}, + {file = "ta_lib-0.6.7-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:79e0d15db755d4c4c34216945467e2c0c0a0768bb6cd5ced09c331deb54c7702"}, + {file = "ta_lib-0.6.7-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:35ba7ce42cc5dcdd0ad9dee6d4b9fd9025538ffd00865b1be81d5b56e44da377"}, + {file = "ta_lib-0.6.7-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a04fdc1bc77093098557d2af8ba7ac6cf9fdbe33c9224471f8e0c066c9e6dfa0"}, + {file = "ta_lib-0.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:79efa70190cee4cb3440a57ce8e7897fb0939890ee35f1564f01ea313319ddba"}, + {file = "ta_lib-0.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c6b86adaeb801248f4043fce58c560655179969c9133c16763687fb28c2a3bdc"}, + {file = "ta_lib-0.6.7-cp310-cp310-win32.whl", hash = "sha256:3123ffe82a5677b79085b2206b4ee370b13c6ba7594818277734326ecace21ee"}, + {file = "ta_lib-0.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:39c60c48a5fb62c890f1cb3faa165dfe66e0e9c7e49bb3b43b3c7843868f1fb4"}, + {file = "ta_lib-0.6.7-cp310-cp310-win_arm64.whl", hash = "sha256:250b9047dc52c6d9fa812aebf86495537dcf4f24d84b6bd8fbae32c7ee2f110e"}, + {file = "ta_lib-0.6.7-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:f3f5fda59669203d2f890606eb0a146bc4682ece9bfd77dacf5bc749b91c38d6"}, + {file = "ta_lib-0.6.7-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:293c92b4b5b58174c7689b7349c8be104886ee34245e8354016ba6849cf628c1"}, + {file = "ta_lib-0.6.7-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ea4ea820dad59415a5cab2ab86b3529ea1ca230230fb3d4036f2273c4345140f"}, + {file = "ta_lib-0.6.7-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:348c615e8c5fc9461cb241616dc7953977fef7004f2cf1d5ff009e43edf39759"}, + {file = "ta_lib-0.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1118a0957c8b3d26dd4a0d49c49c7fda61794aa04b39b652d258b65a0f3c0869"}, + {file = "ta_lib-0.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:028defb19a9042e2876fd269aee489b300029cc5c58a1cb5a21f31d7495ad728"}, + {file = "ta_lib-0.6.7-cp311-cp311-win32.whl", hash = "sha256:ef3bb9ba7ed8e8f112a7c60e386a41b260c77c126613a6a01d393681c32a60dc"}, + {file = "ta_lib-0.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:681517c82d4803bf5940524d2e046bbc55b86a82c449f7c70b1bfce93cc5828e"}, + {file = "ta_lib-0.6.7-cp311-cp311-win_arm64.whl", hash = "sha256:46214874dfbee6f3b071737df1ddf0f7ccfbb7764cf612a2f3d74942d308cc64"}, + {file = "ta_lib-0.6.7-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:4c28957ab9e6f26e4ffc8c9354dc2d3f920a16b3516ff010888cded8fe258b14"}, + {file = "ta_lib-0.6.7-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fb49c2388238bd176c7e4589979ab3d58f9876ca144cd6551273c19d9e6aa1eb"}, + {file = "ta_lib-0.6.7-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d9d2352c4b12bb221d599b1730fca5748b9259ff29aa19c4b1a368d87c54656d"}, + {file = "ta_lib-0.6.7-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a07967a28066a12b8fb94c97fc3bd3c563f04432f2383703763f53bc94aba92b"}, + {file = "ta_lib-0.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1843491cb19de376366e490c9ea60d43408162ddd9675c006cec03eeca32063d"}, + {file = "ta_lib-0.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76e5b89bc9be6f1721a65384c8c33bb384a17f29ec22490dd57db49ed185cd5b"}, + {file = "ta_lib-0.6.7-cp312-cp312-win32.whl", hash = "sha256:411b4d05e976c9de4993c3b578aaadec353478287e886a0b72db881e0abba43d"}, + {file = "ta_lib-0.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:1cb66ca90d954a5bf3dbfad2679ba14e83f7d2ee72feeb51c1187efb88ef282c"}, + {file = "ta_lib-0.6.7-cp312-cp312-win_arm64.whl", hash = "sha256:0ff9490faaaba625ffc2cfd520764067cd6255e663dd8d020cbcaa33870c8a56"}, + {file = "ta_lib-0.6.7-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:dfe4a7fb1c6a6452e7704b0baa3633c03241d616515d07365bc9114c5b85be87"}, + {file = "ta_lib-0.6.7-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d3fd2175bdfeb5822b7c8364a0fcdc8eb4c07af9593f84480a10a4cf827d9266"}, + {file = "ta_lib-0.6.7-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:167a4b75a8c723d0b995269e67270d9cdfa22bed59957f0386cea0fe82b41c33"}, + {file = "ta_lib-0.6.7-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8640e9e12a4bcddf5f7e3460430f259fea16c27eeab13481ff16a6964db1e58b"}, + {file = "ta_lib-0.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f69f22a561f10457b86e09bb8700c36eeafad1944010c7cc894bba8be827cd9"}, + {file = "ta_lib-0.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ef03e89b39f0e5d8ab74a9d2a2537ce202bcf622ed190d8d1c0077cf9cb7c11b"}, + {file = "ta_lib-0.6.7-cp313-cp313-win32.whl", hash = "sha256:3af4953d128676488a58df42b61bcaace669f83110f54d2dd2e866105093f597"}, + {file = "ta_lib-0.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:213423db3afddc9541ab54e8841bcf5f6a6b5a41625d70bd1544e58952a8fe2c"}, + {file = "ta_lib-0.6.7-cp313-cp313-win_arm64.whl", hash = "sha256:ffc54a335c0e7ea69a4217ab8e93cada8871e591badb95fd7f2a0430eaa59825"}, + {file = "ta_lib-0.6.7-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:0bbb20f2653397c28fddafbd23b5ee98b64a3c60e7d11fe67eae7435f464896c"}, + {file = "ta_lib-0.6.7-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:e6ddceae8193ca4337cf70698fd2f58b341d073f0c5a9d49c1ba5d289dc1d6c9"}, + {file = "ta_lib-0.6.7-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:07d20576d12dd75f9823212d8aa297bdb50f1c88749ccc4d3b44a771ee74fb99"}, + {file = "ta_lib-0.6.7-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c7d1ba210ac9f8f211b3cace3c8e2fe6d65a8ff729af33133efac8910f239a44"}, + {file = "ta_lib-0.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79e8d8d1e9d7fdf0ce784c7a11dfef4dc1c5010f82bb3b5bf97c36eb399eefa8"}, + {file = "ta_lib-0.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0bb487f39c333e93226a796eec8c8fbfdea1275bb8415790601967aa3280fe04"}, + {file = "ta_lib-0.6.7-cp39-cp39-win32.whl", hash = "sha256:3c56e43aede6f8514045102d3d0bae52d8752aaac559c34fee5c447fd1cbf90a"}, + {file = "ta_lib-0.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:f6e62d9e2d2d7b3825030a94eb82741299825d6d90016b2fc01c96610a8853f9"}, + {file = "ta_lib-0.6.7-cp39-cp39-win_arm64.whl", hash = "sha256:da66c7c6feda7bdc9ece5ecb518d872339f764d890d1c6e71b98bf4ad37deb07"}, + {file = "ta_lib-0.6.7.tar.gz", hash = "sha256:d702cbb20ab34d6d6c46bef87a1bd4bfeae0cc0ab65cbdf1d23dd392ce110d69"}, +] + +[package.dependencies] +build = "*" +cython = "*" +numpy = "*" + [[package]] name = "toolz" version = "1.0.0" @@ -1647,4 +1835,4 @@ repair = ["scipy (>=1.6.3)"] [metadata] lock-version = "2.1" python-versions = ">=3.13" -content-hash = "f8ab199e4a4efc383caa5691f31314d4cdecc6564e120b4024ce3eb1711d00f5" +content-hash = "c9a5936e446cf6234585a330d4fc3888dd4a8c8db505bbc9e78d454cfe864569" diff --git a/pyproject.toml b/pyproject.toml index 62f06d5..b7cae52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,8 @@ dependencies = [ "yfinance (>=0.2.66,<0.3.0)", "ipython (>=9.6.0,<10.0.0)", "pandas-market-calendars (>=5.1.1,<6.0.0)", - "sqlmodel (>=0.0.27,<0.0.28)" + "sqlmodel (>=0.0.27,<0.0.28)", + "ta-lib (>=0.6.7,<0.7.0)" ]