Add MVRV strategy backtesting module with feature engineering and on-chain data integration. Implement model training and evaluation pipeline, including probability threshold analysis. Update configuration for strategy parameters and enhance logging for trade results. Include instructions for running the backtest and preparing data.
This commit is contained in:
parent
c4aa965a98
commit
5cc6791877
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -9,7 +9,7 @@
|
||||
"console": "integratedTerminal",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"2025-03-01", "2025-09-22",
|
||||
"2025-01-01", "2025-12-31",
|
||||
"--timeframes-minutes", "60", "240", "480",
|
||||
"--stop-loss", "0.02", "0.05",
|
||||
"--exit-on-bearish-flip",
|
||||
|
||||
42
INSTRUCTIONS_MVRV.md
Normal file
42
INSTRUCTIONS_MVRV.md
Normal file
@ -0,0 +1,42 @@
|
||||
# MVRV Strategy Port Instructions
|
||||
|
||||
This document explains how to run the ported MVRV/NUPL strategy.
|
||||
|
||||
## Prerequisites
|
||||
1. **Dependencies:** Ensure you have installed the required packages:
|
||||
```bash
|
||||
uv add requests xgboost scikit-learn numba python-dotenv
|
||||
```
|
||||
2. **API Key:** Your CryptoQuant API key is set in `.env` (`CRYPTOQUANT_API_KEY`).
|
||||
3. **Data:** You need a high-frequency (1m or 15m) OHLCV CSV file for BTC/USDT.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Prepare Data
|
||||
Fetch on-chain data from CryptoQuant, merge it with your price data, and generate features.
|
||||
```bash
|
||||
python prepare_data.py --csv <path_to_your_ohlcv.csv> --days 730
|
||||
```
|
||||
*Output:* `data/features.csv`
|
||||
|
||||
### 2. Train Model
|
||||
Train the XGBoost model using the generated features.
|
||||
```bash
|
||||
python train_model.py
|
||||
```
|
||||
*Output:* `data/model.pkl`
|
||||
|
||||
### 3. Run Backtest
|
||||
Run the strategy backtest using the trained model and your price data.
|
||||
```bash
|
||||
python backtest_mvrv.py --csv <path_to_your_ohlcv.csv>
|
||||
```
|
||||
*Output:* Logs in `logs/mvrv_trade_log.csv` and summary printed to console.
|
||||
|
||||
## Configuration
|
||||
You can adjust strategy parameters in `strategy_config.py`:
|
||||
- `PROB_THRESHOLD`: ML probability threshold for entry (default 0.55).
|
||||
- `SL_ATR_MULT`: Stop Loss ATR multiplier (default 0.8).
|
||||
- `TP_ATR_MULT`: Take Profit ATR multiplier (default 1.5).
|
||||
- `MVRV_Z_THRESH`: Overheated MVRV Z-score threshold (default 1.5).
|
||||
- `NUPL_THRESH`: Overheated NUPL threshold (default 0.6).
|
||||
@ -66,5 +66,8 @@ def backtest(
|
||||
equity_curve = pd.Series(equity, index=df["Timestamp"])
|
||||
if log_path:
|
||||
write_trade_log(trades, log_path)
|
||||
perf = compute_metrics(equity_curve, trades)
|
||||
|
||||
# Calculate correct annualization factor based on timeframe
|
||||
periods_per_year = int(252 * 24 * 60 / timeframe_minutes)
|
||||
perf = compute_metrics(equity_curve, trades, periods_per_year=periods_per_year)
|
||||
return perf, equity_curve, trades
|
||||
|
||||
270
backtest_mvrv.py
Normal file
270
backtest_mvrv.py
Normal file
@ -0,0 +1,270 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import pickle
|
||||
import os
|
||||
import strategy_config as config
|
||||
from trade import TradeState, enter_long, exit_long
|
||||
from logging_utils import write_trade_log
|
||||
from metrics import compute_metrics
|
||||
from pathlib import Path
|
||||
|
||||
def backtest_mvrv(
|
||||
df_features: pd.DataFrame,
|
||||
df_1min: pd.DataFrame,
|
||||
initial_cash: float = 10000.0,
|
||||
log_path: Path | None = None,
|
||||
test_only: bool = True # NEW: Only backtest on test set to avoid train/test leakage
|
||||
):
|
||||
print("--- Starting MVRV Strategy Backtest ---")
|
||||
|
||||
# 1. Load Model and Generate Predictions
|
||||
print(f"Loading model from {config.MODEL_PATH}...")
|
||||
with open(config.MODEL_PATH, 'rb') as f:
|
||||
model = pickle.load(f)
|
||||
|
||||
# Load split info to identify test set boundary
|
||||
split_info_path = config.MODEL_PATH.replace('.pkl', '_split.pkl')
|
||||
if test_only and os.path.exists(split_info_path):
|
||||
with open(split_info_path, 'rb') as f:
|
||||
split_info = pickle.load(f)
|
||||
test_start_idx = split_info['test_start_idx']
|
||||
print(f"Filtering to TEST SET ONLY (starting at index {test_start_idx})")
|
||||
print(f" Train size was: {split_info['train_size']}, Test size: {split_info['test_size']}")
|
||||
|
||||
# Filter features to test set only
|
||||
df_features = df_features.iloc[test_start_idx:].copy()
|
||||
|
||||
# Filter 1min data to match the test period
|
||||
test_start_ts = df_features.index[0]
|
||||
df_1min = df_1min[df_1min['Timestamp'] >= test_start_ts].copy()
|
||||
|
||||
print(f"Backtest period: {df_features.index[0]} to {df_features.index[-1]}")
|
||||
elif test_only:
|
||||
print("WARNING: Split info not found. Running on FULL dataset (includes training data!).")
|
||||
|
||||
# Prepare features for prediction
|
||||
# Only use columns that were used in training
|
||||
# We rely on config.FEATURE_NAMES, but we must check what's in df_features
|
||||
# The model expects specific columns.
|
||||
X = df_features[config.FEATURE_NAMES]
|
||||
|
||||
print("Generating predictions...")
|
||||
probs = model.predict_proba(X)[:, 1]
|
||||
df_features['signal_prob'] = probs
|
||||
|
||||
# 2. Setup Backtest Loop
|
||||
state = TradeState(
|
||||
cash=initial_cash,
|
||||
fee_bps=config.FEES_PERCENT * 10000, # Convert to bps
|
||||
slippage_bps=config.SLIPPAGE_PERCENT * 10000
|
||||
)
|
||||
|
||||
equity = []
|
||||
trades = []
|
||||
|
||||
# Track dynamic SL/TP
|
||||
current_sl_price = 0.0
|
||||
current_tp_price = 0.0
|
||||
|
||||
# Pre-calculate entry signals to speed up loop
|
||||
# Logic: Prob > Thresh AND Funding > Filter AND (MVRV < Thresh AND NUPL < Thresh)
|
||||
# Note: features.py handles MVRV/NUPL Z-scores.
|
||||
# The strategy uses raw NUPL/MVRV for regime filter, or Z-scores?
|
||||
# Source: (mvrv_z > MVRV_Z_THRESH) | (nupl > NUPL_THRESH) -> is_overheated
|
||||
# Check if we have 'mvrv_z' and 'nupl' columns in df_features.
|
||||
|
||||
# Apply filters
|
||||
# Defaults if cols missing (safe fallback)
|
||||
s_prob = df_features['signal_prob']
|
||||
|
||||
funding = df_features['funding_rate'] if 'funding_rate' in df_features.columns else pd.Series(0, index=df_features.index)
|
||||
|
||||
# Use 'mvrv_z' if available, else 'mvrv' (but Z-score is preferred for normalization)
|
||||
# The source strategy used 'mvrv_z' > 1.5 for overheated.
|
||||
mvrv_z = df_features['mvrv_z'] if 'mvrv_z' in df_features.columns else pd.Series(0, index=df_features.index)
|
||||
|
||||
# Source used raw 'nupl' > 0.6 for overheated
|
||||
nupl = df_features['nupl'] if 'nupl' in df_features.columns else pd.Series(0, index=df_features.index)
|
||||
|
||||
# Regime Filter: True if NOT overheated
|
||||
is_overheated = (mvrv_z > config.MVRV_Z_THRESH) | (nupl > config.NUPL_THRESH)
|
||||
regime_can_trade = ~is_overheated
|
||||
|
||||
# Entry Signal
|
||||
entry_signals = (
|
||||
(s_prob > config.PROB_THRESHOLD) &
|
||||
(funding > config.FUNDING_FILTER) &
|
||||
regime_can_trade
|
||||
)
|
||||
|
||||
df_features['entry_signal'] = entry_signals
|
||||
|
||||
print(f"Total Entry Signals: {entry_signals.sum()}")
|
||||
|
||||
# Loop
|
||||
# df_features is 1H. df_1min is 1m.
|
||||
# We iterate through df_features (hourly steps).
|
||||
# If in a trade, we check df_1min for SL/TP within that hour.
|
||||
# If not in a trade, we check for Entry Signal at the close of the hour (or open of next).
|
||||
# Standard backtesting: Signals calculated on 'Close' are executable at 'Open' of next candle.
|
||||
# But df_1min covers the interval.
|
||||
# Let's align carefully.
|
||||
|
||||
for i in range(len(df_features) - 1):
|
||||
# Current 1H candle (completed)
|
||||
row = df_features.iloc[i]
|
||||
next_row = df_features.iloc[i+1]
|
||||
|
||||
ts_start = row.name # Timestamp of the row (e.g. 10:00)
|
||||
ts_end = next_row.name # Timestamp of next row (e.g. 11:00)
|
||||
|
||||
# Get 1m data for this interval [ts_start, ts_end)
|
||||
# Note: df_1min['Timestamp'] needs to be datetime
|
||||
mask = (df_1min['Timestamp'] >= ts_start) & (df_1min['Timestamp'] < ts_end)
|
||||
chunk_1min = df_1min.loc[mask]
|
||||
|
||||
# 1. Manage Existing Position (Exit Logic)
|
||||
# Store initial qty state to prevent re-entry in same candle if we exited
|
||||
started_with_position = state.qty > 0
|
||||
|
||||
if state.qty > 0:
|
||||
# Check for SL/TP hits in 1m data
|
||||
for _, m_row in chunk_1min.iterrows():
|
||||
m_high = m_row['High']
|
||||
m_low = m_row['Low']
|
||||
m_ts = m_row['Timestamp']
|
||||
|
||||
# Check SL
|
||||
if m_low <= current_sl_price:
|
||||
evt = exit_long(state, current_sl_price) # Exec at SL price
|
||||
if evt:
|
||||
prev = trades[-1]
|
||||
pnl = (evt["price"] - prev["price"]) * prev["qty"]
|
||||
evt.update({"t": m_ts.isoformat(), "reason": "stop_loss", "pnl": pnl})
|
||||
trades.append(evt)
|
||||
break # Exit loop
|
||||
|
||||
# Check TP
|
||||
if m_high >= current_tp_price:
|
||||
evt = exit_long(state, current_tp_price) # Exec at TP price
|
||||
if evt:
|
||||
prev = trades[-1]
|
||||
pnl = (evt["price"] - prev["price"]) * prev["qty"]
|
||||
evt.update({"t": m_ts.isoformat(), "reason": "take_profit", "pnl": pnl})
|
||||
trades.append(evt)
|
||||
break # Exit loop
|
||||
|
||||
# 2. Check for New Entry (if no position)
|
||||
# Logic: If signal was True at 'row' (completed candle), we enter at Open of 'next_row' (or first 1m candle of next hour)
|
||||
# Actually, we can enter immediately at the start of the interval if the signal was from the *previous* completed candle.
|
||||
# Here 'row' is the current interval processing.
|
||||
# If 'entry_signal' is True for 'row', it means at the end of 10:00 we have a signal.
|
||||
# We should enter at 11:00 (which is start of next interval).
|
||||
# So we check entry_signal of 'row', and if True, we enter at first available price in 'chunk_1min'??
|
||||
# WAIT. 'chunk_1min' is [ts_start, ts_end).
|
||||
# If row is 10:00 (meaning data for 09:00-10:00?), standard pandas resample labels left or right?
|
||||
# Usually 10:00 label means 10:00-11:00 or 09:00-10:00?
|
||||
# prepare_data used resample('1h').
|
||||
# Pandas default for 1h is usually start of bin (left).
|
||||
# So 10:00 row contains data from 10:00 to 11:00.
|
||||
# We can only know the signal at 11:00 (Close of the candle).
|
||||
# So we can execute at 11:00 (start of next bin).
|
||||
|
||||
# So: processing interval i (10:00-11:00).
|
||||
# We check signal from i-1 (09:00-10:00).
|
||||
# If i-1 had signal, we enter at start of i.
|
||||
|
||||
if state.qty <= 0 and not started_with_position:
|
||||
# Check previous row signal
|
||||
if i > 0:
|
||||
prev_row = df_features.iloc[i-1]
|
||||
if prev_row['entry_signal']:
|
||||
# Enter Long
|
||||
# Price = Open of current interval (or first 1m open)
|
||||
entry_price = row['open']
|
||||
if not chunk_1min.empty:
|
||||
entry_price = chunk_1min.iloc[0]['Open']
|
||||
|
||||
# Calculate ATR-based SL/TP
|
||||
atr = prev_row['atr']
|
||||
if pd.isna(atr) or atr == 0:
|
||||
atr = row['open'] * 0.01 # Fallback 1%
|
||||
|
||||
sl_dist = atr * config.SL_ATR_MULT
|
||||
tp_dist = atr * config.TP_ATR_MULT
|
||||
|
||||
current_sl_price = entry_price - sl_dist
|
||||
current_tp_price = entry_price + tp_dist
|
||||
|
||||
evt = enter_long(state, entry_price)
|
||||
if evt:
|
||||
evt.update({
|
||||
"t": ts_start.isoformat(),
|
||||
"reason": "signal_entry",
|
||||
"sl": current_sl_price,
|
||||
"tp": current_tp_price
|
||||
})
|
||||
trades.append(evt)
|
||||
|
||||
# Update Equity Curve (mark-to-market at close of hour)
|
||||
current_price = row['close']
|
||||
val = state.cash + (state.qty * current_price)
|
||||
equity.append({'timestamp': ts_start, 'equity': val})
|
||||
|
||||
# Create Equity Series
|
||||
equity_df = pd.DataFrame(equity).set_index('timestamp')
|
||||
equity_curve = equity_df['equity']
|
||||
|
||||
# Save Logs
|
||||
if log_path:
|
||||
write_trade_log(trades, log_path)
|
||||
|
||||
# Metrics (hourly bars: 252 trading days * 24 hours = 6048 periods/year)
|
||||
perf = compute_metrics(equity_curve, trades, periods_per_year=252 * 24)
|
||||
|
||||
# Print Summary
|
||||
print("\n--- Backtest Summary ---")
|
||||
print(f"Total Return: {perf.total_return * 100:.2f}%")
|
||||
print(f"Sharpe Ratio: {perf.sharpe_ratio:.2f}")
|
||||
print(f"Max Drawdown: {perf.max_drawdown * 100:.2f}%")
|
||||
print(f"Total Trades: {perf.num_trades}")
|
||||
|
||||
return perf, equity_curve, trades
|
||||
|
||||
import argparse
|
||||
def run():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--csv", required=True, help="Path to 1m/15m OHLCV CSV")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load 1M Data
|
||||
print(f"Loading 1m/15m data from {args.csv}...")
|
||||
df_1min = pd.read_csv(args.csv)
|
||||
# Ensure Timestamp
|
||||
if 'Timestamp' in df_1min.columns:
|
||||
ts_max = df_1min['Timestamp'].max()
|
||||
if ts_max < 3000000000:
|
||||
unit = 's'
|
||||
elif ts_max < 3000000000000:
|
||||
unit = 'ms'
|
||||
else:
|
||||
unit = None
|
||||
df_1min['Timestamp'] = pd.to_datetime(df_1min['Timestamp'], unit=unit)
|
||||
elif 'Date' in df_1min.columns:
|
||||
df_1min['Timestamp'] = pd.to_datetime(df_1min['Date'])
|
||||
|
||||
df_1min = df_1min.sort_values('Timestamp')
|
||||
|
||||
# Load Features (1H)
|
||||
print(f"Loading features from {config.FEATURES_PATH}...")
|
||||
if not os.path.exists(config.FEATURES_PATH):
|
||||
print("Error: features.csv not found. Run prepare_data.py first.")
|
||||
return
|
||||
|
||||
df_features = pd.read_csv(config.FEATURES_PATH, parse_dates=['timestamp'], index_col='timestamp')
|
||||
|
||||
# Run Backtest
|
||||
backtest_mvrv(df_features, df_1min, log_path=Path("logs/mvrv_trade_log.csv"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
263
cryptoquant_client.py
Normal file
263
cryptoquant_client.py
Normal file
@ -0,0 +1,263 @@
|
||||
import ssl
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional
|
||||
import logging
|
||||
import strategy_config as config
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class RateLimiter:
|
||||
"""Simple rate limiter for CryptoQuant's API."""
|
||||
|
||||
def __init__(self, max_requests: int = None, window_seconds: int = 60):
|
||||
# Default to 20 if not specified, or read from env
|
||||
if max_requests is None:
|
||||
max_requests = int(os.getenv('CRYPTOQUANT_RATE_LIMIT', 20))
|
||||
|
||||
self.max_requests = max_requests
|
||||
self.window_seconds = window_seconds
|
||||
self.requests = [] # List of timestamps
|
||||
|
||||
def wait_if_needed(self) -> None:
|
||||
"""Sleep if we've hit the rate limit in current window."""
|
||||
now = time.time()
|
||||
|
||||
# Remove requests outside the window
|
||||
self.requests = [ts for ts in self.requests if now -
|
||||
ts < self.window_seconds]
|
||||
|
||||
# If at limit, sleep until oldest request expires
|
||||
if len(self.requests) >= self.max_requests:
|
||||
sleep_time = self.window_seconds - (now - self.requests[0])
|
||||
if sleep_time > 0:
|
||||
logger.warning(
|
||||
f"Rate limit hit. Sleeping {sleep_time:.1f}s...")
|
||||
time.sleep(sleep_time)
|
||||
|
||||
# Record this request
|
||||
self.requests.append(now)
|
||||
|
||||
def get_remaining(self) -> int:
|
||||
"""Get requests remaining in current window."""
|
||||
now = time.time()
|
||||
recent_requests = [
|
||||
ts for ts in self.requests if now - ts < self.window_seconds]
|
||||
return self.max_requests - len(recent_requests)
|
||||
|
||||
|
||||
class CryptoQuantClient:
|
||||
def __init__(self, api_key: str = None):
|
||||
self.api_key = api_key or os.getenv('CRYPTOQUANT_API_KEY')
|
||||
if not self.api_key:
|
||||
raise ValueError("API key required - CRYPTOQUANT_API_KEY env var not set.")
|
||||
|
||||
self.base_url = 'https://api.cryptoquant.com/v1'
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_key}',
|
||||
'User-Agent': f'CryptoQuantBot/{random.uniform(1.0, 2.0):.1f}'
|
||||
}
|
||||
self.logger = logger
|
||||
self.rate_limiter = RateLimiter()
|
||||
|
||||
# Create a robust session with retries
|
||||
self.session = requests.Session()
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
ssl_context.options |= 0x80000000
|
||||
retry_strategy = Retry(
|
||||
total=5,
|
||||
status_forcelist=[429, 500, 502, 503, 504],
|
||||
allowed_methods=["HEAD", "GET", "OPTIONS"],
|
||||
backoff_factor=1
|
||||
)
|
||||
|
||||
class SSLAdapter(HTTPAdapter):
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = ssl_context
|
||||
return super().init_poolmanager(*args, **kwargs)
|
||||
|
||||
adapter = SSLAdapter(max_retries=retry_strategy)
|
||||
self.session.mount("https://", adapter)
|
||||
self.session.headers.update(headers)
|
||||
|
||||
def _make_request(self, method: str, url: str, **kwargs) -> requests.Response:
|
||||
"""Internal method with robust error handling."""
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
response = self.session.get(url, **kwargs, timeout=30)
|
||||
elif method.upper() == 'POST':
|
||||
response = self.session.post(url, **kwargs, timeout=30)
|
||||
else:
|
||||
raise ValueError(f"Unsupported method: {method}")
|
||||
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.error(f"Request failed after all retries: {e}")
|
||||
raise
|
||||
|
||||
def _fetch_metric_chunked(self, url_suffix: str, params: Dict, days_back: int, chunk_days: int = 90) -> List[Dict]:
|
||||
"""Fetch data in chunks to avoid API limits."""
|
||||
all_data = []
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=days_back)
|
||||
|
||||
current_start = start_date
|
||||
while current_start < end_date:
|
||||
current_end = min(current_start + timedelta(days=chunk_days), end_date)
|
||||
|
||||
# Format dates for API
|
||||
chunk_params = params.copy()
|
||||
chunk_params.update({
|
||||
'from': current_start.strftime('%Y%m%d'),
|
||||
'to': current_end.strftime('%Y%m%d'),
|
||||
'limit': 10000 # Request max limit just in case
|
||||
})
|
||||
|
||||
self.rate_limiter.wait_if_needed()
|
||||
url = f"{self.base_url}/{url_suffix}"
|
||||
|
||||
try:
|
||||
self.logger.info(f"Fetching chunk {chunk_params['from']} to {chunk_params['to']}")
|
||||
response = self._make_request('GET', url, params=chunk_params)
|
||||
|
||||
# Check for specific "Out of allowed range" error to stop early if needed?
|
||||
# Actually, if we iterate Old -> New, the early chunks fail, later succeed.
|
||||
# If we want to optimize, we could start New -> Old and stop on failure.
|
||||
# But current Old -> New approach ensures we get the most recent valid data eventually.
|
||||
|
||||
data = response.json()
|
||||
result = data.get('result', {}).get('data', [])
|
||||
all_data.extend(result)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# Handle 400 specially if it's "Out of allowed request range"
|
||||
if e.response.status_code == 400:
|
||||
self.logger.warning(f"Chunk failed (likely data limit): {e}. Continuing to next chunk...")
|
||||
else:
|
||||
self.logger.error(f"HTTP Error fetching chunk: {e}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching chunk: {e}")
|
||||
|
||||
current_start = current_end + timedelta(days=1) # Move to next day
|
||||
time.sleep(0.5) # Gentle pace
|
||||
|
||||
# Remove duplicates based on date if any? usually API is clean.
|
||||
return all_data
|
||||
|
||||
def get_nupl(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 364) -> List[Dict]:
|
||||
logger.info(f"Fetching {asset} NUPL ({days_back} days)")
|
||||
params = {'window': window}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/network-indicator/nupl", params, days_back)
|
||||
|
||||
def get_mvrv(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 364) -> List[Dict]:
|
||||
logger.info(f"Fetching {asset} MVRV ({days_back} days)")
|
||||
params = {'window': window}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/market-indicator/mvrv", params, days_back)
|
||||
|
||||
def get_lth_sopr(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 364) -> List[Dict]:
|
||||
logger.info(f"Fetching {asset} LTH-SOPR ({days_back} days)")
|
||||
params = {'window': window}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/market-indicator/sopr", params, days_back)
|
||||
|
||||
def get_puell_multiple(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 364) -> List[Dict]:
|
||||
logger.info(f"Fetching {asset} Puell Multiple ({days_back} days)")
|
||||
params = {'window': window}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/network-indicator/puell-multiple", params, days_back)
|
||||
|
||||
def get_fund_flow_ratio(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 364) -> List[Dict]:
|
||||
logger.info(f"Fetching {asset} Fund Flow Ratio ({days_back} days)")
|
||||
params = {'window': window, 'exchange': 'all_exchange'}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/flow-indicator/fund-flow-ratio", params, days_back)
|
||||
|
||||
def get_funding_rates(self, asset: str = config.ASSET, exchange: str = 'all_exchange', window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
self.logger.info(f"Fetching {asset} funding rates ({days_back} days)")
|
||||
params = {'window': window, 'exchange': exchange}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/market-data/funding-rates", params, days_back)
|
||||
|
||||
def get_exchange_net_flow(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
self.logger.info(f"Fetching {asset} exchange netflow ({days_back} days)")
|
||||
params = {'window': window, 'exchange': 'all_exchange'}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/exchange-flows/netflow", params, days_back)
|
||||
|
||||
def get_sopr_ratio(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
self.logger.info(f"Fetching {asset} SOPR Ratio ({days_back} days)")
|
||||
params = {'window': window}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/market-indicator/sopr-ratio", params, days_back)
|
||||
|
||||
def get_active_addresses(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
self.logger.info(f"Fetching {asset} active addresses ({days_back} days)")
|
||||
params = {'window': window}
|
||||
if asset.lower() == 'eth':
|
||||
suffix = f"{asset.lower()}/network-data/addresses-count-all"
|
||||
else:
|
||||
suffix = f"{asset.lower()}/network-data/addresses-count"
|
||||
return self._fetch_metric_chunked(suffix, params, days_back)
|
||||
|
||||
def get_leverage_ratio(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
"""Get Estimated Leverage Ratio."""
|
||||
self.logger.info(f"Fetching {asset} Estimated Leverage Ratio ({days_back} days)")
|
||||
params = {'window': window, 'exchange': 'all_exchange'}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/market-indicator/estimated-leverage-ratio", params, days_back)
|
||||
|
||||
def get_exchange_whale_ratio(self, asset: str = config.ASSET, window: str = 'day', days_back: int = 90) -> List[Dict]:
|
||||
"""Get the ratio of whale-sized deposits to total exchange deposits."""
|
||||
self.logger.info(f"Fetching {asset} Exchange Whale Ratio ({days_back} days)")
|
||||
params = {'window': window, 'exchange': 'all_exchange'}
|
||||
return self._fetch_metric_chunked(f"{asset.lower()}/flow-indicator/exchange-whale-ratio", params, days_back)
|
||||
|
||||
def fetch_all_onchain(self, asset: str = config.ASSET, days_back: int = 364) -> Dict[str, List[Dict]]:
|
||||
"""Batch all features from config.ONCHAIN_FEATURE_NAMES."""
|
||||
features = {}
|
||||
for feat in config.ONCHAIN_FEATURE_NAMES:
|
||||
self.rate_limiter.wait_if_needed()
|
||||
if feat == 'funding_rate':
|
||||
features[feat] = self.get_funding_rates(
|
||||
asset, 'all_exchange', 'day', days_back)
|
||||
elif feat == 'net_exchange_flow':
|
||||
features[feat] = self.get_exchange_net_flow(
|
||||
asset, 'day', days_back)
|
||||
elif feat == 'sopr_ratio':
|
||||
features[feat] = self.get_sopr_ratio(asset, 'day', days_back)
|
||||
elif feat == 'active_addresses':
|
||||
features[feat] = self.get_active_addresses(
|
||||
asset, 'day', days_back)
|
||||
elif feat == 'leverage_ratio':
|
||||
features[feat] = self.get_leverage_ratio(
|
||||
asset, 'day', days_back)
|
||||
elif feat == 'exchange_whale_ratio':
|
||||
features[feat] = self.get_exchange_whale_ratio(
|
||||
asset, 'day', days_back)
|
||||
elif feat == 'nupl':
|
||||
features[feat] = self.get_nupl(asset, 'day', days_back)
|
||||
elif feat == 'mvrv':
|
||||
features[feat] = self.get_mvrv(asset, 'day', days_back)
|
||||
elif feat == 'lth_sopr':
|
||||
features[feat] = self.get_lth_sopr(asset, 'day', days_back)
|
||||
elif feat == 'puell_multiple':
|
||||
features[feat] = self.get_puell_multiple(
|
||||
asset, 'day', days_back)
|
||||
elif feat == 'fund_flow_ratio':
|
||||
features[feat] = self.get_fund_flow_ratio(
|
||||
asset, 'day', days_back)
|
||||
|
||||
time.sleep(1) # Pace for limits
|
||||
return features
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test
|
||||
try:
|
||||
client = CryptoQuantClient()
|
||||
print("Fetching funding rates as test...")
|
||||
rates = client.get_funding_rates(days_back=7)
|
||||
print(f"Got {len(rates)} records")
|
||||
if rates:
|
||||
print(rates[-1])
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
164
features.py
Normal file
164
features.py
Normal file
@ -0,0 +1,164 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import strategy_config as config
|
||||
from numba import njit
|
||||
|
||||
# ==============================================================================
|
||||
# --- LOOKBACK WINDOW FOR ROLLING Z-SCORES (PREVENTS LOOK-AHEAD BIAS) ---
|
||||
# ==============================================================================
|
||||
ZSCORE_LOOKBACK = 168 # 1 week in hourly candles
|
||||
MIN_PERIODS_ZSCORE = 24
|
||||
|
||||
@njit
|
||||
def ema_nb(arr, window):
|
||||
alpha = 2.0 / (window + 1.0)
|
||||
ema_arr = np.full_like(arr, np.nan)
|
||||
ema_arr[0] = arr[0]
|
||||
for i in range(1, len(arr)):
|
||||
ema_arr[i] = alpha * arr[i] + (1.0 - alpha) * ema_arr[i-1]
|
||||
return ema_arr
|
||||
|
||||
@njit
|
||||
def atr_nb(high, low, close, window):
|
||||
n = len(close)
|
||||
tr = np.zeros(n)
|
||||
atr = np.full_like(close, np.nan)
|
||||
for i in range(1, n):
|
||||
tr[i] = max(high[i] - low[i], abs(high[i] - close[i-1]),
|
||||
abs(low[i] - close[i-1]))
|
||||
tr_series = tr[1:window+1]
|
||||
atr[window] = np.mean(tr_series)
|
||||
for i in range(window + 1, n):
|
||||
atr[i] = (atr[i-1] * (window - 1) + tr[i]) / window
|
||||
return atr
|
||||
|
||||
def _add_price_action_features(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Adds price action based features like returns, momentum, SMAs."""
|
||||
# Ensure column names match what we expect (lowercase usually)
|
||||
# If user has 'Close', 'Open' etc. we might need to adjust or rename before calling this.
|
||||
# Assuming the input df has lowercase 'close', 'high', 'low' etc. or we handle it.
|
||||
|
||||
# Map typical Capitalized names to lowercase if needed
|
||||
mapper = {c: c.lower() for c in df.columns if c in ['Open', 'High', 'Low', 'Close', 'Volume']}
|
||||
if mapper:
|
||||
df = df.rename(columns=mapper)
|
||||
|
||||
df['returns'] = df['close'].pct_change()
|
||||
df['log_returns'] = np.log(df['close'] / df['close'].shift(1))
|
||||
|
||||
df[f'momentum_{config.MOMENTUM_1_PERIODS}'] = df['close'].pct_change(
|
||||
periods=config.MOMENTUM_1_PERIODS)
|
||||
df[f'momentum_{config.MOMENTUM_2_PERIODS}'] = df['close'].pct_change(
|
||||
periods=config.MOMENTUM_2_PERIODS)
|
||||
|
||||
df[f'SMA_{config.SMA_FAST_PERIODS}'] = df['close'].rolling(
|
||||
window=config.SMA_FAST_PERIODS).mean()
|
||||
df[f'SMA_{config.SMA_SLOW_PERIODS}'] = df['close'].rolling(
|
||||
window=config.SMA_SLOW_PERIODS).mean()
|
||||
|
||||
df[f'volatility_{config.VOLATILITY_PERIODS}'] = df['log_returns'].rolling(
|
||||
window=config.VOLATILITY_PERIODS).std()
|
||||
return df
|
||||
|
||||
def _add_mean_reversion_features(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Adds Bollinger Band features."""
|
||||
window = config.BBAND_PERIODS
|
||||
df['bb_middle'] = df['close'].rolling(window=window).mean()
|
||||
bb_std = df['close'].rolling(window=window).std()
|
||||
df['bb_upper'] = df['bb_middle'] + (bb_std * 2)
|
||||
df['bb_lower'] = df['bb_middle'] - (bb_std * 2)
|
||||
|
||||
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
|
||||
|
||||
denom = (df['bb_upper'] - df['bb_lower'])
|
||||
denom = denom.replace(0, np.nan)
|
||||
df['bb_percent'] = (df['close'] - df['bb_lower']) / denom
|
||||
return df
|
||||
|
||||
def _add_volatility_features(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Adds ATR features."""
|
||||
atr_values = atr_nb(
|
||||
df['high'].to_numpy(),
|
||||
df['low'].to_numpy(),
|
||||
df['close'].to_numpy(),
|
||||
config.ATR_PERIOD
|
||||
)
|
||||
df['atr'] = atr_values
|
||||
return df
|
||||
|
||||
def _add_onchain_features(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Adds on-chain features with z-score normalization and cycle MAs."""
|
||||
for col_name in config.ONCHAIN_FEATURE_NAMES:
|
||||
if col_name in df.columns:
|
||||
col_data = df[col_name]
|
||||
# Ensure numeric
|
||||
df[col_name] = pd.to_numeric(col_data, errors='coerce')
|
||||
|
||||
# Custom transforms
|
||||
if col_name == 'net_exchange_flow':
|
||||
df[col_name] = -df[col_name] # Outflows bullish
|
||||
elif col_name == 'funding_rate':
|
||||
df[col_name] *= 100 # To %
|
||||
|
||||
# Rolling Z-Score (prevents look-ahead bias)
|
||||
if df[col_name].notna().sum() > MIN_PERIODS_ZSCORE:
|
||||
rolling_mean = df[col_name].rolling(
|
||||
window=ZSCORE_LOOKBACK, min_periods=MIN_PERIODS_ZSCORE).mean()
|
||||
rolling_std = df[col_name].rolling(
|
||||
window=ZSCORE_LOOKBACK, min_periods=MIN_PERIODS_ZSCORE).std()
|
||||
|
||||
rolling_std = rolling_std.replace(0, np.nan)
|
||||
df[f'{col_name}_z'] = (df[col_name] - rolling_mean) / rolling_std
|
||||
|
||||
# Cycle MAs for key metrics
|
||||
if col_name == 'nupl':
|
||||
df[f'{col_name}_ma_{config.NUPL_MA_PERIODS}'] = df[col_name].rolling(
|
||||
config.NUPL_MA_PERIODS).mean()
|
||||
elif col_name == 'mvrv':
|
||||
df[f'{col_name}_ma_{config.MVRV_MA_PERIODS}'] = df[col_name].rolling(
|
||||
config.MVRV_MA_PERIODS).mean()
|
||||
else:
|
||||
# Create NaN columns if missing so model doesn't crash (or handle later)
|
||||
df[f'{col_name}_z'] = np.nan
|
||||
|
||||
return df
|
||||
|
||||
def _add_target_variables(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Adds target variables for ML training."""
|
||||
# 1% gain threshold in prediction horizon
|
||||
df['future_price'] = df['close'].shift(-config.PREDICTION_PERIOD)
|
||||
future_ret = (df['future_price'] - df['close']) / df['close']
|
||||
|
||||
df['target'] = (future_ret > 0.01).astype(int)
|
||||
return df
|
||||
|
||||
def create_features(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Engineers all features using modular sub-functions."""
|
||||
df_feat = df.copy()
|
||||
|
||||
# Pass through raw data needed for ATR/exits
|
||||
# Ensure lowercase columns first
|
||||
mapper = {c: c.lower() for c in df_feat.columns if c in ['Open', 'High', 'Low', 'Close', 'Volume']}
|
||||
if mapper:
|
||||
df_feat = df_feat.rename(columns=mapper)
|
||||
|
||||
df_feat['high_raw'] = df_feat['high']
|
||||
df_feat['low_raw'] = df_feat['low']
|
||||
df_feat['close_raw'] = df_feat['close']
|
||||
|
||||
# 1. Price-Action
|
||||
df_feat = _add_price_action_features(df_feat)
|
||||
|
||||
# 2. Mean-Reversion
|
||||
df_feat = _add_mean_reversion_features(df_feat)
|
||||
|
||||
# 3. Volatility
|
||||
df_feat = _add_volatility_features(df_feat)
|
||||
|
||||
# 4. On-Chain
|
||||
df_feat = _add_onchain_features(df_feat)
|
||||
|
||||
# 5. Target
|
||||
df_feat = _add_target_variables(df_feat)
|
||||
|
||||
return df_feat
|
||||
18
metrics.py
18
metrics.py
@ -19,7 +19,21 @@ class Perf:
|
||||
avg_slippage_bps: float
|
||||
|
||||
|
||||
def compute_metrics(equity_curve: pd.Series, trades: list[dict]) -> Perf:
|
||||
def compute_metrics(equity_curve: pd.Series, trades: list[dict], periods_per_year: int = None) -> Perf:
|
||||
"""
|
||||
Compute backtest performance metrics.
|
||||
|
||||
Args:
|
||||
equity_curve: Series of portfolio values over time
|
||||
trades: List of trade event dictionaries
|
||||
periods_per_year: Number of periods in a year for Sharpe annualization.
|
||||
Defaults to hourly (252 * 24 = 6048).
|
||||
For minute bars: 252 * 24 * 60 = 525600
|
||||
For daily bars: 252
|
||||
"""
|
||||
if periods_per_year is None:
|
||||
periods_per_year = 252 * 24 # Default to hourly bars
|
||||
|
||||
ret = equity_curve.pct_change().fillna(0.0)
|
||||
total_return = equity_curve.iat[-1] / equity_curve.iat[0] - 1.0
|
||||
cummax = equity_curve.cummax()
|
||||
@ -27,7 +41,7 @@ def compute_metrics(equity_curve: pd.Series, trades: list[dict]) -> Perf:
|
||||
max_drawdown = dd
|
||||
|
||||
if ret.std(ddof=0) > 0:
|
||||
sharpe = (ret.mean() / ret.std(ddof=0)) * np.sqrt(252 * 24 * 60) # minute bars -> annualized
|
||||
sharpe = (ret.mean() / ret.std(ddof=0)) * np.sqrt(periods_per_year)
|
||||
else:
|
||||
sharpe = 0.0
|
||||
|
||||
|
||||
140
prepare_data.py
Normal file
140
prepare_data.py
Normal file
@ -0,0 +1,140 @@
|
||||
import pandas as pd
|
||||
import strategy_config as config
|
||||
from cryptoquant_client import CryptoQuantClient
|
||||
from features import create_features
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
|
||||
def fetch_onchain_data(client, asset='BTC', days_back=365*2):
|
||||
"""Fetches and aligns on-chain data."""
|
||||
print(f"Fetching on-chain data for {asset}...")
|
||||
|
||||
# 1. Fetch all metrics
|
||||
# Note: This might take a while due to rate limits
|
||||
raw_data = client.fetch_all_onchain(asset, days_back=days_back)
|
||||
|
||||
dfs = []
|
||||
for metric_name, records in raw_data.items():
|
||||
if not records:
|
||||
print(f"⚠️ No data for {metric_name}")
|
||||
continue
|
||||
|
||||
df = pd.DataFrame(records)
|
||||
# Standardize date column
|
||||
if 'date' in df.columns:
|
||||
df['timestamp'] = pd.to_datetime(df['date'])
|
||||
else:
|
||||
# Try to find date-like column
|
||||
cols = [c for c in df.columns if 'date' in c or 'time' in c]
|
||||
if cols:
|
||||
df['timestamp'] = pd.to_datetime(df[cols[0]])
|
||||
else:
|
||||
print(f"❌ Could not find date column for {metric_name}")
|
||||
continue
|
||||
|
||||
df = df.set_index('timestamp').sort_index()
|
||||
|
||||
# Keep only the value column
|
||||
# Value column name varies by endpoint.
|
||||
# usually same as metric name or 'value' or specific name.
|
||||
# Simple heuristic: take the first numeric column that isn't 'timestamp'
|
||||
numeric_cols = df.select_dtypes(include=['number']).columns
|
||||
if len(numeric_cols) > 0:
|
||||
val_col = numeric_cols[0]
|
||||
df = df[[val_col]].rename(columns={val_col: metric_name})
|
||||
|
||||
# Resample to hourly and forward fill (since on-chain is daily/block)
|
||||
# CAUTION: Daily data from CryptoQuant (e.g. Total Active Addresses) is usually
|
||||
# timestamped at 00:00 but represents the FULL day's activity.
|
||||
# If we use it at 10:00 AM on the same day, that is Lookahead Bias.
|
||||
# We must SHIFT it by 1 day to ensure we only use it AFTER it's available (next day).
|
||||
# Funding rates might be 8h, but 'window=day' implies daily aggregation.
|
||||
# Safer to lag by 24h.
|
||||
|
||||
df = df.shift(1, freq='D') # Shift index by 1 Day
|
||||
|
||||
df = df.resample('1h').ffill()
|
||||
dfs.append(df)
|
||||
else:
|
||||
print(f"❌ No numeric data for {metric_name}")
|
||||
|
||||
if not dfs:
|
||||
return pd.DataFrame()
|
||||
|
||||
# Concatenate all on-chain metrics
|
||||
onchain_df = pd.concat(dfs, axis=1)
|
||||
return onchain_df
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Prepare data for MVRV Strategy")
|
||||
parser.add_argument("--csv", required=True, help="Path to OHLCV CSV file")
|
||||
parser.add_argument("--days", type=int, default=365, help="Days of on-chain data to fetch")
|
||||
args = parser.parse_args()
|
||||
|
||||
# 1. Load OHLCV
|
||||
print(f"Loading OHLCV from {args.csv}...")
|
||||
df_ohlcv = pd.read_csv(args.csv)
|
||||
|
||||
# Standardize OHLCV columns/index
|
||||
# Expecting Timestamp/Date column
|
||||
if 'Timestamp' in df_ohlcv.columns:
|
||||
# Smart detection of unit
|
||||
ts_max = df_ohlcv['Timestamp'].max()
|
||||
if ts_max < 3000000000: # < 3B, likely seconds (valid until ~2065)
|
||||
unit = 's'
|
||||
elif ts_max < 3000000000000: # < 3T, likely milliseconds
|
||||
unit = 'ms'
|
||||
else:
|
||||
unit = None # Let pandas guess (ns?)
|
||||
|
||||
df_ohlcv['timestamp'] = pd.to_datetime(df_ohlcv['Timestamp'], unit=unit)
|
||||
elif 'Date' in df_ohlcv.columns:
|
||||
df_ohlcv['timestamp'] = pd.to_datetime(df_ohlcv['Date'])
|
||||
|
||||
df_ohlcv = df_ohlcv.set_index('timestamp').sort_index()
|
||||
|
||||
# Resample to 1H for feature engineering
|
||||
df_1h = df_ohlcv.resample('1h').agg({
|
||||
'Open': 'first',
|
||||
'High': 'max',
|
||||
'Low': 'min',
|
||||
'Close': 'last',
|
||||
'Volume': 'sum'
|
||||
}).dropna()
|
||||
|
||||
# Rename to lowercase for features.py
|
||||
df_1h = df_1h.rename(columns={
|
||||
'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume'
|
||||
})
|
||||
|
||||
print(f"OHLCV 1H shape: {df_1h.shape}")
|
||||
|
||||
# 2. Fetch On-Chain
|
||||
client = CryptoQuantClient()
|
||||
df_onchain = fetch_onchain_data(client, asset=config.ASSET, days_back=args.days)
|
||||
print(f"On-Chain shape: {df_onchain.shape}")
|
||||
|
||||
# 3. Merge
|
||||
# Left join on OHLCV index
|
||||
df_merged = df_1h.join(df_onchain, how='left')
|
||||
|
||||
# Forward fill on-chain data (it's slower than price)
|
||||
df_merged = df_merged.ffill()
|
||||
|
||||
# Drop rows where we still have NaNs (start of data)
|
||||
df_merged = df_merged.dropna()
|
||||
print(f"Merged shape: {df_merged.shape}")
|
||||
|
||||
# 4. Create Features
|
||||
print("Engineering features...")
|
||||
df_features = create_features(df_merged)
|
||||
df_features = df_features.dropna()
|
||||
|
||||
# 5. Save
|
||||
print(f"Saving features to {config.FEATURES_PATH}...")
|
||||
df_features.to_csv(config.FEATURES_PATH)
|
||||
print("Done.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -5,5 +5,10 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"numba>=0.63.1",
|
||||
"python-dotenv>=1.2.1",
|
||||
"requests>=2.32.5",
|
||||
"scikit-learn>=1.8.0",
|
||||
"ta>=0.11.0",
|
||||
"xgboost>=3.1.2",
|
||||
]
|
||||
|
||||
95
strategy_config.py
Normal file
95
strategy_config.py
Normal file
@ -0,0 +1,95 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# ==============================================================================
|
||||
# --- 1. MASTER STRATEGY CONFIG ---
|
||||
# ==============================================================================
|
||||
ASSET = 'BTC'
|
||||
TIMEFRAME_HOURS = 1.0 # We work with 1h candles for the ML model
|
||||
|
||||
# --- Define Strategy Logic (in HOURS) ---
|
||||
PREDICT_HOURS = 5.0
|
||||
SMA_FAST_HOURS = 12.0
|
||||
SMA_SLOW_HOURS = 50.0
|
||||
VOLATILITY_HOURS = 12.0
|
||||
BBAND_HOURS = 20.0
|
||||
MOMENTUM_1_HOURS = 5.0
|
||||
MOMENTUM_2_HOURS = 10.0
|
||||
ATR_PERIOD_HOURS = 10.0
|
||||
|
||||
# --- Backtest / Trade Config ---
|
||||
PROB_THRESHOLD = 0.55
|
||||
SL_ATR_MULT = 1.5 # Widened from 0.8
|
||||
TP_ATR_MULT = 3.0 # Widened from 1.5
|
||||
FEES_PERCENT = 0.001
|
||||
SLIPPAGE_PERCENT = 0.001
|
||||
|
||||
# --- MVRV/NUPL Strategy-Specific Config ---
|
||||
NUPL_MA_HOURS = 200.0 # ~200-day MA (normalized to hours if needed, source used hours directly)
|
||||
MVRV_MA_HOURS = 111.0 # ~111-day MA
|
||||
|
||||
# Thresholds for "Overheated" Regime
|
||||
MVRV_Z_THRESH = 1.5
|
||||
NUPL_THRESH = 0.6
|
||||
FUNDING_FILTER = -0.05 # Filter out if funding rate is below this (but source used > -0.05, wait check source)
|
||||
|
||||
# Source: (fund_grid_bool_base > funding_rate_filters_b) where filter is -0.05.
|
||||
# So we want Funding Rate > -0.05.
|
||||
|
||||
# ==============================================================================
|
||||
# --- 2. CRYPTOQUANT METRICS ---
|
||||
# ==============================================================================
|
||||
ONCHAIN_FEATURE_NAMES = [
|
||||
'funding_rate', 'sopr_ratio', 'leverage_ratio',
|
||||
'net_exchange_flow', 'fund_flow_ratio',
|
||||
'exchange_whale_ratio',
|
||||
'nupl', 'mvrv', 'lth_sopr', 'puell_multiple',
|
||||
'active_addresses'
|
||||
]
|
||||
|
||||
# ==============================================================================
|
||||
# --- 3. AUTO-CALCULATED PARAMETERS ---
|
||||
# ==============================================================================
|
||||
import math
|
||||
|
||||
def hours_to_candles(hours):
|
||||
return math.ceil(hours / TIMEFRAME_HOURS)
|
||||
|
||||
PREDICTION_PERIOD = hours_to_candles(PREDICT_HOURS)
|
||||
SMA_FAST_PERIODS = hours_to_candles(SMA_FAST_HOURS)
|
||||
SMA_SLOW_PERIODS = hours_to_candles(SMA_SLOW_HOURS)
|
||||
VOLATILITY_PERIODS = hours_to_candles(VOLATILITY_HOURS)
|
||||
BBAND_PERIODS = hours_to_candles(BBAND_HOURS)
|
||||
MOMENTUM_1_PERIODS = hours_to_candles(MOMENTUM_1_HOURS)
|
||||
MOMENTUM_2_PERIODS = hours_to_candles(MOMENTUM_2_HOURS)
|
||||
ATR_PERIOD = hours_to_candles(ATR_PERIOD_HOURS)
|
||||
|
||||
NUPL_MA_PERIODS = hours_to_candles(NUPL_MA_HOURS)
|
||||
MVRV_MA_PERIODS = hours_to_candles(MVRV_MA_HOURS)
|
||||
|
||||
# --- FEATURE NAMES ---
|
||||
BASE_FEATURE_NAMES = [
|
||||
'returns', 'log_returns', f'momentum_{MOMENTUM_1_PERIODS}',
|
||||
f'momentum_{MOMENTUM_2_PERIODS}', f'SMA_{SMA_FAST_PERIODS}',
|
||||
f'SMA_{SMA_SLOW_PERIODS}', f'volatility_{VOLATILITY_PERIODS}',
|
||||
'bb_lower', 'bb_middle', 'bb_upper', 'bb_width', 'bb_percent', 'atr'
|
||||
]
|
||||
|
||||
# Append cycle MAs
|
||||
BASE_FEATURE_NAMES += [
|
||||
f'nupl_ma_{NUPL_MA_PERIODS}', f'mvrv_ma_{MVRV_MA_PERIODS}'
|
||||
]
|
||||
|
||||
ONCHAIN_Z_SCORES = [f'{feat}_z' for feat in ONCHAIN_FEATURE_NAMES]
|
||||
FEATURE_NAMES = BASE_FEATURE_NAMES + ONCHAIN_Z_SCORES
|
||||
|
||||
# --- PATHS ---
|
||||
DATA_DIR = 'data'
|
||||
if not os.path.exists(DATA_DIR):
|
||||
os.makedirs(DATA_DIR)
|
||||
|
||||
FEATURES_PATH = os.path.join(DATA_DIR, 'features.csv')
|
||||
MODEL_PATH = os.path.join(DATA_DIR, 'model.pkl')
|
||||
129
train_model.py
Normal file
129
train_model.py
Normal file
@ -0,0 +1,129 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import xgboost as xgb
|
||||
import pickle
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import accuracy_score, classification_report, precision_recall_fscore_support
|
||||
import strategy_config as config
|
||||
|
||||
def train_model():
|
||||
print(f"--- Starting Model Training Pipeline ---")
|
||||
|
||||
try:
|
||||
if not os.path.exists(config.FEATURES_PATH):
|
||||
print(f"Error: {config.FEATURES_PATH} not found. Run prepare_data.py first.")
|
||||
return
|
||||
|
||||
df = pd.read_csv(config.FEATURES_PATH)
|
||||
# Ensure index if needed, but read_csv usually reads generic index unless specified
|
||||
# prepare_data saved with index (timestamp)
|
||||
if 'timestamp' in df.columns:
|
||||
df = df.set_index('timestamp')
|
||||
|
||||
print(f"Loaded {len(df)} data points from {config.FEATURES_PATH}")
|
||||
|
||||
y = df['target']
|
||||
print(f"Buy signals rate: {y.mean():.1%}")
|
||||
|
||||
# Use the dynamic feature list directly from config.py
|
||||
# Check if all features exist
|
||||
available_feats = [f for f in config.FEATURE_NAMES if f in df.columns]
|
||||
missing_feats = [f for f in config.FEATURE_NAMES if f not in df.columns]
|
||||
|
||||
if missing_feats:
|
||||
print(f"⚠️ Missing features: {missing_feats}")
|
||||
print(f"Proceeding with {len(available_feats)} features.")
|
||||
|
||||
X = df[available_feats]
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
X, y, test_size=0.3, shuffle=False
|
||||
)
|
||||
|
||||
# Save the test set start index for the backtester to use
|
||||
# This prevents train/test leakage during backtesting
|
||||
test_start_idx = len(X_train)
|
||||
test_start_timestamp = df.index[test_start_idx] if hasattr(df.index, '__getitem__') else test_start_idx
|
||||
|
||||
# Save split info
|
||||
split_info = {
|
||||
'test_start_idx': test_start_idx,
|
||||
'test_start_timestamp': str(test_start_timestamp),
|
||||
'train_size': len(X_train),
|
||||
'test_size': len(X_test)
|
||||
}
|
||||
split_info_path = config.MODEL_PATH.replace('.pkl', '_split.pkl')
|
||||
with open(split_info_path, 'wb') as f:
|
||||
pickle.dump(split_info, f)
|
||||
print(f"Split info saved: Test starts at index {test_start_idx} ({test_start_timestamp})")
|
||||
|
||||
print(f"Training set size: {len(X_train)}")
|
||||
print(f"Test set size: {len(X_test)}")
|
||||
|
||||
print("\nTraining XGBoost model...")
|
||||
model = xgb.XGBClassifier(
|
||||
objective='binary:logistic',
|
||||
eval_metric='logloss',
|
||||
n_estimators=200,
|
||||
learning_rate=0.05,
|
||||
scale_pos_weight=8.0,
|
||||
max_depth=5,
|
||||
subsample=0.8,
|
||||
random_state=42,
|
||||
early_stopping_rounds=10
|
||||
)
|
||||
|
||||
model.fit(
|
||||
X_train, y_train,
|
||||
eval_set=[(X_test, y_test)],
|
||||
verbose=False
|
||||
)
|
||||
print("Model training complete.")
|
||||
|
||||
y_pred = model.predict(X_test)
|
||||
y_proba = model.predict_proba(X_test)[:, 1]
|
||||
|
||||
accuracy = accuracy_score(y_test, y_pred)
|
||||
print(f"\n--- Model Evaluation ---")
|
||||
print(f"Accuracy on Test Set: {accuracy * 100:.2f}%")
|
||||
print("\nClassification Report:")
|
||||
print(classification_report(y_test, y_pred, target_names=['Hold/Sell (0)', 'Buy (1)']))
|
||||
|
||||
print("\n--- Probability Threshold Analysis ---")
|
||||
thresholds = [0.35, 0.40, 0.45, 0.50, 0.55, 0.60]
|
||||
for thresh in thresholds:
|
||||
pred_at_thresh = (y_proba >= thresh).astype(int)
|
||||
if pred_at_thresh.sum() > 0:
|
||||
precision, recall, f1, _ = precision_recall_fscore_support(
|
||||
y_test, pred_at_thresh, average='binary', zero_division=0
|
||||
)
|
||||
signal_rate = pred_at_thresh.mean() * 100
|
||||
print(f" Thresh {thresh:.2f}: Precision={precision:.2f}, Recall={recall:.2f}, "
|
||||
f"F1={f1:.2f}, Signals={signal_rate:.1f}%")
|
||||
else:
|
||||
print(f" Thresh {thresh:.2f}: No signals generated")
|
||||
|
||||
with open(config.MODEL_PATH, 'wb') as f:
|
||||
pickle.dump(model, f)
|
||||
|
||||
print(f"\nSUCCESS: Model saved to {config.MODEL_PATH}")
|
||||
|
||||
# Feature Importance
|
||||
importance = pd.DataFrame({
|
||||
'feature': X.columns,
|
||||
'importance': model.feature_importances_
|
||||
}).sort_values('importance', ascending=False)
|
||||
|
||||
print("\nTop 10 Features:")
|
||||
print(importance.head(10))
|
||||
|
||||
except Exception as e:
|
||||
print(f"AN ERROR OCCURRED: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
import os
|
||||
if __name__ == "__main__":
|
||||
train_model()
|
||||
316
uv.lock
generated
316
uv.lock
generated
@ -2,16 +2,156 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.5.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "llvmlite"
|
||||
version = "0.46.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lowkey-backtest"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "numba" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "requests" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "ta" },
|
||||
{ name = "xgboost" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "ta", specifier = ">=0.11.0" }]
|
||||
requires-dist = [
|
||||
{ name = "numba", specifier = ">=0.63.1" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "scikit-learn", specifier = ">=1.8.0" },
|
||||
{ name = "ta", specifier = ">=0.11.0" },
|
||||
{ name = "xgboost", specifier = ">=3.1.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numba"
|
||||
version = "0.63.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "llvmlite" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666, upload-time = "2025-12-10T02:57:39.002Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981, upload-time = "2025-12-10T02:57:17.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656, upload-time = "2025-12-10T02:57:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857, upload-time = "2025-12-10T02:57:20.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282, upload-time = "2025-12-10T02:57:22.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/f7/e19e6eff445bec52dde5bed1ebb162925a8e6f988164f1ae4b3475a73680/numba-0.63.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0bd4fd820ef7442dcc07da184c3f54bb41d2bdb7b35bacf3448e73d081f730dc", size = 2680954, upload-time = "2025-12-10T02:57:24.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/6c/1e222edba1e20e6b113912caa9b1665b5809433cbcb042dfd133c6f1fd38/numba-0.63.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53de693abe4be3bd4dee38e1c55f01c55ff644a6a3696a3670589e6e4c39cde2", size = 3809736, upload-time = "2025-12-10T02:57:25.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/0a/590bad11a8b3feeac30a24d01198d46bdb76ad15c70d3a530691ce3cae58/numba-0.63.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81227821a72a763c3d4ac290abbb4371d855b59fdf85d5af22a47c0e86bf8c7e", size = 3508854, upload-time = "2025-12-10T02:57:27.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/f5/3800384a24eed1e4d524669cdbc0b9b8a628800bb1e90d7bd676e5f22581/numba-0.63.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb227b07c2ac37b09432a9bda5142047a2d1055646e089d4a240a2643e508102", size = 2750228, upload-time = "2025-12-10T02:57:30.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/2f/53be2aa8a55ee2608ebe1231789cbb217f6ece7f5e1c685d2f0752e95a5b/numba-0.63.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f180883e5508940cc83de8a8bea37fc6dd20fbe4e5558d4659b8b9bef5ff4731", size = 2681153, upload-time = "2025-12-10T02:57:32.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/91/53e59c86759a0648282368d42ba732c29524a745fd555ed1fb1df83febbe/numba-0.63.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0938764afa82a47c0e895637a6c55547a42c9e1d35cac42285b1fa60a8b02bb", size = 3778718, upload-time = "2025-12-10T02:57:33.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/0c/2be19eba50b0b7636f6d1f69dfb2825530537708a234ba1ff34afc640138/numba-0.63.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f90a929fa5094e062d4e0368ede1f4497d5e40f800e80aa5222c4734236a2894", size = 3478712, upload-time = "2025-12-10T02:57:35.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/5f/4d0c9e756732577a52211f31da13a3d943d185f7fb90723f56d79c696caa/numba-0.63.1-cp314-cp314-win_amd64.whl", hash = "sha256:8d6d5ce85f572ed4e1a135dbb8c0114538f9dd0e3657eeb0bb64ab204cbe2a8f", size = 2752161, upload-time = "2025-12-10T02:57:37.12Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
@ -76,6 +216,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-nccl-cu12"
|
||||
version = "2.29.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/b2/e4dc7b33020645746710040cb2a6ac0de8332687d3ce902156dd3d7c351a/nvidia_nccl_cu12-2.29.2-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:0712e55c067965c6093cc793a9bbcc5f37b5b47248e9ebf8ae3af06867757587", size = 289707761, upload-time = "2026-01-07T00:21:30.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/2d/609d0392d992259c6dc39881688a7fc13b1397a668bc360fbd68d1396f85/nvidia_nccl_cu12-2.29.2-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:3a9a0bf4142126e0d0ed99ec202579bef8d007601f9fab75af60b10324666b12", size = 289762233, upload-time = "2026-01-07T00:21:56.124Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.3.1"
|
||||
@ -122,6 +271,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
@ -131,6 +289,126 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "numpy" },
|
||||
{ name = "scipy" },
|
||||
{ name = "threadpoolctl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.16.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@ -150,6 +428,15 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/9a/37d92a6b470dc9088612c2399a68f1a9ac22872d4e1eff416818e22ab11b/ta-0.11.0.tar.gz", hash = "sha256:de86af43418420bd6b088a2ea9b95483071bf453c522a8441bc2f12bcf8493fd", size = 25308, upload-time = "2023-11-02T13:53:35.434Z" }
|
||||
|
||||
[[package]]
|
||||
name = "threadpoolctl"
|
||||
version = "3.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
@ -158,3 +445,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be76
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xgboost"
|
||||
version = "3.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/64/42310363ecd814de5930981672d20da3d35271721ad2d2b4970b4092825b/xgboost-3.1.2.tar.gz", hash = "sha256:0f94496db277f5c227755e1f3ec775c59bafae38f58c94aa97c5198027c93df5", size = 1237438, upload-time = "2025-11-20T18:33:29.614Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/35/1e/efdd603db8cb37422b01d925f9cce1baaac46508661c73f6aafd5b9d7c51/xgboost-3.1.2-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b44f6ee43a28b998e289ab05285bd65a65d7999c78cf60b215e523d23dc94c5d", size = 2377854, upload-time = "2025-11-20T18:06:21.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/c6/ed928cb106f56ab73b3f4edb5287c1352251eb9225b5932d3dd5e2803f60/xgboost-3.1.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:09690f7430504fcd3b3e62bf826bb1282bb49873b68b07120d2696ab5638df41", size = 2211078, upload-time = "2025-11-20T18:06:47.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/2f/5418f4b1deaf0886caf81c5e056299228ac2fc09b965a2dfe5e4496331c8/xgboost-3.1.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f9b83f39340e5852bbf3e918318e7feb7a2a700ac7e8603f9bc3a06787f0d86b", size = 4953319, upload-time = "2025-11-20T18:28:29.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/ab/c60fcc137fa685533bb31e721de3ecc88959d393830d59d0204c5cbd2c85/xgboost-3.1.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:24879ac75c0ee21acae0101f791bc43303f072a86d70fdfc89dae10a0008767f", size = 115885060, upload-time = "2025-11-20T18:32:00.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/7d/41847e45ff075f3636c95d1000e0b75189aed4f1ae18c36812575bb42b4b/xgboost-3.1.2-py3-none-win_amd64.whl", hash = "sha256:e627c50003269b4562aa611ed348dff8cb770e11a9f784b3888a43139a0f5073", size = 71979118, upload-time = "2025-11-20T18:27:55.23Z" },
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user