Compare commits

..

12 Commits

Author SHA1 Message Date
Giulio De Pasquale
46662a37a4 feat(populate): add indicator calculation to populate script 2025-10-18 11:33:59 +01:00
Giulio De Pasquale
d87733b80e feat(indicators): add validation and force_update to indicator calculations 2025-10-18 11:33:24 +01:00
Giulio De Pasquale
30ed373ac5 devenv 2025-10-18 10:55:48 +01:00
Giulio De Pasquale
c11c2bccf4 refactor: use Fetcher and add reference tickers 2025-10-18 10:55:36 +01:00
Giulio De Pasquale
600d6b76f6 feat(indicators): add indicator service and related dataclasses 2025-10-18 10:05:05 +01:00
Giulio De Pasquale
4fb70813e6 refactor(client): use models for TickerOHLCV and include ticker in OHLCV 2025-10-18 09:27:19 +01:00
Giulio De Pasquale
35de3b208c build(pyproject): add sqlmodel dependency 2025-10-18 09:26:47 +01:00
Giulio De Pasquale
5b67db68ea refactor(scripts): replace paperone.py with populate.py to populate database 2025-10-18 09:26:06 +01:00
Giulio De Pasquale
41d0f4060f ignore db 2025-10-18 09:23:32 +01:00
Giulio De Pasquale
13d5987e49 refactor(models): add explicit foreign key constraints for indicators 2025-10-18 09:23:02 +01:00
Giulio De Pasquale
f5aa0e5848 feat(database): add trading data CRUD operations 2025-10-18 09:22:23 +01:00
Giulio De Pasquale
b4cc94a444 asodijasoidjasoidj 2025-10-16 12:59:39 +01:00
13 changed files with 1753 additions and 151 deletions

1
.gitignore vendored
View File

@ -209,3 +209,4 @@ pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python,go
.aider*
*.db

View File

@ -74,16 +74,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1758532697,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f",
"lastModified": 1760775323,
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cef7b5a1998b7d7f4b945168421141f50f98e4ed",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cef7b5a1998b7d7f4b945168421141f50f98e4ed",
"type": "github"
}
},

View File

@ -3,8 +3,9 @@
{
env.GREET = "devenv";
cachix.enable = false;
dotenv.disableHint = true;
packages = with pkgs; [ poetry ];
packages = with pkgs; [ poetry repomix ];
languages.python = {
enable = true;

View File

@ -1,7 +1,7 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
url: github:nixos/nixpkgs/cef7b5a1998b7d7f4b945168421141f50f98e4ed
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python
from sys import exit
from dotenv import load_dotenv
from typing import NoReturn
from paperone.utils import (
parse_date_yyyymmdd,
is_trading_day,
get_last_n_trading_days,
)
from os import environ
from paperone.client import Client
from rich.progress import track
load_dotenv()
def main() -> NoReturn:
api_key = environ.get("API_KEY")
if not api_key:
print("API_KEY not set")
exit(0)
client = Client(api_key)
date = parse_date_yyyymmdd("20250821")
days_range = 60
dates_range = get_last_n_trading_days(date, days_range)
# tickers = ["VIX"]
# indicators = list(IndicatorEnum)
for x in track([x for x in dates_range if is_trading_day(x)]):
print(client.ticker_data_for("AAPL", x))
exit(0)
if __name__ == "__main__":
main()

View File

@ -1,76 +1,120 @@
from datetime import datetime, timedelta
from .taapi import TaapiClient
from .data import TickerData
from typing import List
from .models import TickerOHLCV
import yfinance as yf
import pandas as pd
class Client:
def __init__(self, taapi_key: str):
self._taapi = TaapiClient(taapi_key)
class Fetcher:
@staticmethod
def ticker_data_for(ticker: str, date: datetime) -> TickerData | None:
# Set end date to next day to ensure we get the target date
start_date = date.strftime("%Y-%m-%d")
end_date = (date + timedelta(days=1)).strftime("%Y-%m-%d")
def ticker_data_for(
ticker: str, date: datetime, end_date: datetime | None = None
) -> List[TickerOHLCV]:
"""
Fetch OHLCV data for a ticker for a date range.
Args:
ticker: Stock ticker symbol
date: Start date (inclusive)
end_date: End date (inclusive). If None, fetches only the start date.
Returns:
List of TickerOHLCV records, one per trading day in the range.
Returns empty list if no data available.
"""
start_date_str = date.strftime("%Y-%m-%d")
if not end_date:
end_date_str = (date + timedelta(days=1)).strftime("%Y-%m-%d")
else:
end_date_str = (end_date + timedelta(days=1)).strftime("%Y-%m-%d")
try:
data = yf.download(
ticker,
start=start_date,
end=end_date,
start=start_date_str,
end=end_date_str,
auto_adjust=True,
progress=False,
)
if data.empty:
return None
return []
row = data.iloc[0]
results: List[TickerOHLCV] = []
open_price = (
float(row["Open"].iloc[0])
if isinstance(row["Open"], pd.Series)
else float(row["Open"])
)
high = (
float(row["High"].iloc[0])
if isinstance(row["High"], pd.Series)
else float(row["High"])
)
low = (
float(row["Low"].iloc[0])
if isinstance(row["Low"], pd.Series)
else float(row["Low"])
)
close = (
float(row["Close"].iloc[0])
if isinstance(row["Close"], pd.Series)
else float(row["Close"])
)
volume = (
int(row["Volume"].iloc[0])
if isinstance(row["Volume"], pd.Series)
else int(row["Volume"])
if "Volume" in row
else 0
)
for idx, row in data.iterrows():
# Extract datetime from index
if hasattr(idx, "to_pydatetime"):
row_date = idx.to_pydatetime()
elif isinstance(idx, datetime):
row_date = idx
else:
row_date = pd.to_datetime(idx).to_pydatetime()
# Calculate average price
avg = (high + low) / 2.0
# Extract values (handle both Series and scalar)
def safe_extract(value):
if isinstance(value, pd.Series):
return float(value.iloc[0])
return float(value)
return TickerData(
date=date,
open=round(open_price, 2),
high=round(high, 2),
low=round(low, 2),
close=round(close, 2),
avg=round(avg, 2),
volume=volume,
)
open_price = safe_extract(row["Open"])
high = safe_extract(row["High"])
low = safe_extract(row["Low"])
close = safe_extract(row["Close"])
volume = int(safe_extract(row.get("Volume", 0)))
avg = (high + low) / 2.0
ohlcv = TickerOHLCV(
ticker=ticker,
date=row_date,
open=round(open_price, 2),
high=round(high, 2),
low=round(low, 2),
close=round(close, 2),
avg=round(avg, 2),
volume=volume,
)
results.append(ohlcv)
return results
except Exception as e:
print(f"Error fetching data for {ticker} on {start_date}: {str(e)}")
print(
f"Error fetching data for {ticker} from {start_date_str} to {end_date_str}: {str(e)}"
)
return []
return None
@staticmethod
def ticker_data_for_single_day(ticker: str, date: datetime) -> TickerOHLCV | None:
"""
Fetch OHLCV data for a single day (backward compatibility).
Args:
ticker: Stock ticker symbol
date: The specific date
Returns:
TickerOHLCV record for that day, or None if not available
"""
results = Fetcher.ticker_data_for(ticker, date, end_date=None)
return results[0] if results else None
@staticmethod
def ticker_data_for_range(
ticker: str, start_date: datetime, end_date: datetime
) -> List[TickerOHLCV]:
"""
Fetch OHLCV data for a date range (explicit range method).
Args:
ticker: Stock ticker symbol
start_date: Start date (inclusive)
end_date: End date (inclusive)
Returns:
List of TickerOHLCV records for the range
"""
return Fetcher.ticker_data_for(ticker, start_date, end_date)

477
paperone/database.py Normal file
View File

@ -0,0 +1,477 @@
from sqlmodel import SQLModel, Session, create_engine, select
from typing import List, Optional, Tuple
from datetime import datetime
from contextlib import contextmanager
from .models import TickerOHLCV, IndicatorsData
from .entities import TimeSeriesTickerData
class TradingDataCRUD:
"""
CRUD operations for trading data with SQLModel/SQLite.
Handles OHLCV data and technical indicators with proper session management.
"""
def __init__(self, database_url: str = "sqlite:///trading_data.db"):
"""
Initialize the CRUD manager with database connection.
Args:
database_url: SQLite database URL (default: local file)
"""
self.engine = create_engine(
database_url,
echo=False, # Set to True for SQL query debugging
connect_args={"check_same_thread": False}, # Needed for SQLite
)
self._create_tables()
def _create_tables(self):
"""Create all tables if they don't exist."""
SQLModel.metadata.create_all(self.engine)
@contextmanager
def get_session(self):
"""Context manager for database sessions with automatic cleanup."""
session = Session(self.engine, expire_on_commit=False)
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
# ========================================================================
# CREATE Operations
# ========================================================================
def create_ohlcv(self, ohlcv: TickerOHLCV) -> TickerOHLCV:
"""
Insert a single OHLCV record.
Args:
ohlcv: TickerOHLCV instance to insert
Returns:
The inserted TickerOHLCV record
"""
with self.get_session() as session:
session.add(ohlcv)
session.commit()
session.refresh(ohlcv)
return ohlcv
def create_ohlcv_bulk(self, ohlcv_list: List[TickerOHLCV]) -> int:
"""
Bulk insert OHLCV records (more efficient for large datasets).
Args:
ohlcv_list: List of TickerOHLCV instances
Returns:
Number of records inserted
"""
with self.get_session() as session:
session.add_all(ohlcv_list)
session.commit()
return len(ohlcv_list)
def create_indicators(self, indicators: IndicatorsData) -> IndicatorsData:
"""
Insert a single indicators record.
Args:
indicators: IndicatorsData instance to insert
Returns:
The inserted IndicatorsData record
"""
with self.get_session() as session:
session.add(indicators)
session.commit()
session.refresh(indicators)
return indicators
def create_indicators_bulk(self, indicators_list: List[IndicatorsData]) -> int:
"""
Bulk insert indicators records.
Args:
indicators_list: List of IndicatorsData instances
Returns:
Number of records inserted
"""
with self.get_session() as session:
session.add_all(indicators_list)
session.commit()
return len(indicators_list)
# ========================================================================
# READ Operations
# ========================================================================
def get_ohlcv(self, ticker: str, date: datetime) -> Optional[TickerOHLCV]:
"""
Get a single OHLCV record by ticker and date.
Args:
ticker: Stock ticker symbol
date: Trading date
Returns:
TickerOHLCV record or None if not found
"""
with self.get_session() as session:
statement = select(TickerOHLCV).where(
TickerOHLCV.ticker == ticker, TickerOHLCV.date == date
)
return session.exec(statement).first()
def get_ohlcv_range(
self, ticker: str, start_date: datetime, end_date: datetime
) -> List[TickerOHLCV]:
"""
Get OHLCV records for a ticker within a date range.
Args:
ticker: Stock ticker symbol
start_date: Start of date range (inclusive)
end_date: End of date range (inclusive)
Returns:
List of TickerOHLCV records, sorted by date
"""
with self.get_session() as session:
statement = (
select(TickerOHLCV)
.where(
TickerOHLCV.ticker == ticker,
TickerOHLCV.date >= start_date,
TickerOHLCV.date <= end_date,
)
.order_by(TickerOHLCV.date)
)
return list(session.exec(statement).all())
def get_ohlcv_latest(self, ticker: str, limit: int = 1) -> List[TickerOHLCV]:
"""
Get the most recent OHLCV record(s) for a ticker.
Args:
ticker: Stock ticker symbol
limit: Number of recent records to retrieve
Returns:
List of most recent TickerOHLCV records, sorted by date descending
"""
with self.get_session() as session:
statement = (
select(TickerOHLCV)
.where(TickerOHLCV.ticker == ticker)
.order_by(TickerOHLCV.date.desc())
.limit(limit)
)
return list(session.exec(statement).all())
def get_indicators(self, ticker: str, date: datetime) -> Optional[IndicatorsData]:
"""
Get indicators for a specific ticker and date.
Args:
ticker: Stock ticker symbol
date: Trading date
Returns:
IndicatorsData record or None if not found
"""
with self.get_session() as session:
statement = select(IndicatorsData).where(
IndicatorsData.ticker == ticker, IndicatorsData.date == date
)
return session.exec(statement).first()
def get_indicators_range(
self, ticker: str, start_date: datetime, end_date: datetime
) -> List[IndicatorsData]:
"""
Get indicators for a ticker within a date range.
Args:
ticker: Stock ticker symbol
start_date: Start of date range (inclusive)
end_date: End of date range (inclusive)
Returns:
List of IndicatorsData records, sorted by date
"""
with self.get_session() as session:
statement = (
select(IndicatorsData)
.where(
IndicatorsData.ticker == ticker,
IndicatorsData.date >= start_date,
IndicatorsData.date <= end_date,
)
.order_by(IndicatorsData.date)
)
return list(session.exec(statement).all())
def get_ohlcv_with_indicators(
self, ticker: str, date: datetime
) -> Optional[Tuple[TickerOHLCV, IndicatorsData]]:
"""
Get OHLCV and indicators together for a specific ticker and date.
Args:
ticker: Stock ticker symbol
date: Trading date
Returns:
Tuple of (TickerOHLCV, IndicatorsData) or None if not found
"""
with self.get_session() as session:
ohlcv_statement = select(TickerOHLCV).where(
TickerOHLCV.ticker == ticker, TickerOHLCV.date == date
)
ohlcv = session.exec(ohlcv_statement).first()
if not ohlcv:
return None
indicators_statement = select(IndicatorsData).where(
IndicatorsData.ticker == ticker, IndicatorsData.date == date
)
indicators = session.exec(indicators_statement).first()
if not indicators:
return None
return (ohlcv, indicators)
def get_time_series(
self, ticker: str, start_date: datetime, end_date: datetime
) -> TimeSeriesTickerData:
"""
Get time series data for building TimeSeriesTickerData.
Args:
ticker: Stock ticker symbol
start_date: Start of date range
end_date: End of date range
Returns:
TimeSeriesTickerData instance with OHLCV data
"""
ohlcv_list = self.get_ohlcv_range(ticker, start_date, end_date)
return TimeSeriesTickerData.build_time_series_ticker_data(ticker, ohlcv_list)
# ========================================================================
# UPDATE Operations
# ========================================================================
def update_ohlcv(
self, ticker: str, date: datetime, **kwargs
) -> Optional[TickerOHLCV]:
"""
Update OHLCV fields for a specific record.
Args:
ticker: Stock ticker symbol
date: Trading date
**kwargs: Fields to update (e.g., close=150.0, volume=1000000)
Returns:
Updated TickerOHLCV record or None if not found
"""
with self.get_session() as session:
statement = select(TickerOHLCV).where(
TickerOHLCV.ticker == ticker, TickerOHLCV.date == date
)
ohlcv = session.exec(statement).first()
if not ohlcv:
return None
for key, value in kwargs.items():
if hasattr(ohlcv, key):
setattr(ohlcv, key, value)
session.add(ohlcv)
session.commit()
session.refresh(ohlcv)
return ohlcv
def update_indicators(
self, ticker: str, date: datetime, **kwargs
) -> Optional[IndicatorsData]:
"""
Update indicator fields for a specific record.
Args:
ticker: Stock ticker symbol
date: Trading date
**kwargs: Fields to update (e.g., rsi_14=55.2, macd_line=1.8)
Returns:
Updated IndicatorsData record or None if not found
"""
with self.get_session() as session:
statement = select(IndicatorsData).where(
IndicatorsData.ticker == ticker, IndicatorsData.date == date
)
indicators = session.exec(statement).first()
if not indicators:
return None
for key, value in kwargs.items():
if hasattr(indicators, key):
setattr(indicators, key, value)
session.add(indicators)
session.commit()
session.refresh(indicators)
return indicators
# ========================================================================
# UPSERT Operations (Insert or Update)
# ========================================================================
def upsert_ohlcv(self, ohlcv: TickerOHLCV) -> TickerOHLCV:
"""
Insert or update OHLCV record if it already exists.
Args:
ohlcv: TickerOHLCV instance
Returns:
The upserted TickerOHLCV record
"""
existing = self.get_ohlcv(ohlcv.ticker, ohlcv.date)
if existing:
return self.update_ohlcv(
ohlcv.ticker,
ohlcv.date,
open=ohlcv.open,
close=ohlcv.close,
low=ohlcv.low,
high=ohlcv.high,
avg=ohlcv.avg,
volume=ohlcv.volume,
)
else:
return self.create_ohlcv(ohlcv)
def upsert_indicators(self, indicators: IndicatorsData) -> IndicatorsData:
"""
Insert or update indicators record if it already exists.
Args:
indicators: IndicatorsData instance
Returns:
The upserted IndicatorsData record
"""
existing = self.get_indicators(indicators.ticker, indicators.date)
if existing:
# Get all indicator fields dynamically
indicator_fields = {
k: v
for k, v in indicators.__dict__.items()
if k not in ["ticker", "date", "ohlcv", "_sa_instance_state"]
}
return self.update_indicators(
indicators.ticker, indicators.date, **indicator_fields
)
else:
return self.create_indicators(indicators)
# ========================================================================
# DELETE Operations
# ========================================================================
def delete_ohlcv(self, ticker: str, date: datetime) -> bool:
"""
Delete an OHLCV record.
Args:
ticker: Stock ticker symbol
date: Trading date
Returns:
True if deleted, False if not found
"""
with self.get_session() as session:
statement = select(TickerOHLCV).where(
TickerOHLCV.ticker == ticker, TickerOHLCV.date == date
)
ohlcv = session.exec(statement).first()
if not ohlcv:
return False
session.delete(ohlcv)
session.commit()
return True
def delete_indicators(self, ticker: str, date: datetime) -> bool:
"""
Delete an indicators record.
Args:
ticker: Stock ticker symbol
date: Trading date
Returns:
True if deleted, False if not found
"""
with self.get_session() as session:
statement = select(IndicatorsData).where(
IndicatorsData.ticker == ticker, IndicatorsData.date == date
)
indicators = session.exec(statement).first()
if not indicators:
return False
session.delete(indicators)
session.commit()
return True
def delete_ticker_data(self, ticker: str) -> Tuple[int, int]:
"""
Delete all data (OHLCV and indicators) for a ticker.
Args:
ticker: Stock ticker symbol
Returns:
Tuple of (ohlcv_deleted_count, indicators_deleted_count)
"""
with self.get_session() as session:
# Delete indicators first (due to foreign key constraint)
indicators_statement = select(IndicatorsData).where(
IndicatorsData.ticker == ticker
)
indicators_list = session.exec(indicators_statement).all()
indicators_count = len(list(indicators_list))
for ind in indicators_list:
session.delete(ind)
# Delete OHLCV
ohlcv_statement = select(TickerOHLCV).where(TickerOHLCV.ticker == ticker)
ohlcv_list = session.exec(ohlcv_statement).all()
ohlcv_count = len(list(ohlcv_list))
for ohlcv in ohlcv_list:
session.delete(ohlcv)
session.commit()
return (ohlcv_count, indicators_count)

View File

@ -1,56 +1,13 @@
from dataclasses import dataclass, field
from datetime import datetime
from typing import List
@dataclass
class TickerData:
date: datetime
open: float
close: float
low: float
high: float
avg: float
volume: int
from .models import TickerOHLCV
@dataclass
class TimeSeriesTickerData:
ticker: str
series: List[TickerData] = field(default_factory=list)
@staticmethod
def build_time_series_ticker_data(
ticker: str, all_data: List[TickerData]
):
ordered = sorted({d.date: d for d in all_data}.values(), key=lambda d: d.date)
return TimeSeriesTickerData(
ticker=ticker,
series=ordered,
)
def get_by_date(self, dt: datetime) -> TickerData | None:
for d in self.series:
if d.date == dt:
return d
return None
def get_latest(self) -> TickerData | None:
if not self.series:
return None
return self.series[-1]
def get_range(self, start_date: datetime, end_date: datetime) -> List[TickerData]:
return [d for d in self.series if start_date <= d.date <= end_date]
@property
def target_date_data(self) -> TickerData | None:
"""Return the most recent TickerData (by convention, the target date tick)."""
return self.get_latest()
series: List[TickerOHLCV] = field(default_factory=list)
@dataclass

369
paperone/indicators.py Normal file
View File

@ -0,0 +1,369 @@
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
or if any calculated values are NaN
"""
# Add indicator-specific validation
required_periods = {
"MACD": 26 + 9, # slowperiod + signalperiod
"ADX": 14 + 14, # Additional warmup needed
"RSI": 14 + 1, # Plus warmup
}
min_required = max(required_periods.values()) + 10 # Safety buffer
if lookback_days < min_required:
return None
# 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
)
if len(ohlcv_records) < min_required:
return None
# Convert to numpy arrays for TA-Lib
arrays: OHLCVArrays = self._convert_to_arrays(ohlcv_records)
# Calculate all indicator categories
momentum: MomentumIndicators | None = self._calculate_momentum(arrays)
volatility: VolatilityIndicators | None = self._calculate_volatility(arrays)
trend: TrendIndicators | None = self._calculate_trend(arrays)
volume: VolumeIndicators | None = self._calculate_volume(arrays)
support_resistance: SupportResistanceIndicators = (
self._calculate_support_resistance(arrays)
)
market_regime: MarketRegimeIndicators | None = self._calculate_market_regime(
arrays
)
# Skip if any indicator group returned None (contains NaN values)
if (
momentum is None
or volatility is None
or trend is None
or volume is None
or market_regime is None
):
return None
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, force_update: bool = False
) -> IndicatorsData | None:
if not force_update:
existing = self._crud.get_indicators(ticker=ticker, date=target_date)
if existing is not None:
return existing
indicators = self.calculate_indicators_for_date(
ticker=ticker, target_date=target_date
)
if indicators is None:
return None
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], force_update: bool = False
) -> int:
# Batch check existing records
if not force_update:
existing_dates = self._crud.get_existing_indicator_dates(ticker, dates)
dates = [d for d in dates if d not in existing_dates]
if not dates:
return 0
start_date = min(dates) - timedelta(days=150)
end_date = max(dates)
all_ohlcv = self._crud.get_ohlcv_range(ticker, start_date, end_date)
results = []
for date in dates:
indicators = self._calculate_for_date_from_data(all_ohlcv, date)
if indicators:
results.append(indicators)
# Batch insert
return self._crud.bulk_upsert_indicators(results)
# ========================================================================
# 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)
)

163
paperone/models.py Normal file
View File

@ -0,0 +1,163 @@
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):
__tablename__ = "ohlcv"
ticker: str = Field(primary_key=True)
date: datetime = Field(primary_key=True, index=True)
open: float
close: float
low: float
high: float
avg: float
volume: int
indicators: Optional["IndicatorsData"] = Relationship(
back_populates="ohlcv",
sa_relationship_kwargs={"uselist": False},
)
class IndicatorsData(SQLModel, table=True):
__tablename__ = "indicators"
__table_args__ = (
ForeignKeyConstraint(["ticker", "date"], ["ohlcv.ticker", "ohlcv.date"]),
)
ticker: str = Field(primary_key=True)
date: datetime = Field(primary_key=True, index=True)
# ========================================================================
# MOMENTUM INDICATORS (Trend Direction & Strength)
# ========================================================================
rsi_14: float # Standard 14-period RSI
rsi_20: float # Longer 20-period RSI for smoother signal
macd_line: float # MACD line (12 EMA - 26 EMA)
macd_signal: float # Signal line (9-period EMA of MACD)
macd_histogram: float # Histogram (MACD - Signal), shows momentum strength
stoch_k: float # Fast stochastic %K (14-period)
stoch_d: float # Slow stochastic %D (3-period SMA of %K)
# ========================================================================
# VOLATILITY INDICATORS (Price Dispersion & Risk)
# ========================================================================
bb_upper: float # Upper Bollinger Band (SMA + 2*std)
bb_middle: float # Middle band (20-period SMA)
bb_lower: float # Lower Bollinger Band (SMA - 2*std)
bb_width: float # Band width (upper - lower), measures volatility magnitude
bb_percent: float # %B indicator: (close - lower) / (upper - lower)
atr_14: float # 14-period Average True Range
# ========================================================================
# TREND INDICATORS (Trend Presence & Sustainability)
# ========================================================================
adx_14: float # 14-period ADX (trend strength)
di_plus: float # +DI (bullish directional indicator)
di_minus: float # -DI (bearish directional indicator)
sar: float # Current Parabolic SAR level
# ========================================================================
# VOLUME INDICATORS (Institutional Participation)
# ========================================================================
obv: float # On-Balance Volume cumulative total
obv_sma_20: float # 20-day SMA of OBV (trend confirmation)
volume_roc_5: float # 5-day volume rate of change (%)
# ========================================================================
# SUPPORT/RESISTANCE INDICATORS (Key Price Levels)
# ========================================================================
fib_236: float # 23.6% Fibonacci retracement level
fib_382: float # 38.2% Fibonacci retracement level
fib_618: float # 61.8% Fibonacci retracement level (golden ratio)
pivot_point: float # Standard pivot point (High + Low + Close) / 3
resistance_1: float # First resistance level (R1)
support_1: float # First support level (S1)
# ========================================================================
# MARKET REGIME INDICATORS (Market Condition Classification)
# ========================================================================
cci_20: float # 20-period Commodity Channel Index
williams_r_14: float # 14-period Williams %R
# 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

554
poetry.lock generated
View File

@ -1,4 +1,16 @@
# 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"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "asttokens"
@ -58,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"
@ -278,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"},
@ -314,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"
@ -413,6 +528,75 @@ files = [
{file = "frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e"},
]
[[package]]
name = "greenlet"
version = "3.2.4"
description = "Lightweight in-process concurrent programming"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""
files = [
{file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"},
{file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"},
{file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"},
{file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"},
{file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"},
{file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"},
{file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"},
{file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"},
{file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"},
{file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"},
{file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"},
{file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"},
{file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"},
{file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"},
]
[package.extras]
docs = ["Sphinx", "furo"]
test = ["objgraph", "psutil", "setuptools"]
[[package]]
name = "idna"
version = "3.10"
@ -653,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"
@ -905,6 +1101,158 @@ files = [
{file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"},
]
[[package]]
name = "pydantic"
version = "2.12.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae"},
{file = "pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.41.4"
typing-extensions = ">=4.14.1"
typing-inspection = ">=0.4.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
version = "2.41.4"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"},
{file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"},
{file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"},
{file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"},
{file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"},
{file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"},
{file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"},
{file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"},
{file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"},
{file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"},
{file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"},
{file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"},
{file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"},
{file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"},
{file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"},
{file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"},
{file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"},
{file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"},
{file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"},
{file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"},
{file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"},
{file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"},
{file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"},
{file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"},
{file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"},
{file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"},
{file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"},
{file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"},
{file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"},
{file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"},
{file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"},
{file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"},
{file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"},
{file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"},
{file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"},
{file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"},
{file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"},
{file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"},
{file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"},
{file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"},
{file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"},
{file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"},
{file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"},
{file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"},
{file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"},
{file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"},
{file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"},
{file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"},
{file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"},
{file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"},
{file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"},
{file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"},
{file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"},
{file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"},
{file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"},
{file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"},
{file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"},
{file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"},
{file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"},
{file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"},
{file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"},
{file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"},
{file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"},
{file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"},
{file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"},
{file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"},
{file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"},
{file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"},
{file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"},
{file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"},
{file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"},
{file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"},
{file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"},
]
[package.dependencies]
typing-extensions = ">=4.14.1"
[[package]]
name = "pygments"
version = "2.19.2"
@ -936,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"
@ -1055,6 +1415,118 @@ files = [
{file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"},
]
[[package]]
name = "sqlalchemy"
version = "2.0.44"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "SQLAlchemy-2.0.44-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf4bb6b3d6228fcf3a71b50231199fb94d2dd2611b66d33be0578ea3e6c2726"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:e998cf7c29473bd077704cea3577d23123094311f59bdc4af551923b168332b1"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ebac3f0b5732014a126b43c2b7567f2f0e0afea7d9119a3378bde46d3dcad88e"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-win32.whl", hash = "sha256:3255d821ee91bdf824795e936642bbf43a4c7cedf5d1aed8d24524e66843aa74"},
{file = "SQLAlchemy-2.0.44-cp37-cp37m-win_amd64.whl", hash = "sha256:78e6c137ba35476adb5432103ae1534f2f5295605201d946a4198a0dea4b38e7"},
{file = "sqlalchemy-2.0.44-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce"},
{file = "sqlalchemy-2.0.44-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985"},
{file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0"},
{file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e"},
{file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749"},
{file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2"},
{file = "sqlalchemy-2.0.44-cp310-cp310-win32.whl", hash = "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165"},
{file = "sqlalchemy-2.0.44-cp310-cp310-win_amd64.whl", hash = "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5"},
{file = "sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd"},
{file = "sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa"},
{file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e"},
{file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e"},
{file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399"},
{file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b"},
{file = "sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3"},
{file = "sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5"},
{file = "sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250"},
{file = "sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29"},
{file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44"},
{file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1"},
{file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7"},
{file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d"},
{file = "sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4"},
{file = "sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e"},
{file = "sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1"},
{file = "sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45"},
{file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976"},
{file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c"},
{file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d"},
{file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40"},
{file = "sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73"},
{file = "sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e"},
{file = "sqlalchemy-2.0.44-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2fc44e5965ea46909a416fff0af48a219faefd5773ab79e5f8a5fcd5d62b2667"},
{file = "sqlalchemy-2.0.44-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dc8b3850d2a601ca2320d081874033684e246d28e1c5e89db0864077cfc8f5a9"},
{file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d733dec0614bb8f4bcb7c8af88172b974f685a31dc3a65cca0527e3120de5606"},
{file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22be14009339b8bc16d6b9dc8780bacaba3402aa7581658e246114abbd2236e3"},
{file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:357bade0e46064f88f2c3a99808233e67b0051cdddf82992379559322dfeb183"},
{file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4848395d932e93c1595e59a8672aa7400e8922c39bb9b0668ed99ac6fa867822"},
{file = "sqlalchemy-2.0.44-cp38-cp38-win32.whl", hash = "sha256:2f19644f27c76f07e10603580a47278abb2a70311136a7f8fd27dc2e096b9013"},
{file = "sqlalchemy-2.0.44-cp38-cp38-win_amd64.whl", hash = "sha256:1df4763760d1de0dfc8192cc96d8aa293eb1a44f8f7a5fbe74caf1b551905c5e"},
{file = "sqlalchemy-2.0.44-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4"},
{file = "sqlalchemy-2.0.44-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9"},
{file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a"},
{file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2"},
{file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0"},
{file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26"},
{file = "sqlalchemy-2.0.44-cp39-cp39-win32.whl", hash = "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100"},
{file = "sqlalchemy-2.0.44-cp39-cp39-win_amd64.whl", hash = "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6"},
{file = "sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05"},
{file = "sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22"},
]
[package.dependencies]
greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
typing-extensions = ">=4.6.0"
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"]
aioodbc = ["aioodbc", "greenlet (>=1)"]
aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (>=1)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=8)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3_binary"]
[[package]]
name = "sqlmodel"
version = "0.0.27"
description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49"},
{file = "sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620"},
]
[package.dependencies]
pydantic = ">=1.10.13,<3.0.0"
SQLAlchemy = ">=2.0.14,<2.1.0"
[[package]]
name = "stack-data"
version = "0.6.3"
@ -1075,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"
@ -1133,6 +1666,21 @@ files = [
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"},
{file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"},
]
[package.dependencies]
typing-extensions = ">=4.12.0"
[[package]]
name = "tzdata"
version = "2025.2"
@ -1287,4 +1835,4 @@ repair = ["scipy (>=1.6.3)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.13"
content-hash = "3519edd3b81c820008526606d507cec7cac4734a4ff83eb9c8c46ffb3b21ac56"
content-hash = "c9a5936e446cf6234585a330d4fc3888dd4a8c8db505bbc9e78d454cfe864569"

79
populate.py Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env python
from sys import exit
from dotenv import load_dotenv
from typing import NoReturn
from paperone.utils import (
get_last_n_trading_days,
)
from paperone.database import TradingDataCRUD
from paperone.indicators import IndicatorService
from paperone.client import Fetcher
from rich.progress import track
from datetime import datetime
load_dotenv()
DB_FILE = "trading_data.db"
def main() -> NoReturn:
fetcher = Fetcher()
crud = TradingDataCRUD(f"sqlite:///{DB_FILE}")
ind = IndicatorService(crud)
date = datetime.now()
days_range = 360 * 10
tickers = [
"AAPL",
"ARM",
"AMD",
"GOOG",
"META",
"MNDY",
"MSFT",
"NVDA",
]
reference_tickers = [
"VOO", # US
"VT", # global
"GLD", # gold
"^VIX", # volatility
"VNQ", # real estate
"DBC", # commodities
"VGK", # EU
"VPL", # Asia
"VWO", # Emerging
"BND", # Global bonds
]
for ticker in tickers + reference_tickers:
trading_days = get_last_n_trading_days(date, days_range)
start_date = trading_days[0]
end_date = trading_days[-1]
all_ohlcv_data = fetcher.ticker_data_for(ticker, start_date, end_date)
ohlcv_dates = []
for ohlcv in track(all_ohlcv_data, description=f"{ticker} OHLCV"):
existing = crud.get_ohlcv(ticker, ohlcv.date)
if not existing:
crud.create_ohlcv(ohlcv)
ohlcv_dates.append(ohlcv.date)
for calc_date in track(ohlcv_dates, description=f"{ticker} Indicators"):
existing_indicator = crud.get_indicators(ticker, calc_date)
if existing_indicator:
continue
ind.calculate_and_save_indicators(ticker=ticker, target_date=calc_date)
exit(0)
if __name__ == "__main__":
main()

View File

@ -14,7 +14,9 @@ dependencies = [
"typer (>=0.19.2,<0.20.0)",
"yfinance (>=0.2.66,<0.3.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)",
"ta-lib (>=0.6.7,<0.7.0)"
]