feat(indicators): add indicator service and related dataclasses

This commit is contained in:
Giulio De Pasquale 2025-10-18 10:05:05 +01:00
parent 4fb70813e6
commit 600d6b76f6
5 changed files with 632 additions and 5 deletions

View File

@ -33,7 +33,8 @@ class TradingDataCRUD:
@contextmanager @contextmanager
def get_session(self): def get_session(self):
"""Context manager for database sessions with automatic cleanup.""" """Context manager for database sessions with automatic cleanup."""
session = Session(self.engine) session = Session(self.engine, expire_on_commit=False)
try: try:
yield session yield session
session.commit() session.commit()

360
paperone/indicators.py Normal file
View File

@ -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)
)

View File

@ -1,6 +1,8 @@
from sqlmodel import SQLModel, Field, Relationship, ForeignKeyConstraint from sqlmodel import SQLModel, Field, Relationship, ForeignKeyConstraint
from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
import numpy as np
class TickerOHLCV(SQLModel, table=True): class TickerOHLCV(SQLModel, table=True):
@ -84,3 +86,78 @@ class IndicatorsData(SQLModel, table=True):
# One-to-one relationship back to TickerOHLCV # One-to-one relationship back to TickerOHLCV
ohlcv: TickerOHLCV = Relationship(back_populates="indicators") 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

194
poetry.lock generated
View File

@ -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]] [[package]]
name = "annotated-types" name = "annotated-types"
@ -70,6 +70,27 @@ charset-normalizer = ["charset-normalizer"]
html5lib = ["html5lib"] html5lib = ["html5lib"]
lxml = ["lxml"] 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]] [[package]]
name = "certifi" name = "certifi"
version = "2025.10.5" version = "2025.10.5"
@ -290,7 +311,7 @@ description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"] groups = ["main"]
markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" markers = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\""
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {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)"] 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)"] 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]] [[package]]
name = "decorator" name = "decorator"
version = "5.2.1" version = "5.2.1"
@ -734,6 +837,18 @@ files = [
{file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, {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]] [[package]]
name = "pandas" name = "pandas"
version = "2.3.3" 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)"] doc = ["sphinx (>=6.1.3,<6.2.0)", "sphinx_rtd_theme (>=1.2.0,<1.3.0)"]
test = ["beautifulsoup4", "flake8", "pytest", "pytest-cov"] 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]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"
@ -1420,6 +1547,67 @@ pure-eval = "*"
[package.extras] [package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] 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]] [[package]]
name = "toolz" name = "toolz"
version = "1.0.0" version = "1.0.0"
@ -1647,4 +1835,4 @@ repair = ["scipy (>=1.6.3)"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.13" python-versions = ">=3.13"
content-hash = "f8ab199e4a4efc383caa5691f31314d4cdecc6564e120b4024ce3eb1711d00f5" content-hash = "c9a5936e446cf6234585a330d4fc3888dd4a8c8db505bbc9e78d454cfe864569"

View File

@ -15,7 +15,8 @@ dependencies = [
"yfinance (>=0.2.66,<0.3.0)", "yfinance (>=0.2.66,<0.3.0)",
"ipython (>=9.6.0,<10.0.0)", "ipython (>=9.6.0,<10.0.0)",
"pandas-market-calendars (>=5.1.1,<6.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)"
] ]