From 58a1dc230894d637ea89a125cb68e8d31fdc2c76 Mon Sep 17 00:00:00 2001 From: Pratik Bhadane Date: Tue, 24 Mar 2026 12:49:17 +0530 Subject: [PATCH] chore: remove .coverage file and update .gitignore to exclude coverage files - Deleted the .coverage file to clean up the repository. - Updated .gitignore to ensure .coverage and .coverage.* files are ignored in future commits. - Revised README.md to enhance clarity and conciseness regarding the library's capabilities and performance. - Improved documentation for the MCP server, emphasizing its expanded functionality and integration with clients. --- .coverage | Bin 53248 -> 0 bytes .gitignore | 4 +- README.md | 1079 ++----------------- TA_LIB_COMPATIBILITY.md | 262 +++++ docs/adjacent_tooling.rst | 3 +- docs/agentic.md | 2 +- docs/index.rst | 2 +- docs/mcp.md | 174 ++- python/ferro_ta/mcp/__init__.py | 1686 +++++++++++++++++++++++------- tests/unit/test_tools_and_api.py | 197 +++- uv.lock | 363 ++++--- 11 files changed, 2145 insertions(+), 1627 deletions(-) delete mode 100644 .coverage create mode 100644 TA_LIB_COMPATIBILITY.md diff --git a/.coverage b/.coverage deleted file mode 100644 index 62a786cc5743c2de099dda340e6eb7503fce9571..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI5Yj70Tm4I(g&rDCxtA&IRLXT-ZM?x=HdqWJeG!hSkO#;R^57(}T)~F>7Xr{+A zEs#Rl^w=cM7O9Y;)+Ang9g0+v{grI3<7_GfGnK8{WH;UgZ-ojKvql@&BJ6k(Hg-@f zB<;C(p5l?pbX*neI#Z)L{kZ4cbH01-?Ku+NJoMn!u&&BOT0ElY@?E5aa2yH9G9g5S z9}oOwUjl4cvpXQUg6-RFillSz-*}|Y5V!qvM0(V-Lh5i2yWep=@BXCtuq!OIi=A)+ zUq}E6AOR%su_2Ir&@EI{R&q~1t}6qhsvcKDYCQevS-)dr-_DKl&c6Pw8|Cydxy32N ztD{5iljGVxIi|+tq420GhoghxkfMj9!?Hf2W?PRX)Ioa9f@5&iq76HlKHUn22SH0$ zhrvWF9*!vS33-n?(asuVdehZ$J$(R-sNvx#Z6UX0)LZ4aI;6(cXh=<@y=Vy!wmMs% zI^Yy4%F4K>RTh&H55e2DC>l7B4`s^`7z%0eLF)ZLT#1H8)I__i(dTeenmic%W4QCcP z9nrMbSTy|Dn3@~>b~&%v&ccXl?J^AlGN8$2uehg-BaoHqc;c~9`05l?#&nJC2VwAo ztKq|aaEU{x@cFo>A7hC^@dO9LdLp+WE=ro*;oGDRJ%9VWp|fOhlF(T+Ix1^JY|2n= zIXyU#&4qKOD+@!3(}^Z)nTFu$pEcX4v*3d~FscnK&NY@X2^A&?tOeO-Lu1hpohk_$ zLrB}J#+6~UGjle*Ed03>2~uaZXs_7lyItz!QXsfGx9Ln)+J%a$D(*>b6;YMMSUkIs{GmlMtC3$NvvVYK2EyqO>=k`gw+koJF1R|g;YbQrp`yH; zOQvT8U6<&OZBY>PNPY_3a3T2P-H$2yhB>Oyce{j?t}u`rjv z%rp8CSoG-+zsO~JB=7PA>O{w0WfWGEm=cb|l!Y}oeFczd!$AcaD+Ah?&hnS;vh<|a zp1i+l1Nr?dduY|ln#1(ot|by-xUAC4R$^`TsO%q65_A5oItv-RYndSllP&Pg*5A=vTRR18H>G zR+%qI&Q@CBbJ8qnp0jWQt^Y03B#|z| z2fmO15UX~0g;Jxnsv3J~ix96(opFCktv-_g^J8s3@;QEd0xNDb7cK)~XIp^n{ zHR5aHbK>X48pp33&pIA*lnQ5rL&7$}V}I5DRr~#R(e|&lgSJgz7GFpJ2_S(xm%#cq zD@nS#Mzo096(5U6)p*xFExxB~NJ;1&x{{Nz34KJ1vfUsY4#nXXyr7aus5;!L*1eJ^ z2a2{ariVuppdDxh7i>kVQ#Xf3wS8cruLUd!MO%nzaeYV|4QpVc*AFK6qD>^==6Quh z(ccUP#G(zv)r1m>!A-BjG>#@yaSXz(dIOpg9|RL?8+o$7XfO7LzW};{2I#){ff$0D z3gOOLI+Sbc&GbH)7zN$+bf8b*X743wG*0-g$@ z!ZRl5g4VOdln0^sL<}CJ0rR;8^oyU*p^>rZ9_B!|WM(D~D0*lFx?k^w?oF&}x*iV? zz!j3MYCRrP43trL<_uj9dfcY;_iCeKY!v%kpl>FTU|CM+;SjiRpA$@!6+N5b2{s`$ z2#>9aCg9l}5jvhC7%F}|;qeEsz|%`aufvo9`e2PwB?boi1u#(jl^$pU4tQn?o7{G= zQT(Mg0$*GcJG`^ z5xmY??+M2_$4>8rqs;T3=SA_n?Ps3dVy&yr`H1}iyVK)$zV7%t+kN)`^8TfJ%J#)O z_iI0Th6IoR5+G!QHR&pT0bgAIx82JhDBec)8nCj@nzR+KUQqwH_FI!e@fHf}|CWF? z$ro=TU;q2pTa#k(1{T%-&F11LtpA(V@%xMSB3=JCnj46M`oFi;F?=5xOOFEKR}v-Q7Za#dSY|9j0b$kqQIbNadZ-)%OL^7X%Kvo%>({A|9t{&$)i z&wTwanlq5E{~hM8^o#0$!Q8wksQ>LIE#&Keo4Gg^)&Ev=nU$;mc~g@sTmM^NC~d|6 zCZ?eN$LIfH4TJ=c01`j~NB{{S0VIF~kN^@u0!ZLvO@QZk;-P>4=cMZd-uOZSNB{{S z0VIF~kN^@u0!RP}AOR$R1U`lYc-|wj`~MvsM0!a&E=@?=r8e(n?+Ncy-a&7_x61Rr z=T*;(o}}lKo=W$x+%LJGafjR;?k3mwU0-#5!nxg9E6#|M;z{u<;;7g!);g{^E;&v* ze&9Id7;`)dX7PmtkN^@u0!RP}eAEeawOKiFSE~i$Fn+xnK7H>I@>@dwN+LwKmyk2B z_|^qe3z^6MTzajxd?hHfyDcMIE(&Dw_W~jFpCXgu(pEUUQpg_W$aoJ)mH1oW==Mfx zo$Tf>_tIW0jug4j^Vrv~bJNr81i3QbDfppb$;#uF)@In)S;J0jcg;{mvU6ej=RUUe z!JG3-2x)@tGw)P*nxJX*Qr7e!aWpl;?pBwDJaVLQOEXE8fmfuPGyLR=mAsJIzhbR# zfM%QgptXJ0BEyrCd%)Vc<0t>#GsDBytmhvWxR!cov!j_>4s?*qAmGJMU$oJ(rSF2kWX8PAv!j1>yqL8uClbHElzooi(Ti)%po7S z{u_Zj!Oc^!Bf(du{R_1B^QY^4ps=Be88iZL=%$xrSuHf_bXv|F8GM2iMAldX$M0{U zs=H5-5<+(M@=@41I^&Ch@ficY7+&F4RYR+R<*WlZ`BLhw-d%*Oxe<+;zSfFP6Y4hV826JK*K za{$-+bUIN7$!UogQyj#WJqEzQ6if$@^E6eBg*9}}qz;t%DuH*v$9Nwrzw}MWAY=9! z_?&#S78+h_)0fkZN9?r4nLOC%X~vFQ$hJ%x)6T&B3lg~kf7k0iK&a=?8v(d5SFZy* zV~Qu;y(FyyNl#q@cbT8GE!`z4+PeB1_6l&zTf6e^WgvG~Gxs3dKP%6i@-OUv_muw^ z7xr&Edg|={cc*%}7S7O4eShK2?kUbf{}=RJTza)x zb2S3Z&%gq6R`}YS5GbXbNGTi(tOrhi0SL(3QxFZXyb91$*WTb+kmU7+836opH)J%N z{-#7G0okNjvjqC+wo)Hn*#$j+12!%+5OS}BTseG3yRU?H;H`)A#Q34Dm<M0FA=(`7=I0$#DJkWK{wccBo8~emt5@yjkOjF>DY2aq*({+))eF!2S`(8^&U98 zX$jancC^uUeH`>A;UiWJF^AlQw9A+08{N?6{x;fYVAf*LEaQ6s@fB#x*Fu+cDdn16 z&}wTXZAI?h4EOwh4cftfZNKVt6G=6}xPsN+zHF79(5%0NHQWBcj+8)*Hp#e$Tm`d` z;oo|CS&axSx+Q24h$Itqye4M>_vHU_sbBC$#U~taI$F*QkYB?6@BuP+fJ?!&`5ol& zk59sKaO^+lQvBPDf+1b_V(fq3eS2D1Er8yjm+JlU$Wr1qK&^L<1OI2rc-b+3*e|VnE^d94k zx%sK-g;yr8MP~Oj*g$!69Vi>WUMHune#vr~<=)Td@A}#F@F{*dF&^gqR%oHNFA}jpb+W`NSJW!6(F2p$eEoR#rhJ` zS7Hqihq1u!|M#9H(yVk$`akKAq)Cr>-;lOS8>Ll}-Fsd-C%r2Di}VxeyV60a-n$_E zyHq9Zm3rX!1@B3%(uj9PQly8Zi_!(@i1fJkE$LZlspOH?c+Yy@k-m3h$GDFKkN^@u z0!RP}AOR$R1dsp{KmthMLnJ_}e6F>P3HM4SgjOaRTbNksXQHN=iKR_UxEh)8H!#s$ z&&0AiCdy `ferro-ta` is a Rust-backed Python technical analysis library with a TA-Lib-compatible API for NumPy-first workloads. -> -> Performance varies by indicator, array layout, warmup, build flags, and machine. Public checked-in runs show `ferro-ta` is often faster on selected indicators, while TA-Lib still wins or ties on others. The benchmark workflow, artifacts, and caveats are published in [`benchmarks/README.md`](benchmarks/README.md). - ---- +> `ferro-ta` is a Rust-backed Python technical analysis library for NumPy-first workloads. It keeps TA-Lib-style ergonomics, ships pre-built wheels on supported targets, and publishes reproducible benchmark artifacts instead of blanket speed claims. ## πŸš€ What ferro-ta is @@ -27,68 +23,34 @@ | **Primary product** | C-backed Python TA library | Rust-backed Python TA library | | **API shape** | `talib.SMA(close, 20)` | `ferro_ta.SMA(close, 20)` | | **Installation** | Often requires native/system setup | Pre-built wheels on supported targets | -| **Performance claim** | Established baseline | Often faster on selected indicators; see reproducible benchmarks | | **Scope** | Technical indicators | Technical indicators first; other tooling is optional and secondary | ---- - ## ⚑ Benchmark evidence The latest checked-in TA-Lib comparison artifact uses contiguous `float64` -arrays at 10k and 100k bars on an `Apple M3 Max`, `CPython 3.13.5`, `Rust -1.91.1`, default release profile (`lto = true`, `codegen-units = 1`), with no -extra `RUSTFLAGS`: +arrays at 10k and 100k bars on an `Apple M3 Max`, `CPython 3.13.5`, and `Rust +1.91.1`. -- `ferro-ta` is ahead outside the tie band on 6 of 12 indicators at 10k bars and 6 of 12 at 100k bars. -- At 100k bars, the stronger public wins are `SMA` (`2.28x`), `BBANDS` (`2.34x`), `MACD` (`1.38x`), `MFI` (`3.04x`), and `WMA` (`2.39x`). -- TA-Lib still wins on `STOCH` and `ADX` in the current checked-in 10k and 100k runs, and still wins or ties on `EMA`, `RSI`, `ATR`, and `OBV`. -- The published JSON now includes per-run samples, variance stats, and Python-tracked allocation snapshots, not just a single median. +- `ferro-ta` is ahead outside the tie band on 6 of 12 indicators at both 10k and 100k bars. +- Strong public wins in the latest 100k-bar artifact include `SMA` (`2.28x`), `BBANDS` (`2.34x`), `MFI` (`3.04x`), and `WMA` (`2.39x`). +- TA-Lib still wins or ties on parts of the suite, including `STOCH`, `ADX`, and some current `EMA` / `RSI` / `ATR` runs. -The point of the benchmark suite is not to claim universal wins. It is to let readers reproduce the results, inspect the raw artifact, and see where each library is stronger. +See the benchmark methodology and artifacts: -### πŸ† Reproduce the public comparison - -- Methodology, artifact format, and result tables: [`benchmarks/README.md`](benchmarks/README.md) -- Latest checked-in artifact bundle: [`benchmarks/artifacts/latest/`](benchmarks/artifacts/latest/) -- TA-Lib head-to-head script: `benchmarks/bench_vs_talib.py` -- Cross-library suite: `benchmarks/test_speed.py` - -```bash -# Reproduce the TA-Lib comparison yourself -pip install ferro-ta ta-lib -python benchmarks/bench_vs_talib.py --sizes 10000 100000 --json benchmark_vs_talib.json - -# or with uv -uv run python benchmarks/bench_vs_talib.py --sizes 10000 100000 --json benchmark_vs_talib.json -uv run python benchmarks/check_vs_talib_regression.py --input benchmark_vs_talib.json - -# full cross-library speed suite (100k bars) -uv run pytest benchmarks/test_speed.py --benchmark-only --benchmark-json=benchmarks/results.json -v - -# generate the comparison table from results.json -uv run python benchmarks/benchmark_table.py -``` - ---- +- [benchmarks/README.md](benchmarks/README.md) +- [benchmarks/artifacts/latest/](benchmarks/artifacts/latest/) +- [docs/benchmarks.rst](docs/benchmarks.rst) ## 🎯 Core capabilities -- **TA-Lib-style API** for 160+ indicators, including the common `SMA`, `EMA`, `RSI`, `MACD`, and `BBANDS` entry points. -- **Pre-built wheels** for supported Python and OS targets, so the common install path stays `pip install ferro-ta`. -- **NumPy-first execution** with pandas and polars adapters, plus explicit guidance on contiguous-array fast paths. -- **Batch and streaming APIs** for multi-series and bar-by-bar workloads. -- **Compatibility and support docs** covering parity status, supported wheels, supported Python versions, and experimental modules. -- **Type stubs, error model, API discovery, and examples** for day-to-day library use. +- 160+ indicators with a TA-Lib-style public API. +- Batch and streaming APIs for multi-series and bar-by-bar workloads. +- NumPy-first execution with pandas and polars adapters. +- Pre-built wheels on the supported Python and OS matrix. +- Type stubs, error codes, examples, and reproducible benchmarks. -## πŸ§ͺ Adjacent and experimental modules - -These ship in the repo, but they are not the primary product story: - -- **Adjacent analytics:** derivatives helpers, backtesting utilities, portfolio and cross-asset analysis, feature generation, and charting. -- **Experimental or optional tooling:** GPU backend, plugin system, WASM package, agent/tool wrappers, and the MCP server. -- **Docs posture:** these modules are now called out separately in the docs nav and support matrix so the core TA library remains the main narrative. - ---- +Adjacent and experimental surfaces such as derivatives analytics, MCP, GPU, +plugins, and WASM remain opt-in and secondary to the core TA library story. ## πŸ“¦ Installation @@ -99,17 +61,15 @@ pip install ferro-ta Optional extras: ```bash -pip install "ferro-ta[pandas]" # transparent pandas.Series support -pip install "ferro-ta[polars]" # transparent polars.Series support -pip install "ferro-ta[gpu]" # GPU-accelerated SMA/EMA/RSI via PyTorch (CUDA/MPS) -pip install "ferro-ta[options]" # Derivatives analytics helpers -pip install "ferro-ta[mcp]" # MCP server for Cursor/Claude agent integration -pip install "ferro-ta[all]" # all optional extras (excluding gpu) +pip install "ferro-ta[pandas]" # pandas.Series support +pip install "ferro-ta[polars]" # polars.Series support +pip install "ferro-ta[gpu]" # PyTorch-backed GPU helpers +pip install "ferro-ta[options]" # derivatives analytics helpers +pip install "ferro-ta[mcp]" # MCP server for agent/tool clients +pip install "ferro-ta[all]" # most optional extras (excluding gpu) ``` ---- - -## ⚑ Quick Start +## ⚑ Quick start ```python import numpy as np @@ -118,980 +78,61 @@ from ferro_ta import SMA, EMA, RSI, MACD, BBANDS close = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.15, 43.61, 44.33, 44.83, 45.10, 45.15, 43.61, 44.33]) -# Simple Moving Average sma = SMA(close, timeperiod=5) - -# Exponential Moving Average ema = EMA(close, timeperiod=5) - -# Relative Strength Index rsi = RSI(close, timeperiod=14) - -# MACD (returns macd_line, signal_line, histogram) macd_line, signal, histogram = MACD(close, fastperiod=12, slowperiod=26, signalperiod=9) - -# Bollinger Bands (returns upper, middle, lower) upper, middle, lower = BBANDS(close, timeperiod=5, nbdevup=2.0, nbdevdn=2.0) ``` -## Ξ” Derivatives Analytics +## πŸ“Š TA-Lib compatibility -```python -from ferro_ta.analysis.options import greeks, implied_volatility, option_price -from ferro_ta.analysis.futures import basis, curve_summary +- `ferro-ta` implements 100% of TA-Lib's function set (`162+` indicators). +- Most functions are marked `Exact` or `Close`; the remaining notable non-exact categories are the Hilbert cycle indicators plus `MAMA`, `SAR`, and `SAREXT`. +- The full parity matrix and coverage summary now live in [TA_LIB_COMPATIBILITY.md](TA_LIB_COMPATIBILITY.md). -price = option_price(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call", model="bsm") -iv = implied_volatility(price, 100.0, 100.0, 0.05, 1.0, option_type="call", model="bsm") -g = greeks(100.0, 100.0, 0.05, 1.0, 0.20, option_type="call", model="bsm") +Migration and compatibility references: -front_basis = basis(100.0, 103.0) -curve = curve_summary(100.0, [0.1, 0.5, 1.0], [101.0, 102.0, 104.0]) -``` +- [docs/migration_talib.rst](docs/migration_talib.rst) +- [docs/compatibility/talib.md](docs/compatibility/talib.md) +- [docs/support_matrix.rst](docs/support_matrix.rst) -The derivatives layer is analytics-only. It includes: +## πŸ—ΊοΈ Docs map -- options pricing under Black-Scholes-Merton and Black-76 -- delta, gamma, vega, theta, and rho -- implied volatility inversion and smile metrics -- futures basis, carry, curve, and continuous-roll helpers -- typed strategy schemas and multi-leg payoff/Greeks aggregation +Core guides: -**Migrating from TA-Lib?** Just swap the import β€” the API is identical: +- [docs/quickstart.rst](docs/quickstart.rst) +- [docs/migration_talib.rst](docs/migration_talib.rst) +- [docs/support_matrix.rst](docs/support_matrix.rst) +- [PLATFORMS.md](PLATFORMS.md) -```python -# Before (TA-Lib) -import talib -sma = talib.SMA(close, timeperiod=20) -rsi = talib.RSI(close, timeperiod=14) +Evidence and APIs: -# After (ferro-ta β€” same call signature) -import ferro_ta -sma = ferro_ta.SMA(close, timeperiod=20) -rsi = ferro_ta.RSI(close, timeperiod=14) -``` +- [benchmarks/README.md](benchmarks/README.md) +- [docs/batch.rst](docs/batch.rst) +- [docs/streaming.rst](docs/streaming.rst) +- [docs/derivatives.rst](docs/derivatives.rst) ---- +Optional and experimental surfaces: -## πŸ› οΈ Development Setup +- [docs/mcp.md](docs/mcp.md) +- [docs/adjacent_tooling.rst](docs/adjacent_tooling.rst) +- [docs/plugins.rst](docs/plugins.rst) +- [wasm/README.md](wasm/README.md) -Requires Rust and **Python 3.10–3.13** (PyO3 supports up to 3.13; for Python 3.14+ use a compatible interpreter or set `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` to attempt a build). +Project and release docs: + +- [CONTRIBUTING.md](CONTRIBUTING.md) +- [CHANGELOG.md](CHANGELOG.md) +- [VERSIONING.md](VERSIONING.md) +- [RELEASE.md](RELEASE.md) + +## πŸ› οΈ Development ```bash -# Create a virtual environment -python -m venv .venv -source .venv/bin/activate # Windows: .venv\Scripts\activate - -# Install build tool and dependencies -pip install maturin numpy pytest pandas - -# Compile and install in editable mode -maturin develop --release - -# Run tests -pytest tests/unit/ tests/integration/ -# or: uv run pytest tests/unit/ tests/integration/ - -# Run TA-Lib comparison tests (requires ta-lib package) -pip install "ferro-ta[comparison]" # or: pip install ta-lib -pytest tests/integration/test_vs_talib.py -v - -# Build Sphinx documentation (requires sphinx + sphinx-rtd-theme) -pip install "ferro-ta[docs]" -cd docs && make html -# Output: docs/_build/html/index.html +uv sync --extra dev +uv run pytest tests/unit tests/integration +uv run maturin build --release --out dist ``` ---- - -## πŸ“Š Full TA-Lib Compatibility - -ferro-ta covers **100% of TA-Lib's function set** (162+ indicators). The table below shows implementation status and numerical accuracy vs TA-Lib. - -**Legend** - -| Symbol | Meaning | -|--------|---------| -| βœ… Exact | Values match TA-Lib to floating-point precision | -| βœ… Close | Values match after a short convergence window (EMA-seed difference) | -| ⚠️ Corr | Strong correlation (> 0.95) but not numerically identical (Wilder smoothing seed or algorithm variant) | -| ⚠️ Shape | Same output shape / NaN structure; values differ due to algorithm variant | -| ❌ | Not yet implemented | - -### Overlap Studies - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `BBANDS` | βœ… | βœ… Exact | Bollinger Bands | -| `DEMA` | βœ… | βœ… Close | Double EMA; converges after ~20 bars | -| `EMA` | βœ… | βœ… Close | Exponential Moving Average; converges after ~20 bars | -| `KAMA` | βœ… | βœ… Exact | Kaufman Adaptive MA (values match after seed bar) | -| `MA` | βœ… | βœ… Exact | Moving average (generic, type-selectable) | -| `MAMA` | βœ… | ⚠️ Corr | MESA Adaptive MA | -| `MAVP` | βœ… | βœ… Exact | MA with variable period | -| `MIDPOINT` | βœ… | βœ… Exact | Midpoint over period | -| `MIDPRICE` | βœ… | βœ… Exact | Midpoint price over period | -| `SAR` | βœ… | ⚠️ Shape | Parabolic SAR (same shape; reversal history diverges) | -| `SAREXT` | βœ… | ⚠️ Shape | Parabolic SAR Extended | -| `SMA` | βœ… | βœ… Exact | Simple Moving Average | -| `T3` | βœ… | βœ… Close | Triple Exponential MA (T3); converges after ~50 bars | -| `TEMA` | βœ… | βœ… Close | Triple EMA; converges after ~20 bars | -| `TRIMA` | βœ… | βœ… Exact | Triangular Moving Average | -| `WMA` | βœ… | βœ… Exact | Weighted Moving Average | - -### Momentum Indicators - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `ADX` | βœ… | βœ… Close | Avg Directional Movement Index (TA-Lib Wilder sum-seeding) | -| `ADXR` | βœ… | βœ… Close | ADX Rating (inherits ADX; TA-Lib seeding) | -| `APO` | βœ… | βœ… Close | Absolute Price Oscillator (EMA-based) | -| `AROON` | βœ… | βœ… Exact | Aroon Up/Down | -| `AROONOSC` | βœ… | βœ… Exact | Aroon Oscillator | -| `BOP` | βœ… | βœ… Exact | Balance Of Power | -| `CCI` | βœ… | βœ… Exact | Commodity Channel Index (TA-Lib–compatible MAD formula) | -| `CMO` | βœ… | βœ… Close | Chande Momentum Oscillator (rolling window, TA-Lib–compatible) | -| `DX` | βœ… | βœ… Close | Directional Movement Index (TA-Lib Wilder sum-seeding) | -| `MACD` | βœ… | βœ… Close | MACD (EMA-based; converges after ~30 bars) | -| `MACDEXT` | βœ… | βœ… Close | MACD with controllable MA type (EMA-based; converges) | -| `MACDFIX` | βœ… | βœ… Close | MACD Fixed 12/26 (EMA-based; converges) | -| `MFI` | βœ… | βœ… Exact | Money Flow Index | -| `MINUS_DI` | βœ… | βœ… Close | Minus Directional Indicator (TA-Lib Wilder sum-seeding) | -| `MINUS_DM` | βœ… | βœ… Close | Minus Directional Movement (TA-Lib Wilder sum-seeding) | -| `MOM` | βœ… | βœ… Exact | Momentum | -| `PLUS_DI` | βœ… | βœ… Close | Plus Directional Indicator (TA-Lib Wilder sum-seeding) | -| `PLUS_DM` | βœ… | βœ… Close | Plus Directional Movement (TA-Lib Wilder sum-seeding) | -| `PPO` | βœ… | βœ… Close | Percentage Price Oscillator (EMA-based) | -| `ROC` | βœ… | βœ… Exact | Rate of Change | -| `ROCP` | βœ… | βœ… Exact | Rate of Change Percentage | -| `ROCR` | βœ… | βœ… Exact | Rate of Change Ratio | -| `ROCR100` | βœ… | βœ… Exact | Rate of Change Ratio Γ— 100 | -| `RSI` | βœ… | βœ… Close | Relative Strength Index (TA-Lib Wilder seeding; converges after ~1 seed bar) | -| `STOCH` | βœ… | βœ… Close | Stochastic (TA-Lib–compatible SMA smoothing for slowk and slowd) | -| `STOCHF` | βœ… | βœ… Exact | Stochastic Fast (%K exact; %D NaN offset Β±2) | -| `STOCHRSI` | βœ… | βœ… Close | Stochastic RSI (TA-Lib–compatible; SMA fastd, Wilder-seeded RSI) | -| `TRIX` | βœ… | βœ… Close | 1-day ROC of Triple EMA (EMA-based; converges) | -| `ULTOSC` | βœ… | βœ… Exact | Ultimate Oscillator | -| `WILLR` | βœ… | βœ… Exact | Williams' %R | - -### Volume Indicators - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `AD` | βœ… | βœ… Exact | Chaikin A/D Line | -| `ADOSC` | βœ… | βœ… Exact | Chaikin A/D Oscillator | -| `OBV` | βœ… | βœ… Exact | On Balance Volume (increments identical; constant offset at bar 0) | - -### Volatility Indicators - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `ATR` | βœ… | βœ… Close | Average True Range (TA-Lib Wilder seeding; matches from bar timeperiod) | -| `NATR` | βœ… | βœ… Close | Normalized ATR (TA-Lib Wilder seeding) | -| `TRANGE` | βœ… | βœ… Exact | True Range (bar 0 differs; all others identical) | - -### Cycle Indicators - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `HT_DCPERIOD` | βœ… | ⚠️ Shape | Hilbert Transform Dominant Cycle Period (Ehlers algorithm) | -| `HT_DCPHASE` | βœ… | ⚠️ Shape | Hilbert Transform Dominant Cycle Phase | -| `HT_PHASOR` | βœ… | ⚠️ Shape | Hilbert Transform Phasor Components (inphase, quadrature) | -| `HT_SINE` | βœ… | ⚠️ Shape | Hilbert Transform SineWave (sine, leadsine) | -| `HT_TRENDLINE` | βœ… | ⚠️ Shape | Hilbert Transform Instantaneous Trendline | -| `HT_TRENDMODE` | βœ… | ⚠️ Shape | Hilbert Transform Trend vs Cycle Mode (1=trend, 0=cycle) | - -### Price Transformations - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `AVGPRICE` | βœ… | βœ… Exact | Average Price | -| `MEDPRICE` | βœ… | βœ… Exact | Median Price | -| `TYPPRICE` | βœ… | βœ… Exact | Typical Price | -| `WCLPRICE` | βœ… | βœ… Exact | Weighted Close Price | - -### Statistic Functions - -| TA-Lib Function | ferro-ta | Accuracy | Notes | -|-----------------|---------|----------|-------| -| `BETA` | βœ… | βœ… Close | Beta coefficient (returns-based regression matching TA-Lib) | -| `CORREL` | βœ… | βœ… Exact | Pearson Correlation Coefficient | -| `LINEARREG` | βœ… | βœ… Exact | Linear Regression | -| `LINEARREG_ANGLE` | βœ… | βœ… Exact | Linear Regression Angle | -| `LINEARREG_INTERCEPT` | βœ… | βœ… Exact | Linear Regression Intercept | -| `LINEARREG_SLOPE` | βœ… | βœ… Exact | Linear Regression Slope | -| `STDDEV` | βœ… | βœ… Exact | Standard Deviation | -| `TSF` | βœ… | βœ… Exact | Time Series Forecast | -| `VAR` | βœ… | βœ… Exact | Variance | - -### Pattern Recognition - -ferro-ta implements all 61 candlestick patterns. All return the same `{-100, 0, 100}` -convention as TA-Lib. Pattern thresholds may differ slightly from the full TA-Lib -implementation. - -| TA-Lib Function | ferro-ta | Notes | -|-----------------|---------|-------| -| `CDL2CROWS` | βœ… | Two Crows | -| `CDL3BLACKCROWS` | βœ… | Three Black Crows | -| `CDL3INSIDE` | βœ… | Three Inside Up/Down | -| `CDL3LINESTRIKE` | βœ… | Three-Line Strike | -| `CDL3OUTSIDE` | βœ… | Three Outside Up/Down | -| `CDL3STARSINSOUTH` | βœ… | Three Stars In The South | -| `CDL3WHITESOLDIERS` | βœ… | Three Advancing White Soldiers | -| `CDLABANDONEDBABY` | βœ… | Abandoned Baby | -| `CDLADVANCEBLOCK` | βœ… | Advance Block | -| `CDLBELTHOLD` | βœ… | Belt-hold | -| `CDLBREAKAWAY` | βœ… | Breakaway | -| `CDLCLOSINGMARUBOZU` | βœ… | Closing Marubozu | -| `CDLCONCEALBABYSWALL` | βœ… | Concealing Baby Swallow | -| `CDLCOUNTERATTACK` | βœ… | Counterattack | -| `CDLDARKCLOUDCOVER` | βœ… | Dark Cloud Cover | -| `CDLDOJI` | βœ… | Doji | -| `CDLDOJISTAR` | βœ… | Doji Star | -| `CDLDRAGONFLYDOJI` | βœ… | Dragonfly Doji | -| `CDLENGULFING` | βœ… | Engulfing Pattern | -| `CDLEVENINGDOJISTAR` | βœ… | Evening Doji Star | -| `CDLEVENINGSTAR` | βœ… | Evening Star | -| `CDLGAPSIDESIDEWHITE` | βœ… | Up/Down-gap side-by-side white lines | -| `CDLGRAVESTONEDOJI` | βœ… | Gravestone Doji | -| `CDLHAMMER` | βœ… | Hammer | -| `CDLHANGINGMAN` | βœ… | Hanging Man | -| `CDLHARAMI` | βœ… | Harami Pattern | -| `CDLHARAMICROSS` | βœ… | Harami Cross Pattern | -| `CDLHIGHWAVE` | βœ… | High-Wave Candle | -| `CDLHIKKAKE` | βœ… | Hikkake Pattern | -| `CDLHIKKAKEMOD` | βœ… | Modified Hikkake Pattern | -| `CDLHOMINGPIGEON` | βœ… | Homing Pigeon | -| `CDLIDENTICAL3CROWS` | βœ… | Identical Three Crows | -| `CDLINNECK` | βœ… | In-Neck Pattern | -| `CDLINVERTEDHAMMER` | βœ… | Inverted Hammer | -| `CDLKICKING` | βœ… | Kicking | -| `CDLKICKINGBYLENGTH` | βœ… | Kicking by the longer Marubozu | -| `CDLLADDERBOTTOM` | βœ… | Ladder Bottom | -| `CDLLONGLEGGEDDOJI` | βœ… | Long Legged Doji | -| `CDLLONGLINE` | βœ… | Long Line Candle | -| `CDLMARUBOZU` | βœ… | Marubozu | -| `CDLMATCHINGLOW` | βœ… | Matching Low | -| `CDLMATHOLD` | βœ… | Mat Hold | -| `CDLMORNINGDOJISTAR` | βœ… | Morning Doji Star | -| `CDLMORNINGSTAR` | βœ… | Morning Star | -| `CDLONNECK` | βœ… | On-Neck Pattern | -| `CDLPIERCING` | βœ… | Piercing Pattern | -| `CDLRICKSHAWMAN` | βœ… | Rickshaw Man | -| `CDLRISEFALL3METHODS` | βœ… | Rising/Falling Three Methods | -| `CDLSEPARATINGLINES` | βœ… | Separating Lines | -| `CDLSHOOTINGSTAR` | βœ… | Shooting Star | -| `CDLSHORTLINE` | βœ… | Short Line Candle | -| `CDLSPINNINGTOP` | βœ… | Spinning Top | -| `CDLSTALLEDPATTERN` | βœ… | Stalled Pattern | -| `CDLSTICKSANDWICH` | βœ… | Stick Sandwich | -| `CDLTAKURI` | βœ… | Takuri (Dragonfly Doji with very long lower shadow) | -| `CDLTASUKIGAP` | βœ… | Tasuki Gap | -| `CDLTHRUSTING` | βœ… | Thrusting Pattern | -| `CDLTRISTAR` | βœ… | Tristar Pattern | -| `CDLUNIQUE3RIVER` | βœ… | Unique 3 River | -| `CDLUPSIDEGAP2CROWS` | βœ… | Upside Gap Two Crows | -| `CDLXSIDEGAP3METHODS` | βœ… | Upside/Downside Gap Three Methods | - -### Math Operators / Math Transforms - -ferro-ta provides TA-Lib–compatible wrappers for all arithmetic and math-transform functions. -Rolling functions (SUM, MAX, MIN) produce NaN for the first `timeperiod - 1` bars. - -| TA-Lib Function | ferro-ta | Notes | -|-----------------|---------|-------| -| `ADD` | βœ… | Element-wise addition | -| `SUB` | βœ… | Element-wise subtraction | -| `MULT` | βœ… | Element-wise multiplication | -| `DIV` | βœ… | Element-wise division | -| `SUM` | βœ… | Rolling sum over *timeperiod* | -| `MAX` / `MAXINDEX` | βœ… | Rolling maximum / index | -| `MIN` / `MININDEX` | βœ… | Rolling minimum / index | -| `ACOS` / `ASIN` / `ATAN` | βœ… | Arc trig transforms | -| `CEIL` / `FLOOR` | βœ… | Round up / down | -| `COS` / `SIN` / `TAN` | βœ… | Trig transforms | -| `COSH` / `SINH` / `TANH` | βœ… | Hyperbolic transforms | -| `EXP` / `LN` / `LOG10` | βœ… | Exponential / log transforms | -| `SQRT` | βœ… | Square root | - -### Pandas API - -**Contract:** All indicators accept `pandas.Series` (or 1-D DataFrame columns) and return -`pandas.Series` β€” or a **tuple of Series** for multi-output functions like `MACD`, `BBANDS` β€” -with the **original index preserved**. - -**Default OHLCV column names:** When using a DataFrame with OHLCV data, the conventional names -are `open`, `high`, `low`, `close`, `volume`. To use different column names, use the helper -:func:`ferro_ta.utils.get_ohlcv` (or pass Series/arrays extracted from your DataFrame). - -**Single Series or tuple of Series:** - -```python -import pandas as pd -from ferro_ta import SMA, BBANDS, MACD, CDLDOJI - -close = pd.Series([44.34, 44.09, 44.15, 43.61, 44.33], index=pd.date_range("2024-01-01", 5)) - -# Single-output: returns Series -sma = SMA(close, timeperiod=3) # pd.Series with same index - -# Multi-output: returns tuple of Series -upper, mid, lower = BBANDS(close, timeperiod=3) # all pd.Series -``` - -**DataFrame with OHLCV columns (configurable names):** - -```python -import pandas as pd -from ferro_ta import ATR, RSI -from ferro_ta.utils import get_ohlcv # or: from ferro_ta._utils import get_ohlcv - -df = pd.DataFrame({ - "Open": [1, 2, 3], "High": [1.1, 2.1, 3.1], - "Low": [0.9, 1.9, 2.9], "Close": [1.05, 2.05, 3.05], -}, index=pd.date_range("2024-01-01", periods=3, freq="D")) - -# Extract with default names (open, high, low, close, volume) -o, h, l, c, v = get_ohlcv(df, open_col="Open", high_col="High", low_col="Low", close_col="Close") -atr = ATR(h, l, c, timeperiod=2) # index preserved -rsi = RSI(c, timeperiod=2) # index preserved -``` - -### Extended Indicators - -ferro-ta includes popular indicators that go beyond the TA-Lib standard set. -These are available in `ferro_ta.extended` and importable directly from `ferro_ta`. - -| Function | ferro-ta | Notes | -|----------|---------|-------| -| `VWAP` | βœ… | Volume Weighted Average Price β€” cumulative (session) or rolling window | -| `SUPERTREND` | βœ… | ATR-based trend signal; returns (supertrend_line, direction) | -| `ICHIMOKU` | βœ… | Ichimoku Cloud β€” Tenkan, Kijun, Senkou A/B, Chikou Span | -| `DONCHIAN` | βœ… | Donchian Channels β€” rolling highest high / lowest low | -| `PIVOT_POINTS` | βœ… | Pivot points β€” Classic, Fibonacci, Camarilla methods | -| `KELTNER_CHANNELS` | βœ… | EMA Β± (ATR Γ— multiplier) bands; returns (upper, middle, lower) | -| `HULL_MA` | βœ… | Hull Moving Average β€” fast, low-lag WMA-based MA | -| `CHANDELIER_EXIT` | βœ… | ATR-based trailing stop levels; returns (long_exit, short_exit) | -| `VWMA` | βœ… | Volume Weighted Moving Average β€” rolling sum(close*vol) / sum(vol) | -| `CHOPPINESS_INDEX` | βœ… | Market choppiness/trending strength index (0–100) | - -```python -from ferro_ta import VWAP, SUPERTREND, ICHIMOKU, DONCHIAN, PIVOT_POINTS -from ferro_ta import KELTNER_CHANNELS, HULL_MA, CHANDELIER_EXIT, VWMA, CHOPPINESS_INDEX -import numpy as np - -close = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.15]) -high = close + 0.5 -low = close - 0.5 -vol = np.full(len(close), 1_000_000.0) - -# Cumulative / rolling VWAP -vwap = VWAP(high, low, close, vol) -rolling_vwap = VWAP(high, low, close, vol, timeperiod=5) - -# Supertrend (trend line and direction: 1=up, -1=down) -st_line, direction = SUPERTREND(high, low, close, timeperiod=7, multiplier=3.0) - -# Ichimoku Cloud -tenkan, kijun, senkou_a, senkou_b, chikou = ICHIMOKU(high, low, close) - -# Donchian Channels -dc_upper, dc_mid, dc_lower = DONCHIAN(high, low, timeperiod=5) - -# Pivot Points -pivot, r1, s1, r2, s2 = PIVOT_POINTS(high, low, close, method="classic") -# method options: "classic", "fibonacci", "camarilla" - -# Keltner Channels -kc_upper, kc_mid, kc_lower = KELTNER_CHANNELS(high, low, close, timeperiod=20, atr_period=10) - -# Hull Moving Average -hull = HULL_MA(close, timeperiod=16) - -# Chandelier Exit -long_exit, short_exit = CHANDELIER_EXIT(high, low, close, timeperiod=22, multiplier=3.0) - -# Volume Weighted Moving Average -vwma = VWMA(close, vol, timeperiod=20) - -# Choppiness Index (100 = choppy, 0 = strong trend) -ci = CHOPPINESS_INDEX(high, low, close, timeperiod=14) -``` - -### Streaming / Live-Trading API - -For real-time / bar-by-bar processing, import classes from `ferro_ta.streaming`. -Each class maintains state internally and returns `NaN` during the warmup window: - -```python -from ferro_ta.streaming import StreamingSMA, StreamingEMA, StreamingRSI, StreamingATR -from ferro_ta.streaming import StreamingBBands, StreamingMACD, StreamingStoch -from ferro_ta.streaming import StreamingVWAP, StreamingSupertrend - -sma = StreamingSMA(period=20) -rsi = StreamingRSI(period=14) -atr = StreamingATR(period=14) -bb = StreamingBBands(period=20, nbdevup=2.0, nbdevdn=2.0) -macd = StreamingMACD(fastperiod=12, slowperiod=26, signalperiod=9) -stoch = StreamingStoch(fastk_period=5, slowk_period=3, slowd_period=3) -vwap = StreamingVWAP() # reset() at session open -st = StreamingSupertrend(period=7, multiplier=3.0) - -for bar in live_data_feed: - current_sma = sma.update(bar.close) - current_rsi = rsi.update(bar.close) - current_atr = atr.update(bar.high, bar.low, bar.close) - upper, mid, lower = bb.update(bar.close) - macd_line, signal, histogram = macd.update(bar.close) - slowk, slowd = stoch.update(bar.high, bar.low, bar.close) - current_vwap = vwap.update(bar.high, bar.low, bar.close, bar.volume) - st_line, trend_dir = st.update(bar.high, bar.low, bar.close) # 1=up, -1=down -``` - -### πŸ“ˆ Implementation Coverage Summary - -| Category | Implemented | Not Implemented | -|----------|:-----------:|:---------------:| -| Overlap Studies | 19 | 0 | -| Momentum Indicators | 28 | 0 | -| Volume Indicators | 3 | 0 | -| Volatility Indicators | 3 | 0 | -| Cycle Indicators | 6 | 0 | -| Price Transforms | 4 | 0 | -| Statistic Functions | 9 | 0 | -| Pattern Recognition | 61 | 0 | -| Math Operators / Transforms | 24 | 0 | -| Extended Indicators | 10 | β€” | -| Streaming Classes | 9 | β€” | -| **Total** | **162+** | **0** | - -> πŸŽ‰ **100% of TA-Lib's function set is implemented.** NaN values are placed at the beginning of each output array for the warmup period. - ---- - -## πŸ”„ Batch Execution API - -Run indicators on multiple price series (symbols) in a single call. Dedicated Rust-backed functions for SMA, EMA, RSI, ATR, STOCH, and ADX; use `batch_apply` for any other indicator. - -```python -import numpy as np -from ferro_ta.batch import batch_sma, batch_ema, batch_rsi, batch_atr, batch_stoch, batch_adx, batch_apply - -# 100 bars Γ— 5 symbols -close = np.random.rand(100, 5) + 50.0 -high = close + 0.1 -low = close - 0.1 - -sma_out = batch_sma(close, timeperiod=14) # (100, 5) -ema_out = batch_ema(close, timeperiod=14) # (100, 5) -rsi_out = batch_rsi(close, timeperiod=14) # (100, 5) -atr_out = batch_atr(high, low, close, timeperiod=14) -stoch_k, stoch_d = batch_stoch(high, low, close) -adx_out = batch_adx(high, low, close, timeperiod=14) - -# Any single-series function via batch_apply -from ferro_ta import BBANDS -def bbands_upper(c, **kw): - return BBANDS(c, **kw)[0] -upper = batch_apply(close, bbands_upper, timeperiod=20) -``` - ---- - -## πŸ¦€ Pure Rust Core Library - -ferro-ta is structured as a Cargo workspace with two crates: - -| Crate | Purpose | -|-------|---------| -| `ferro_ta` (root) | PyO3 `#[pyfunction]` wrappers β€” converts numpy ↔ `&[f64]`; builds the Python wheel | -| `crates/ferro_ta_core` | Pure Rust indicators β€” no PyO3/numpy dependency; usable from any Rust project | - -```bash -# Build and test the core crate directly -cargo build -p ferro_ta_core -cargo test -p ferro_ta_core -``` - -```rust -use ferro_ta_core::overlap; - -let close = vec![1.0, 2.0, 3.0, 4.0, 5.0]; -let sma = overlap::sma(&close, 3); -``` - -### Rust Module Structure - -The main `ferro_ta` crate (`src/`) uses a **consistent directory-based module layout** matching the TA-Lib category structure. Every module is a directory with `mod.rs` declaring sub-modules and a `register()` function; each indicator (or closely related group) lives in its own `.rs` file: - -``` -src/ -β”œβ”€β”€ lib.rs # PyModule entry point β€” calls each module's register() -β”œβ”€β”€ overlap/ # Overlap Studies (SMA, EMA, BBANDS, MACD, SAR, …) -β”‚ β”œβ”€β”€ mod.rs -β”‚ β”œβ”€β”€ sma.rs, ema.rs, wma.rs, dema.rs, tema.rs, trima.rs, kama.rs, t3.rs -β”‚ β”œβ”€β”€ bbands.rs, macd.rs, macdfix.rs, macdext.rs -β”‚ β”œβ”€β”€ sar.rs, sarext.rs, mama.rs, midpoint.rs, midprice.rs -β”‚ └── ma_mavp.rs -β”œβ”€β”€ momentum/ # Momentum Indicators (RSI, STOCH, ADX, CCI, …) -β”‚ β”œβ”€β”€ mod.rs -β”‚ └── rsi.rs, mom.rs, roc.rs, willr.rs, aroon.rs, cci.rs, mfi.rs, -β”‚ bop.rs, stochf.rs, stoch.rs, stochrsi.rs, apo.rs, ppo.rs, cmo.rs, -β”‚ adx.rs, trix.rs, ultosc.rs -β”œβ”€β”€ volatility/ # Volatility Indicators (ATR, NATR, TRANGE) -β”‚ β”œβ”€β”€ mod.rs -β”‚ β”œβ”€β”€ common.rs # shared TR computation -β”‚ β”œβ”€β”€ trange.rs, atr.rs, natr.rs -β”œβ”€β”€ volume/ # Volume Indicators (AD, ADOSC, OBV) -β”‚ β”œβ”€β”€ mod.rs -β”‚ └── ad.rs, adosc.rs, obv.rs -β”œβ”€β”€ statistic/ # Statistic Functions (STDDEV, VAR, LINEARREG*, BETA, CORREL) -β”‚ β”œβ”€β”€ mod.rs -β”‚ β”œβ”€β”€ common.rs # shared linreg() helper -β”‚ └── stddev.rs, var.rs, linearreg.rs, beta.rs, correl.rs -β”œβ”€β”€ price_transform/ # Price Transformations (AVGPRICE, MEDPRICE, TYPPRICE, WCLPRICE) -β”‚ β”œβ”€β”€ mod.rs -β”‚ └── avgprice.rs, medprice.rs, typprice.rs, wclprice.rs -β”œβ”€β”€ cycle/ # Cycle Indicators (HT_TRENDLINE, HT_DCPERIOD, …) -β”‚ β”œβ”€β”€ mod.rs -β”‚ β”œβ”€β”€ common.rs # shared HT core pipeline (compute_ht_core) -β”‚ └── ht_trendline.rs, ht_dcperiod.rs, ht_dcphase.rs, -β”‚ ht_phasor.rs, ht_sine.rs, ht_trendmode.rs -└── pattern/ # Pattern Recognition (CDL2CROWS, CDLDOJI, …) - β”œβ”€β”€ mod.rs - β”œβ”€β”€ common.rs # shared candle utilities - └── cdl*.rs # one file per pattern (61 patterns) -``` - -This layout makes it easy to add, review, or modify individual indicators in isolation β€” simply edit or add the relevant `.rs` file and update `mod.rs`. - -### Python sub-package layout - -The `python/ferro_ta/` package is organized into sub-packages by concern. -Backward-compat stubs at the old flat paths (e.g. `ferro_ta.momentum`) re-export -from the new locations, so existing code continues to work without changes. - -``` -python/ferro_ta/ -β”œβ”€β”€ __init__.py # top-level re-exports and public API -β”œβ”€β”€ core/ # Exceptions, configuration, registry, logging, raw FFI bindings -β”œβ”€β”€ indicators/ # Technical indicators (momentum, overlap, volatility, volume, -β”‚ # statistic, cycle, pattern, price_transform, math_ops, extended) -β”œβ”€β”€ data/ # Streaming, batch, chunked, resampling, aggregation, adapters -β”œβ”€β”€ analysis/ # Portfolio, backtest, regime, cross_asset, attribution, -β”‚ # signals, features, crypto, options, futures, -β”‚ # options_strategy, derivatives_payoff -β”œβ”€β”€ tools/ # Visualisation, alerting, DSL, pipeline, workflow, -β”‚ # api_info, GPU support -└── mcp/ # Model Context Protocol server -``` - - - -## 🌐 Other Languages (WebAssembly / Node.js) - -A WebAssembly binding is available in the `wasm/` directory, exposing SMA, EMA, BBANDS, -RSI, ATR, OBV, and MACD for use in Node.js and browsers. - -```javascript -// Node.js (after `wasm-pack build --target nodejs --out-dir pkg` in wasm/) -const { sma, rsi, macd } = require('./wasm/pkg/ferro_ta_wasm.js'); - -const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10]); -const smaOut = sma(close, 3); // Float64Array β€” first 2 values are NaN -const rsiOut = rsi(close, 5); // Float64Array β€” first 5 values are NaN - -// MACD β€” returns [macd_line, signal_line, histogram] as a js_sys::Array -const [macdLine, signal, hist] = macd(close, 3, 5, 2); -``` - -See [`wasm/README.md`](wasm/README.md) for build instructions, the full list of exposed -functions, and browser usage examples. - ---- - -## πŸ”₯ GPU Acceleration (Optional) - -For very large arrays (millions of bars), an optional GPU-accelerated path is available -via [PyTorch](https://pytorch.org/). Pass a `torch.Tensor` on CUDA or MPS and get a tensor back; -NumPy in β†’ NumPy out (CPU fallback). - -```bash -pip install "ferro-ta[gpu]" -# or install PyTorch yourself (e.g. with CUDA or MPS support): -# pip install torch -``` - -```python -import torch -from ferro_ta.gpu import sma, ema, rsi - -# Use CUDA or MPS (Apple Silicon) -close_gpu = torch.tensor( - [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.15, 43.61, 44.33], - device="cuda", # or device="mps" on Apple Silicon - dtype=torch.float64, -) - -result = sma(close_gpu, timeperiod=5) # torch.Tensor on same device -result_cpu = result.cpu().numpy() # back to NumPy if needed -``` - -PyTorch tensors in β†’ PyTorch tensors out; NumPy arrays in β†’ NumPy arrays out (CPU). -See [`docs/gpu-backend.md`](docs/gpu-backend.md) for supported indicators, limitations, -and benchmark data. - ---- - -## πŸ“‰ Backtesting - -A minimal vectorized backtester is available at `ferro_ta.backtest`: - -```python -import numpy as np -from ferro_ta.backtest import backtest - -np.random.seed(42) -close = np.cumprod(1 + np.random.randn(200) * 0.01) * 100 - -# Run an RSI 30/70 strategy -result = backtest(close, strategy="rsi_30_70", timeperiod=14) -print(f"Final equity: {result.final_equity:.4f}") -print(f"Number of trades: {result.n_trades}") - -# Or use SMA crossover -result2 = backtest(close, strategy="sma_crossover", fast=10, slow=30) -result3 = backtest(close, strategy="macd_crossover", commission_per_trade=0.001, slippage_bps=5) -``` - -> **Note:** This is a *minimal harness* for testing strategies. Optional `commission_per_trade` and `slippage_bps` are supported; for margin or full order types consider `backtrader`, `zipline`, or `vectorbt`. -> For production use consider `backtrader`, `zipline`, or `vectorbt`. - ---- - -## πŸ”— Indicator Pipeline - -Compose multiple indicators into a reusable pipeline: - -```python -import numpy as np -from ferro_ta import SMA, EMA, RSI, BBANDS -from ferro_ta.pipeline import Pipeline - -close = np.cumprod(1 + np.random.randn(200) * 0.01) * 100 - -pipe = ( - Pipeline() - .add("sma_20", SMA, timeperiod=20) - .add("ema_20", EMA, timeperiod=20) - .add("rsi_14", RSI, timeperiod=14) - .add("bb", BBANDS, output_keys=["bb_upper", "bb_mid", "bb_lower"], - timeperiod=20, nbdevup=2.0, nbdevdn=2.0) -) - -results = pipe.run(close) -# {'sma_20': array([...]), 'ema_20': array([...]), ..., 'bb_lower': array([...])} -print(list(results.keys())) -``` - ---- - -## βš™οΈ Configuration Defaults - -Set global parameter defaults to avoid repeating them on every call: - -```python -import ferro_ta.config as config - -config.set_default("timeperiod", 20) # applies to all indicators -config.set_default("RSI.timeperiod", 14) # RSI-specific override - -from ferro_ta import RSI, SMA -# RSI(close) uses timeperiod=14; SMA(close) uses timeperiod=20 - -# Context manager for temporary overrides -with config.Config(timeperiod=5): - result = SMA(close) # timeperiod=5 inside this block -# back to timeperiod=20 after the block - -config.reset() # clear all custom defaults -``` - ---- - -## πŸ”Œ Plugin Registry - -Register and call any indicator (built-in or custom) by name. See the -`Writing a plugin `_ doc for the plugin contract and a full example -(``examples/custom_indicator.py``). - -```python -import numpy as np -from ferro_ta.registry import register, run, list_indicators - -# Call a built-in by name -close = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10]) -sma = run("SMA", close, timeperiod=3) - -# Register a custom indicator -def DOUBLE_RSI(close, timeperiod=14, smooth=3): - import ferro_ta - rsi = ferro_ta.RSI(close, timeperiod=timeperiod) - return ferro_ta.SMA(rsi, timeperiod=smooth) - -register("DOUBLE_RSI", DOUBLE_RSI) -result = run("DOUBLE_RSI", close, timeperiod=5, smooth=2) - -# List all registered indicators -print(list_indicators()[:5]) # ['AD', 'ADOSC', 'ADX', 'ADXR', 'APO'] -``` - ---- - -## πŸ›‘οΈ Error Handling - -ferro-ta provides a typed exception hierarchy with **error codes** and **actionable suggestions**: - -```python -from ferro_ta import FerroTAError, FerroTAValueError, FerroTAInputError -from ferro_ta.exceptions import check_timeperiod, check_equal_length - -# Catch any ferro-ta error -try: - result = SMA(close, timeperiod=0) -except FerroTAValueError as e: - print(e.code) # "FTERR001" - print(e.suggestion) # "Set timeperiod=1 or higher." - print(e) # "[FTERR001] timeperiod must be >= 1, got 0\n Suggestion: ..." - -# Validate inputs before calling -check_equal_length(open=open_, close=close) # raises FerroTAInputError (FTERR004) on mismatch -check_timeperiod(timeperiod) # raises FerroTAValueError (FTERR001) if < 1 -``` - -Error code reference: - -| Code | Exception | Meaning | -|------|-----------|---------| -| `FTERR001` | `FerroTAValueError` | Invalid parameter value | -| `FTERR002` | `FerroTAInputError` | Invalid input array | -| `FTERR003` | `FerroTAInputError` | Input array too short | -| `FTERR004` | `FerroTAInputError` | Mismatched array lengths | -| `FTERR005` | `FerroTAInputError` | Array contains NaN/Inf (strict mode) | -| `FTERR006` | `FerroTAValueError/InputError` | Rust-bridge error | - -## πŸ” Observability & Logging - -ferro-ta ships a lightweight logging module that integrates with Python's standard `logging` library: - -```python -import ferro_ta - -# Enable DEBUG-level logging (writes to stderr) -ferro_ta.enable_debug() -result = ferro_ta.SMA(close, timeperiod=20) -# DEBUG [ferro_ta] calling SMA(ndarray(252,) dtype=float64, timeperiod=20) -# DEBUG [ferro_ta] SMA β†’ ndarray(252,) [0.042 ms] -ferro_ta.disable_debug() - -# Context manager: temporary debug output -with ferro_ta.debug_mode(): - ferro_ta.RSI(close, timeperiod=14) - -# Call with automatic shape + timing log -result = ferro_ta.log_call(ferro_ta.ATR, high, low, close, timeperiod=14) - -# Benchmark: returns {mean_ms, min_ms, max_ms, total_ms, n} -stats = ferro_ta.benchmark(ferro_ta.SMA, close, timeperiod=20, n=500) -print(f"SMA mean: {stats['mean_ms']:.3f} ms") - -# Decorator: wrap any function with automatic logging -@ferro_ta.traced -def my_strategy(close): - sma = ferro_ta.SMA(close, timeperiod=20) - rsi = ferro_ta.RSI(close, timeperiod=14) - return sma, rsi -``` - -## πŸ”Ž API Discovery - -```python -import ferro_ta - -# List all 160+ indicators with metadata -all_indicators = ferro_ta.indicators() -print(len(all_indicators)) # 160+ - -# Filter by category -overlap = ferro_ta.indicators(category="overlap") -momentum = ferro_ta.indicators(category="momentum") - -# Get parameter info for any indicator -d = ferro_ta.info(ferro_ta.SMA) -print(d["signature"]) # (close: ArrayLike, timeperiod: int = 30) -> NDArray[float64] -print(d["params"]) # {"close": {"default": None, ...}, "timeperiod": {"default": 30, ...}} - -# By name string -d = ferro_ta.info("MACD") -``` - -See [`PLATFORMS.md`](PLATFORMS.md) for supported OS and Python versions. -See [`CHANGELOG.md`](CHANGELOG.md) and [`VERSIONING.md`](VERSIONING.md) for release notes and versioning policy. -See [`RELEASE.md`](RELEASE.md) for the step-by-step release playbook. -See [`examples/`](examples/) for Jupyter notebook examples (quickstart, streaming, backtesting, and more). - -## πŸ—ΊοΈ Multi-Timeframe, Portfolio, and ML Features - -### OHLCV Resampling and Multi-Timeframe API (`ferro_ta.resampling`) - -```python -from ferro_ta.resampling import resample, volume_bars, multi_timeframe -from ferro_ta import RSI -import pandas as pd - -# Resample 1-minute data to 5-minute bars (requires pandas) -df5 = resample(ohlcv_df, '5min') - -# Volume bars (every 10,000 units of volume) β€” Rust backend -vbars = volume_bars(ohlcv_df, volume_threshold=10_000) - -# Multi-timeframe RSI in one call -mtf = multi_timeframe(ohlcv_df, ['5min', '15min'], indicator=RSI, - indicator_kwargs={'timeperiod': 14}) -# mtf = {'5min': array(...), '15min': array(...)} -``` - -### Tick Aggregation Pipeline (`ferro_ta.aggregation`) - -```python -from ferro_ta.aggregation import aggregate_ticks, TickAggregator - -# Tick bars, volume bars, time bars β€” all Rust-backed -tick_bars = aggregate_ticks(ticks, rule='tick:100') -volume_bars = aggregate_ticks(ticks, rule='volume:500') -time_bars = aggregate_ticks(ticks, rule='time:60') - -# Class-based API -agg = TickAggregator(rule='tick:100') -bars = agg.aggregate(ticks) # β†’ pandas DataFrame or dict -``` - -### Strategy Expression DSL (`ferro_ta.dsl`) - -```python -from ferro_ta.dsl import Strategy, evaluate - -# Parse and evaluate expression strings -strat = Strategy("RSI(14) < 30 and close > SMA(20)") -signal = strat.evaluate({"close": close_arr}) # 1/0 integer array -``` - -### Signal Composition and Screening (`ferro_ta.signals`) - -```python -from ferro_ta.signals import compose, screen, rank_signals - -# Weighted combination of signal columns (Rust-backed) -score = compose(signals_df, weights=[0.4, 0.35, 0.25]) - -# Screening -top2 = screen({'AAPL': 0.8, 'MSFT': 0.9, 'GOOG': 0.5}, top_n=2) -# {'MSFT': 0.9, 'AAPL': 0.8} -``` - -### Portfolio Analytics (`ferro_ta.portfolio`) - -```python -from ferro_ta.portfolio import correlation_matrix, portfolio_volatility, beta, drawdown - -corr = correlation_matrix(returns_df) # Pearson corr matrix -vol = portfolio_volatility(returns_df, weights, # sqrt(w'Ξ£w) - annualise=252) -b = beta(asset_returns, benchmark_returns) # OLS beta -rb = beta(asset_returns, benchmark_returns, # rolling beta - window=30) -dd, mx = drawdown(equity_curve) # drawdown series + max -``` - -### Cross-Asset Relative Strength (`ferro_ta.cross_asset`) - -```python -from ferro_ta.cross_asset import relative_strength, spread, ratio, zscore, rolling_beta - -rs = relative_strength(asset_rets, bench_rets) # cumulative return ratio -sp = spread(price_a, price_b, hedge=1.0) # A - hedge * B -z = zscore(sp, window=20) # rolling Z-score -``` - -### Feature Matrix for ML (`ferro_ta.features`) - -```python -from ferro_ta.features import feature_matrix - -fm = feature_matrix(ohlcv, [ - ('RSI', {'timeperiod': 14}), - ('SMA', {'timeperiod': 20}), - ('ATR', {'timeperiod': 14}), -], nan_policy='drop') -# fm is a pandas DataFrame with one column per indicator -# Use with sklearn: clf.fit(fm.values, labels) -``` - -### Charting and Visualization (`ferro_ta.viz`) - -```python -from ferro_ta.viz import plot -from ferro_ta import RSI, SMA - -fig = plot(ohlcv_df, indicators={'RSI(14)': RSI(close), 'SMA(20)': SMA(close)}, - backend='matplotlib', savefig='chart.png') -# Also supports 'plotly' backend for interactive charts -``` - -### Market Data Adapters (`ferro_ta.adapters`) - -```python -from ferro_ta.adapters import CsvAdapter, InMemoryAdapter, register_adapter, DataAdapter - -# Load from CSV -adapter = CsvAdapter('data.csv', index_col='date') -ohlcv = adapter.fetch() - -# Custom adapter -class MyAdapter(DataAdapter): - def fetch(self, **kwargs): return ... - -register_adapter('mybroker', MyAdapter) -``` - ---- - -## 🀝 Community - -[![GitHub Discussions](https://img.shields.io/badge/discussions-GitHub-blue?logo=github)](https://github.com/pratikbhadane24/ferro-ta/discussions) - -- **GitHub Discussions** β€” Ask questions, share strategies, and request features in our [Discussions](https://github.com/pratikbhadane24/ferro-ta/discussions) space. Categories: **Q&A**, **Ideas**, **Show & Tell**, **Announcements**. -- **Contributing**: See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup, code style, and PR guidelines. -- **Code of Conduct**: All participants are expected to follow the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). -- **Governance**: Decision-making process and maintainer info in [`GOVERNANCE.md`](GOVERNANCE.md). -- **Roadmap**: Development plan in [`ROADMAP.md`](ROADMAP.md). -- **Security**: Responsible disclosure policy in [`SECURITY.md`](SECURITY.md). -- **Migration from TA-Lib**: Step-by-step guide in the [documentation](docs/migration_talib.rst). -- **Library Compatibility Guides** β€” drop-in migration instructions and cross-library test results: - - [TA-Lib compatibility](docs/compatibility/talib.md) β€” full indicator mapping, API differences, and migration guide - - [pandas-ta compatibility](docs/compatibility/pandas_ta.md) β€” indicator mapping, known differences, and comparison tests - - [ta (Bukosabino) compatibility](docs/compatibility/ta.md) β€” indicator mapping, known differences, and comparison tests - - [Tulipy compatibility](docs/compatibility/tulipy.md) β€” C99 Tulip Indicators: output truncation, memory requirements, signature mapping - - [finta compatibility](docs/compatibility/finta.md) β€” pure-Pandas library: DataFrame requirements, speed comparison, migration guide - -- **Cross-Library Benchmarks** β€” accuracy and speed comparison across all 6 libraries: - - [Benchmarks README](benchmarks/README.md) β€” real timing results (Β΅s), accuracy methodology, and known limitations - - [Performance Roadmap](PERFORMANCE_ROADMAP.md) β€” plan to achieve 100x speedup over Tulipy - ---- - -
- -**ferro-ta** β€” Built with ❀️ and Rust. [Star ⭐ on GitHub](https://github.com/pratikbhadane24/ferro-ta) to support the project. - -
+More setup details live in [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/TA_LIB_COMPATIBILITY.md b/TA_LIB_COMPATIBILITY.md new file mode 100644 index 0000000..ac4980c --- /dev/null +++ b/TA_LIB_COMPATIBILITY.md @@ -0,0 +1,262 @@ +# TA-Lib Compatibility + +`ferro-ta` covers **100% of TA-Lib's function set** (`162+` indicators). This +file keeps the full GitHub-facing parity matrix in one place so the root +`README.md` can stay product-focused. + +See also: + +- [docs/migration_talib.rst](docs/migration_talib.rst) +- [docs/compatibility/talib.md](docs/compatibility/talib.md) +- [docs/support_matrix.rst](docs/support_matrix.rst) + +## Legend + + +| Symbol | Meaning | +| -------- | ------------------------------------------------------------------------------------------------------ | +| βœ… Exact | Values match TA-Lib to floating-point precision | +| βœ… Close | Values match after a short convergence window (EMA-seed difference) | +| ⚠️ Corr | Strong correlation (> 0.95) but not numerically identical (Wilder smoothing seed or algorithm variant) | +| ⚠️ Shape | Same output shape / NaN structure; values differ due to algorithm variant | +| ❌ | Not yet implemented | + + +## Overlap Studies + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | ----------------------------------------------------- | +| `BBANDS` | βœ… | βœ… Exact | Bollinger Bands | +| `DEMA` | βœ… | βœ… Close | Double EMA; converges after ~20 bars | +| `EMA` | βœ… | βœ… Close | Exponential Moving Average; converges after ~20 bars | +| `KAMA` | βœ… | βœ… Exact | Kaufman Adaptive MA (values match after seed bar) | +| `MA` | βœ… | βœ… Exact | Moving average (generic, type-selectable) | +| `MAMA` | βœ… | ⚠️ Corr | MESA Adaptive MA | +| `MAVP` | βœ… | βœ… Exact | MA with variable period | +| `MIDPOINT` | βœ… | βœ… Exact | Midpoint over period | +| `MIDPRICE` | βœ… | βœ… Exact | Midpoint price over period | +| `SAR` | βœ… | ⚠️ Shape | Parabolic SAR (same shape; reversal history diverges) | +| `SAREXT` | βœ… | ⚠️ Shape | Parabolic SAR Extended | +| `SMA` | βœ… | βœ… Exact | Simple Moving Average | +| `T3` | βœ… | βœ… Close | Triple Exponential MA (T3); converges after ~50 bars | +| `TEMA` | βœ… | βœ… Close | Triple EMA; converges after ~20 bars | +| `TRIMA` | βœ… | βœ… Exact | Triangular Moving Average | +| `WMA` | βœ… | βœ… Exact | Weighted Moving Average | + + +## Momentum Indicators + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | ---------------------------------------------------------------------------- | +| `ADX` | βœ… | βœ… Close | Avg Directional Movement Index (TA-Lib Wilder sum-seeding) | +| `ADXR` | βœ… | βœ… Close | ADX Rating (inherits ADX; TA-Lib seeding) | +| `APO` | βœ… | βœ… Close | Absolute Price Oscillator (EMA-based) | +| `AROON` | βœ… | βœ… Exact | Aroon Up/Down | +| `AROONOSC` | βœ… | βœ… Exact | Aroon Oscillator | +| `BOP` | βœ… | βœ… Exact | Balance Of Power | +| `CCI` | βœ… | βœ… Exact | Commodity Channel Index (TA-Lib-compatible MAD formula) | +| `CMO` | βœ… | βœ… Close | Chande Momentum Oscillator (rolling window, TA-Lib-compatible) | +| `DX` | βœ… | βœ… Close | Directional Movement Index (TA-Lib Wilder sum-seeding) | +| `MACD` | βœ… | βœ… Close | MACD (EMA-based; converges after ~30 bars) | +| `MACDEXT` | βœ… | βœ… Close | MACD with controllable MA type (EMA-based; converges) | +| `MACDFIX` | βœ… | βœ… Close | MACD Fixed 12/26 (EMA-based; converges) | +| `MFI` | βœ… | βœ… Exact | Money Flow Index | +| `MINUS_DI` | βœ… | βœ… Close | Minus Directional Indicator (TA-Lib Wilder sum-seeding) | +| `MINUS_DM` | βœ… | βœ… Close | Minus Directional Movement (TA-Lib Wilder sum-seeding) | +| `MOM` | βœ… | βœ… Exact | Momentum | +| `PLUS_DI` | βœ… | βœ… Close | Plus Directional Indicator (TA-Lib Wilder sum-seeding) | +| `PLUS_DM` | βœ… | βœ… Close | Plus Directional Movement (TA-Lib Wilder sum-seeding) | +| `PPO` | βœ… | βœ… Close | Percentage Price Oscillator (EMA-based) | +| `ROC` | βœ… | βœ… Exact | Rate of Change | +| `ROCP` | βœ… | βœ… Exact | Rate of Change Percentage | +| `ROCR` | βœ… | βœ… Exact | Rate of Change Ratio | +| `ROCR100` | βœ… | βœ… Exact | Rate of Change Ratio Γ— 100 | +| `RSI` | βœ… | βœ… Close | Relative Strength Index (TA-Lib Wilder seeding; converges after ~1 seed bar) | +| `STOCH` | βœ… | βœ… Close | Stochastic (TA-Lib-compatible SMA smoothing for slowk and slowd) | +| `STOCHF` | βœ… | βœ… Exact | Stochastic Fast (%K exact; %D NaN offset Β±2) | +| `STOCHRSI` | βœ… | βœ… Close | Stochastic RSI (TA-Lib-compatible; SMA fastd, Wilder-seeded RSI) | +| `TRIX` | βœ… | βœ… Close | 1-day ROC of Triple EMA (EMA-based; converges) | +| `ULTOSC` | βœ… | βœ… Exact | Ultimate Oscillator | +| `WILLR` | βœ… | βœ… Exact | Williams' %R | + + +## Volume Indicators + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | ------------------------------------------------------------------ | +| `AD` | βœ… | βœ… Exact | Chaikin A/D Line | +| `ADOSC` | βœ… | βœ… Exact | Chaikin A/D Oscillator | +| `OBV` | βœ… | βœ… Exact | On Balance Volume (increments identical; constant offset at bar 0) | + + +## Volatility Indicators + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | ----------------------------------------------------------------------- | +| `ATR` | βœ… | βœ… Close | Average True Range (TA-Lib Wilder seeding; matches from bar timeperiod) | +| `NATR` | βœ… | βœ… Close | Normalized ATR (TA-Lib Wilder seeding) | +| `TRANGE` | βœ… | βœ… Exact | True Range (bar 0 differs; all others identical) | + + +## Cycle Indicators + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | ---------------------------------------------------------- | +| `HT_DCPERIOD` | βœ… | ⚠️ Shape | Hilbert Transform Dominant Cycle Period (Ehlers algorithm) | +| `HT_DCPHASE` | βœ… | ⚠️ Shape | Hilbert Transform Dominant Cycle Phase | +| `HT_PHASOR` | βœ… | ⚠️ Shape | Hilbert Transform Phasor Components (inphase, quadrature) | +| `HT_SINE` | βœ… | ⚠️ Shape | Hilbert Transform SineWave (sine, leadsine) | +| `HT_TRENDLINE` | βœ… | ⚠️ Shape | Hilbert Transform Instantaneous Trendline | +| `HT_TRENDMODE` | βœ… | ⚠️ Shape | Hilbert Transform Trend vs Cycle Mode (1=trend, 0=cycle) | + + +## Price Transformations + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------- | -------- | -------- | -------------------- | +| `AVGPRICE` | βœ… | βœ… Exact | Average Price | +| `MEDPRICE` | βœ… | βœ… Exact | Median Price | +| `TYPPRICE` | βœ… | βœ… Exact | Typical Price | +| `WCLPRICE` | βœ… | βœ… Exact | Weighted Close Price | + + +## Statistic Functions + + +| TA-Lib Function | ferro-ta | Accuracy | Notes | +| --------------------- | -------- | -------- | ----------------------------------------------------------- | +| `BETA` | βœ… | βœ… Close | Beta coefficient (returns-based regression matching TA-Lib) | +| `CORREL` | βœ… | βœ… Exact | Pearson Correlation Coefficient | +| `LINEARREG` | βœ… | βœ… Exact | Linear Regression | +| `LINEARREG_ANGLE` | βœ… | βœ… Exact | Linear Regression Angle | +| `LINEARREG_INTERCEPT` | βœ… | βœ… Exact | Linear Regression Intercept | +| `LINEARREG_SLOPE` | βœ… | βœ… Exact | Linear Regression Slope | +| `STDDEV` | βœ… | βœ… Exact | Standard Deviation | +| `TSF` | βœ… | βœ… Exact | Time Series Forecast | +| `VAR` | βœ… | βœ… Exact | Variance | + + +## Pattern Recognition + +`ferro-ta` implements all 61 candlestick patterns. All return the same +`{-100, 0, 100}` convention as TA-Lib. Pattern thresholds may differ slightly +from the full TA-Lib implementation. + + +| TA-Lib Function | ferro-ta | Notes | +| --------------------- | -------- | --------------------------------------------------- | +| `CDL2CROWS` | βœ… | Two Crows | +| `CDL3BLACKCROWS` | βœ… | Three Black Crows | +| `CDL3INSIDE` | βœ… | Three Inside Up/Down | +| `CDL3LINESTRIKE` | βœ… | Three-Line Strike | +| `CDL3OUTSIDE` | βœ… | Three Outside Up/Down | +| `CDL3STARSINSOUTH` | βœ… | Three Stars In The South | +| `CDL3WHITESOLDIERS` | βœ… | Three Advancing White Soldiers | +| `CDLABANDONEDBABY` | βœ… | Abandoned Baby | +| `CDLADVANCEBLOCK` | βœ… | Advance Block | +| `CDLBELTHOLD` | βœ… | Belt-hold | +| `CDLBREAKAWAY` | βœ… | Breakaway | +| `CDLCLOSINGMARUBOZU` | βœ… | Closing Marubozu | +| `CDLCONCEALBABYSWALL` | βœ… | Concealing Baby Swallow | +| `CDLCOUNTERATTACK` | βœ… | Counterattack | +| `CDLDARKCLOUDCOVER` | βœ… | Dark Cloud Cover | +| `CDLDOJI` | βœ… | Doji | +| `CDLDOJISTAR` | βœ… | Doji Star | +| `CDLDRAGONFLYDOJI` | βœ… | Dragonfly Doji | +| `CDLENGULFING` | βœ… | Engulfing Pattern | +| `CDLEVENINGDOJISTAR` | βœ… | Evening Doji Star | +| `CDLEVENINGSTAR` | βœ… | Evening Star | +| `CDLGAPSIDESIDEWHITE` | βœ… | Up/Down-gap side-by-side white lines | +| `CDLGRAVESTONEDOJI` | βœ… | Gravestone Doji | +| `CDLHAMMER` | βœ… | Hammer | +| `CDLHANGINGMAN` | βœ… | Hanging Man | +| `CDLHARAMI` | βœ… | Harami Pattern | +| `CDLHARAMICROSS` | βœ… | Harami Cross Pattern | +| `CDLHIGHWAVE` | βœ… | High-Wave Candle | +| `CDLHIKKAKE` | βœ… | Hikkake Pattern | +| `CDLHIKKAKEMOD` | βœ… | Modified Hikkake Pattern | +| `CDLHOMINGPIGEON` | βœ… | Homing Pigeon | +| `CDLIDENTICAL3CROWS` | βœ… | Identical Three Crows | +| `CDLINNECK` | βœ… | In-Neck Pattern | +| `CDLINVERTEDHAMMER` | βœ… | Inverted Hammer | +| `CDLKICKING` | βœ… | Kicking | +| `CDLKICKINGBYLENGTH` | βœ… | Kicking by the longer Marubozu | +| `CDLLADDERBOTTOM` | βœ… | Ladder Bottom | +| `CDLLONGLEGGEDDOJI` | βœ… | Long Legged Doji | +| `CDLLONGLINE` | βœ… | Long Line Candle | +| `CDLMARUBOZU` | βœ… | Marubozu | +| `CDLMATCHINGLOW` | βœ… | Matching Low | +| `CDLMATHOLD` | βœ… | Mat Hold | +| `CDLMORNINGDOJISTAR` | βœ… | Morning Doji Star | +| `CDLMORNINGSTAR` | βœ… | Morning Star | +| `CDLONNECK` | βœ… | On-Neck Pattern | +| `CDLPIERCING` | βœ… | Piercing Pattern | +| `CDLRICKSHAWMAN` | βœ… | Rickshaw Man | +| `CDLRISEFALL3METHODS` | βœ… | Rising/Falling Three Methods | +| `CDLSEPARATINGLINES` | βœ… | Separating Lines | +| `CDLSHOOTINGSTAR` | βœ… | Shooting Star | +| `CDLSHORTLINE` | βœ… | Short Line Candle | +| `CDLSPINNINGTOP` | βœ… | Spinning Top | +| `CDLSTALLEDPATTERN` | βœ… | Stalled Pattern | +| `CDLSTICKSANDWICH` | βœ… | Stick Sandwich | +| `CDLTAKURI` | βœ… | Takuri (Dragonfly Doji with very long lower shadow) | +| `CDLTASUKIGAP` | βœ… | Tasuki Gap | +| `CDLTHRUSTING` | βœ… | Thrusting Pattern | +| `CDLTRISTAR` | βœ… | Tristar Pattern | +| `CDLUNIQUE3RIVER` | βœ… | Unique 3 River | +| `CDLUPSIDEGAP2CROWS` | βœ… | Upside Gap Two Crows | +| `CDLXSIDEGAP3METHODS` | βœ… | Upside/Downside Gap Three Methods | + + +## Math Operators / Math Transforms + +`ferro-ta` provides TA-Lib-compatible wrappers for all arithmetic and +math-transform functions. Rolling functions (`SUM`, `MAX`, `MIN`) produce `NaN` +for the first `timeperiod - 1` bars. + + +| TA-Lib Function | ferro-ta | Notes | +| ------------------------ | -------- | ----------------------------- | +| `ADD` | βœ… | Element-wise addition | +| `SUB` | βœ… | Element-wise subtraction | +| `MULT` | βœ… | Element-wise multiplication | +| `DIV` | βœ… | Element-wise division | +| `SUM` | βœ… | Rolling sum over *timeperiod* | +| `MAX` / `MAXINDEX` | βœ… | Rolling maximum / index | +| `MIN` / `MININDEX` | βœ… | Rolling minimum / index | +| `ACOS` / `ASIN` / `ATAN` | βœ… | Arc trig transforms | +| `CEIL` / `FLOOR` | βœ… | Round up / down | +| `COS` / `SIN` / `TAN` | βœ… | Trig transforms | +| `COSH` / `SINH` / `TANH` | βœ… | Hyperbolic transforms | +| `EXP` / `LN` / `LOG10` | βœ… | Exponential / log transforms | +| `SQRT` | βœ… | Square root | + + +## Implementation Coverage Summary + + +| Category | Implemented | Not Implemented | +| --------------------------- | ----------- | --------------- | +| Overlap Studies | 19 | 0 | +| Momentum Indicators | 28 | 0 | +| Volume Indicators | 3 | 0 | +| Volatility Indicators | 3 | 0 | +| Cycle Indicators | 6 | 0 | +| Price Transforms | 4 | 0 | +| Statistic Functions | 9 | 0 | +| Pattern Recognition | 61 | 0 | +| Math Operators / Transforms | 24 | 0 | +| Extended Indicators | 10 | - | +| Streaming Classes | 9 | - | +| **Total** | **162+** | **0** | + + +> `ferro-ta` implements 100% of TA-Lib's function set. NaN values are placed +> at the beginning of each output array for the warmup period. + diff --git a/docs/adjacent_tooling.rst b/docs/adjacent_tooling.rst index 838c878..c7d6688 100644 --- a/docs/adjacent_tooling.rst +++ b/docs/adjacent_tooling.rst @@ -20,7 +20,8 @@ a Python technical analysis library. `docs/agentic.md `_. * - MCP server - Experimental or adjacent - - Exposes selected ferro-ta capabilities to MCP-compatible clients. See + - FastMCP-based server exposing selected ferro-ta capabilities to + MCP-compatible clients. See `docs/mcp.md `_. * - WASM package - Experimental diff --git a/docs/agentic.md b/docs/agentic.md index e5ea996..20e5a94 100644 --- a/docs/agentic.md +++ b/docs/agentic.md @@ -198,6 +198,6 @@ while True: - `ferro_ta.tools` β€” module source. - `ferro_ta.workflow` β€” module source. -- `docs/mcp.md` β€” MCP server for Cursor/Claude integration. +- `docs/mcp.md` β€” MCP server for MCP-compatible clients. - `ferro_ta.backtest` β€” backtest harness. - `ferro_ta.registry` β€” indicator registry. diff --git a/docs/index.rst b/docs/index.rst index e02f952..be643c5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -61,7 +61,7 @@ Adjacent and experimental tooling: - Derivatives analytics β€” see :doc:`derivatives` - Agentic workflow and LangChain tool wrappers β€” see `Agentic guide `_ -- MCP server for Cursor/Claude integration β€” see `MCP guide `_ +- MCP server for MCP-compatible clients β€” see `MCP guide `_ - WASM, plugins, and other optional surfaces β€” see :doc:`adjacent_tooling` Installation diff --git a/docs/mcp.md b/docs/mcp.md index 5ae7660..35ff982 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1,42 +1,56 @@ -# MCP Server β€” Connect ferro-ta in Cursor +# MCP Server -ferro-ta ships an MCP (Model Context Protocol) server that exposes -indicators and backtest tools to AI agents. This guide shows how to run -the server and connect it to Cursor or any MCP-compatible client. +ferro-ta ships an optional MCP (Model Context Protocol) server built on the +official Python SDK's FastMCP layer. The server now exposes the broad public +ferro-ta callable surface instead of a tiny hand-picked subset. + +That means MCP clients can use: + +- Exact top-level ferro-ta exports such as `SMA`, `RSI`, `MACD`, `about`, + `methods`, `info`, `benchmark`, and `traced` +- Non-top-level public tools such as `compute_indicator`, `run_backtest`, + `check_cross`, `aggregate_ticks`, `TickAggregator`, and `AlertManager` +- Legacy lowercase convenience aliases: `sma`, `ema`, `rsi`, `macd`, + and `backtest` +- Generic instance tools for stateful classes and stored callables: + `list_instances`, `describe_instance`, `call_instance_method`, + `call_stored_callable`, and `delete_instance` --- ## Installation -The MCP server requires no additional dependencies beyond ferro_ta itself. -For the full MCP SDK integration (recommended), install the optional extra: +Install the optional MCP extra: ```bash pip install "ferro-ta[mcp]" ``` -or install the `mcp` package separately: +If you are working from this repository, you can install the same extra into +the project environment with: ```bash -pip install "mcp>=1.0" +uv sync --extra mcp ``` --- ## Running the server +Run the server over stdio: + ```bash python -m ferro_ta.mcp ``` -The server listens on stdin/stdout using JSON-RPC 2.0 (the MCP protocol). +The command exits immediately with an install hint if the optional `mcp` +dependency is missing. --- ## Connect in Cursor -1. Open Cursor settings (Command Palette β†’ "Open User Settings (JSON)"). -2. Find or create the `mcpServers` section: +Add the server to Cursor's MCP settings: ```json { @@ -44,82 +58,134 @@ The server listens on stdin/stdout using JSON-RPC 2.0 (the MCP protocol). "ferro-ta": { "command": "python", "args": ["-m", "ferro_ta.mcp"], - "description": "ferro_ta β€” Technical Analysis MCP server" + "description": "ferro-ta technical analysis tools" } } } ``` -3. Reload Cursor (Command Palette β†’ "Developer: Reload Window"). -4. The ferro-ta tools will appear in the Tools panel. +You can place this in your user settings JSON or in a workspace-level +`.cursor/mcp.json`. -### Workspace-level config +--- -You can also add the config to your project's `.cursor/mcp.json`: +## Tool naming + +The MCP server prefers the real ferro-ta API names. + +- Use exact public names when possible, for example `SMA`, `MACD`, + `compute_indicator`, `trade_stats`, `TickAggregator`, or `AlertManager` +- Use the legacy lowercase aliases only when you want the old MCP-friendly + shortcuts and result shapes +- Use `about`, `methods`, `indicators`, and `info` to discover what is + available from inside an MCP client + +--- + +## Stateful classes and object references + +Class tools return stored object references instead of plain text placeholders. +For example, calling `TickAggregator` or `AlertManager` returns a payload like: ```json { - "mcpServers": { - "ferro-ta": { - "command": "python", - "args": ["-m", "ferro_ta.mcp"] - } - } + "instance_id": "tickaggregator-0001", + "type": "ferro_ta.data.aggregation.TickAggregator", + "repr": "TickAggregator(rule='tick:2')" } ``` +Use that `instance_id` with: + +- `describe_instance` to inspect the stored object and list public methods +- `call_instance_method` to call methods like `aggregate`, `update`, + `run_backtest`, or `to_dict` +- `delete_instance` to remove stored objects when you are done + +If a tool returns a stored callable, use `call_stored_callable`. + +--- + +## Callable references + +Some ferro-ta APIs accept other callables, for example `benchmark`, +`log_call`, `traced`, or `multi_timeframe(indicator=...)`. + +Pass public ferro-ta callables using: + +```json +{"callable": "SMA"} +``` + +Pass stored objects using: + +```json +{"instance_id": "function-0001"} +``` + --- ## Example prompts -Once connected, you can ask Claude (or any MCP-enabled AI) things like: +Once connected, you can ask an MCP-compatible client things like: -> "Compute RSI(14) on this price series: [100, 102, 101, 105, 108, 104, 107]" +> "Run `SMA` with `close=[100, 101, 102, 103, 104]` and `timeperiod=3`." -> "Run a backtest with the rsi_30_70 strategy on [100, 101, 99, 103, 106, 102, 108, 105, 109, 112, 108, 111]" +> "Use `compute_indicator` to calculate `MACD` for this close series." -> "List all available ferro_ta indicators" +> "Call `about` and summarize the current ferro-ta API surface." -> "What does the SMA indicator do?" +> "Create a `TickAggregator` with `rule='tick:50'`, aggregate this tick data, +> then delete the instance." + +> "Benchmark `SMA` over this price series using a callable reference." --- -## Available tools +## Programmatic use -| Tool | Description | -|------|-------------| -| `sma` | Simple Moving Average | -| `ema` | Exponential Moving Average | -| `rsi` | Relative Strength Index | -| `macd` | MACD line, signal, histogram | -| `backtest` | Vectorized backtest (rsi_30_70, sma_crossover, macd_crossover) | -| `list_indicators` | List all registered indicators | -| `describe_indicator` | Describe a named indicator | - ---- - -## Programmatic use (Python client) - -You can also use the MCP handlers directly in Python without the server: +Use the server entrypoint: ```python -from ferro_ta.mcp import handle_list_tools, handle_call_tool -import numpy as np +from ferro_ta.mcp import create_server + +server = create_server() +# server.run(transport="stdio") +``` + +Or call the handlers directly without starting the server: + +```python +from ferro_ta.mcp import handle_call_tool, handle_list_tools +import json -# List tools tools = handle_list_tools() -print([t["name"] for t in tools["tools"]]) +print(len(tools["tools"])) -# Call RSI -close = list(np.cumprod(1 + np.random.default_rng(0).normal(0, 0.01, 50)) * 100) -result = handle_call_tool("rsi", {"close": close, "timeperiod": 14}) -print(result) +close = [100, 101, 102, 103, 104] +result = handle_call_tool("SMA", {"close": close, "timeperiod": 3}) +print(json.loads(result["content"][0]["text"])) + +aggregator = json.loads( + handle_call_tool("TickAggregator", {"rule": "tick:2"})["content"][0]["text"] +) +bars = handle_call_tool( + "call_instance_method", + { + "instance_id": aggregator["instance_id"], + "method": "aggregate", + "args": [{"price": [1, 2, 3, 4], "size": [1, 1, 1, 1]}], + }, +) +print(json.loads(bars["content"][0]["text"])) ``` --- ## See also -- `ferro_ta.mcp` β€” module source. -- `ferro_ta.tools` β€” underlying tool functions. -- `docs/agentic.md` β€” LangChain and workflow integration. +- `python -m ferro_ta.mcp` - stdio MCP entrypoint +- `ferro_ta.mcp.create_server()` - FastMCP server factory +- `ferro_ta.tools.api_info` - API discovery helpers used by the MCP catalog +- `ferro_ta.tools` - stable wrappers such as `compute_indicator` +- `docs/agentic.md` - workflow and agent integration notes diff --git a/python/ferro_ta/mcp/__init__.py b/python/ferro_ta/mcp/__init__.py index 6b4476e..459391e 100644 --- a/python/ferro_ta/mcp/__init__.py +++ b/python/ferro_ta/mcp/__init__.py @@ -1,422 +1,1324 @@ -""" -ferro_ta.mcp β€” Model Context Protocol (MCP) Server -================================================== - -An MCP server that exposes ferro_ta indicators and backtest tools to -AI agents (e.g. Claude in Cursor, LangChain, OpenAI function calling). - -Running the server ------------------- -Start the server directly:: - - python -m ferro_ta.mcp - -Or with ``uvicorn`` / ``mcp`` runner if the official MCP SDK is installed:: - - uvicorn ferro_ta.mcp:app --port 8765 - -Cursor integration ------------------- -Add the following to your Cursor MCP settings -(``~/.cursor/mcp.json`` or workspace ``.cursor/mcp.json``):: - - { - "mcpServers": { - "ferro-ta": { - "command": "python", - "args": ["-m", "ferro_ta.mcp"], - "description": "ferro_ta technical analysis tools" - } - } - } - -After reloading Cursor, you can ask the AI assistant things like: - -* "Compute SMA(14) on this price series: [100, 102, ...]" -* "Run a backtest with RSI 30/70 strategy on this data" -* "list all available indicators" - -See ``docs/mcp.md`` for the full guide. - -Install optional dependency ---------------------------- -The MCP server requires the ``mcp`` SDK:: - - pip install ferro-ta[mcp] - -or:: - - pip install "mcp>=1.0" - -Tools exposed -------------- -* ``sma`` β€” Simple Moving Average -* ``ema`` β€” Exponential Moving Average -* ``rsi`` β€” Relative Strength Index -* ``macd`` β€” MACD line, signal, histogram -* ``backtest`` β€” Run a vectorized backtest -* ``list_indicators``β€” list all registered indicators -* ``describe_indicator`` β€” Describe an indicator -""" +"""Optional MCP server exposing the public ferro-ta API.""" from __future__ import annotations +import dataclasses +import enum +import importlib +import inspect import json -import sys -from typing import Any +import re +from collections.abc import Callable, Mapping +from datetime import date, datetime, time +from functools import lru_cache +from itertools import count +from typing import Any, get_args, get_origin, get_type_hints import numpy as np -import ferro_ta as ft -from ferro_ta.tools import ( - compute_indicator, - describe_indicator, - list_indicators, - run_backtest, +import ferro_ta +from ferro_ta.tools import compute_indicator, run_backtest +from ferro_ta.tools.api_info import methods as api_methods + +__all__ = ["create_server", "run_server", "handle_list_tools", "handle_call_tool"] + +_MCP_INSTALL_HINT = ( + "The ferro-ta MCP server requires the optional 'mcp' dependency. " + 'Install it with `pip install "ferro-ta[mcp]"` or `uv sync --extra mcp`.' ) -__all__ = ["run_server", "handle_list_tools", "handle_call_tool"] +_REFERENCE_HELP = ( + "Use {'instance_id': ''} for stored objects or " + "{'callable': ''} for public callables." +) -# --------------------------------------------------------------------------- -# Tool definitions (JSON-schema style) -# --------------------------------------------------------------------------- - -_TOOLS: list[dict[str, Any]] = [ - { - "name": "sma", - "description": "Compute the Simple Moving Average (SMA) of a price series.", - "inputSchema": { - "type": "object", - "properties": { - "close": { - "type": "array", - "items": {"type": "number"}, - "description": "Close price series.", - }, - "timeperiod": { - "type": "integer", - "description": "Look-back period (default 14).", - "default": 14, - }, - }, - "required": ["close"], - }, - }, - { - "name": "ema", - "description": "Compute the Exponential Moving Average (EMA) of a price series.", - "inputSchema": { - "type": "object", - "properties": { - "close": { - "type": "array", - "items": {"type": "number"}, - "description": "Close price series.", - }, - "timeperiod": { - "type": "integer", - "description": "Look-back period (default 14).", - "default": 14, - }, - }, - "required": ["close"], - }, - }, - { - "name": "rsi", - "description": "Compute the Relative Strength Index (RSI) of a price series.", - "inputSchema": { - "type": "object", - "properties": { - "close": { - "type": "array", - "items": {"type": "number"}, - "description": "Close price series.", - }, - "timeperiod": { - "type": "integer", - "description": "Look-back period (default 14).", - "default": 14, - }, - }, - "required": ["close"], - }, - }, - { - "name": "macd", - "description": ( - "Compute MACD (Moving Average Convergence/Divergence). " - "Returns macd line, signal line, and histogram." - ), - "inputSchema": { - "type": "object", - "properties": { - "close": { - "type": "array", - "items": {"type": "number"}, - "description": "Close price series.", - }, - "fastperiod": { - "type": "integer", - "description": "Fast EMA period (default 12).", - "default": 12, - }, - "slowperiod": { - "type": "integer", - "description": "Slow EMA period (default 26).", - "default": 26, - }, - "signalperiod": { - "type": "integer", - "description": "Signal EMA period (default 9).", - "default": 9, - }, - }, - "required": ["close"], - }, - }, - { - "name": "backtest", - "description": ( - "Run a vectorized backtest on close prices using a named strategy. " - "Returns final equity, number of trades, and the equity curve." - ), - "inputSchema": { - "type": "object", - "properties": { - "close": { - "type": "array", - "items": {"type": "number"}, - "description": "Close price series (at least 2 bars).", - }, - "strategy": { - "type": "string", - "description": ( - "Strategy name: 'rsi_30_70', 'sma_crossover', or 'macd_crossover'." - ), - "default": "rsi_30_70", - }, - "commission_per_trade": { - "type": "number", - "description": "Fixed commission per trade (default 0).", - "default": 0.0, - }, - "slippage_bps": { - "type": "number", - "description": "Slippage in basis points (default 0).", - "default": 0.0, - }, - }, - "required": ["close"], - }, - }, - { - "name": "list_indicators", - "description": "list all available indicator names registered in ferro_ta.", - "inputSchema": { - "type": "object", - "properties": {}, - "required": [], - }, - }, - { - "name": "describe_indicator", - "description": "Return a description of a named ferro_ta indicator.", - "inputSchema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Indicator name (e.g. 'SMA', 'RSI', 'BBANDS').", - } - }, - "required": ["name"], - }, - }, +_JSON_ANY_TYPE: list[str] = [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string", ] +_NO_DEFAULT = object() +_INSTANCE_STORE: dict[str, Any] = {} +_INSTANCE_META: dict[str, dict[str, Any]] = {} +_INSTANCE_COUNTER = count(1) -# --------------------------------------------------------------------------- -# Tool handlers -# --------------------------------------------------------------------------- + +@dataclasses.dataclass(frozen=True) +class _ToolSpec: + """Resolved metadata and dispatcher for a single MCP tool.""" + + name: str + description: str + input_schema: dict[str, Any] + wrapper_signature: inspect.Signature + invoke: Callable[[dict[str, Any]], Any] + + +def _import_object(module_name: str, name: str) -> Any: + """Import *name* from *module_name*.""" + module = importlib.import_module(module_name) + return getattr(module, name) + + +@lru_cache(maxsize=1) +def _discover_public_callables() -> tuple[list[dict[str, str]], dict[str, Any]]: + """Return canonical public callable metadata and lookup targets.""" + entries = api_methods() + top_level = sorted( + [item for item in entries if item["category"] == "top_level"], + key=lambda item: item["name"], + ) + top_level_names = {item["name"] for item in top_level} + extras = sorted( + [ + item + for item in entries + if item["category"] != "top_level" and item["name"] not in top_level_names + ], + key=lambda item: item["name"], + ) + + public_entries: list[dict[str, str]] = [] + targets: dict[str, Any] = {} + for item in [*top_level, *extras]: + name = item["name"] + if name in targets: + continue + targets[name] = _import_object(item["module"], name) + public_entries.append(item) + + return public_entries, targets + + +def _slugify(value: str) -> str: + """Convert *value* into a short identifier fragment.""" + slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") + return slug or "object" + + +def _instance_ref(value: Any, *, source_tool: str) -> dict[str, Any]: + """Store *value* and return a serialisable reference payload.""" + identifier = f"{_slugify(type(value).__name__)}-{next(_INSTANCE_COUNTER):04d}" + _INSTANCE_STORE[identifier] = value + payload = { + "instance_id": identifier, + "type": f"{type(value).__module__}.{type(value).__name__}", + "repr": repr(value), + "callable": callable(value), + "source_tool": source_tool, + } + snapshot = _object_snapshot(value) + if snapshot is not None: + payload["snapshot"] = snapshot + _INSTANCE_META[identifier] = payload + return payload + + +def _get_instance(identifier: str) -> Any: + """Return the stored instance for *identifier* or raise a clear error.""" + try: + return _INSTANCE_STORE[identifier] + except KeyError as exc: + raise KeyError(f"Unknown instance_id: {identifier!r}") from exc + + +def _is_instance_ref(value: Any) -> bool: + """Return whether *value* is a stored-object reference payload.""" + return isinstance(value, dict) and set(value) == {"instance_id"} + + +def _is_callable_ref(value: Any) -> bool: + """Return whether *value* is a public-callable reference payload.""" + return isinstance(value, dict) and set(value) == {"callable"} + + +def _public_method_summaries(value: Any) -> list[dict[str, str]]: + """Return public callable methods for *value*.""" + result: list[dict[str, str]] = [] + for method_name, member in inspect.getmembers(value): + if method_name.startswith("_") or not callable(member): + continue + try: + signature = str(inspect.signature(member)) + except (TypeError, ValueError): + signature = "()" + result.append({"name": method_name, "signature": signature}) + return result + + +def _object_snapshot(value: Any) -> Any: + """Return a serialisable snapshot for common non-primitive objects.""" + if isinstance(value, enum.Enum): + return value.value + + if dataclasses.is_dataclass(value): + return _normalise_json(dataclasses.asdict(value), store_objects=False) + + if hasattr(value, "to_dict") and callable(value.to_dict): + try: + return _normalise_json(value.to_dict(), store_objects=False) + except TypeError: + if value.__class__.__module__.startswith("pandas"): + return _normalise_json(value.to_dict(orient="list"), store_objects=False) + if value.__class__.__module__.startswith("polars"): + return _normalise_json( + value.to_dict(as_series=False), store_objects=False + ) + except Exception: + return None + + if hasattr(value, "__dict__"): + fields = { + key: val + for key, val in vars(value).items() + if not key.startswith("_") and not callable(val) + } + if fields: + return _normalise_json(fields, store_objects=False) + + slots = getattr(type(value), "__slots__", ()) + if slots: + fields = {} + for slot in slots: + if slot.startswith("_") or not hasattr(value, slot): + continue + slot_value = getattr(value, slot) + if callable(slot_value): + continue + fields[slot] = slot_value + if fields: + return _normalise_json(fields, store_objects=False) + + return None + + +def _normalise_json(value: Any, *, store_objects: bool = True) -> Any: + """Convert Python and numpy-rich values into JSON-safe values.""" + if value is None or isinstance(value, (str, bool)): + return value + + if isinstance(value, (int, np.integer)): + return int(value) + + if isinstance(value, (float, np.floating)): + return None if np.isnan(value) else float(value) + + if isinstance(value, (date, datetime, time)): + return value.isoformat() + + if isinstance(value, enum.Enum): + return _normalise_json(value.value, store_objects=store_objects) + + if isinstance(value, np.ndarray): + return [_normalise_json(item, store_objects=store_objects) for item in value.tolist()] + + if isinstance(value, np.generic): + return _normalise_json(value.item(), store_objects=store_objects) + + if isinstance(value, Mapping): + return { + str(key): _normalise_json(item, store_objects=store_objects) + for key, item in value.items() + } + + if isinstance(value, (list, tuple, set, frozenset)): + return [_normalise_json(item, store_objects=store_objects) for item in value] + + if hasattr(value, "tolist") and callable(value.tolist): + try: + return _normalise_json(value.tolist(), store_objects=store_objects) + except Exception: + pass + + snapshot = _object_snapshot(value) + if snapshot is not None: + return snapshot + + if store_objects: + return _instance_ref(value, source_tool="return_value") + + return repr(value) + + +def _json_result( + payload: Any, + *, + structured_key: str | None = None, +) -> dict[str, Any]: + """Wrap *payload* in the helper response shape.""" + structured: dict[str, Any] | None = None + if isinstance(payload, dict): + structured = payload + elif structured_key is not None: + structured = {structured_key: payload} + + result: dict[str, Any] = { + "content": [{"type": "text", "text": json.dumps(payload)}], + } + if structured is not None: + result["structuredContent"] = structured + return result + + +def _text_result( + text: str, + *, + structured: dict[str, Any] | None = None, + is_error: bool = False, +) -> dict[str, Any]: + """Wrap *text* in the helper response shape.""" + result: dict[str, Any] = { + "content": [{"type": "text", "text": text}], + } + if structured is not None: + result["structuredContent"] = structured + if is_error: + result["isError"] = True + return result + + +def _response_from_payload(payload: Any) -> dict[str, Any]: + """Create a helper response from a normalised payload.""" + if isinstance(payload, str): + return _text_result(payload, structured={"value": payload}) + return _json_result(payload) + + +def _load_mcp_sdk() -> tuple[type[Any], type[Any], type[Any]]: + """Import the optional MCP SDK lazily.""" + try: + fastmcp = importlib.import_module("mcp.server.fastmcp") + types = importlib.import_module("mcp.types") + except ImportError as exc: # pragma: no cover - exercised via tests + raise RuntimeError(_MCP_INSTALL_HINT) from exc + + return fastmcp.FastMCP, types.CallToolResult, types.TextContent + + +def _to_call_tool_result(result: dict[str, Any]) -> Any: + """Convert helper-style results into an MCP SDK CallToolResult.""" + _, call_tool_result_type, text_content_type = _load_mcp_sdk() + content = [ + text_content_type(type="text", text=item["text"]) + for item in result.get("content", []) + if item.get("type") == "text" + ] + return call_tool_result_type( + content=content, + structuredContent=result.get("structuredContent"), + isError=result.get("isError", False), + ) + + +def _safe_default(value: Any) -> Any: + """Return a JSON-safe schema default or a sentinel when unavailable.""" + if value is inspect._empty: + return _NO_DEFAULT + if isinstance(value, enum.Enum): + return value.value + if isinstance(value, (str, bool, int, float)) or value is None: + return value + if isinstance(value, tuple) and all( + isinstance(item, (str, bool, int, float)) or item is None for item in value + ): + return list(value) + if isinstance(value, list) and all( + isinstance(item, (str, bool, int, float)) or item is None for item in value + ): + return value + return _NO_DEFAULT + + +def _wrapper_default(value: Any) -> Any: + """Return a lightweight default for generated wrapper signatures.""" + default = _safe_default(value) + if default is _NO_DEFAULT: + return None + return default + + +def _annotation_label(annotation: Any) -> str: + """Return a readable label for *annotation*.""" + if annotation is inspect._empty: + return "Any" + if isinstance(annotation, str): + return annotation + origin = get_origin(annotation) + if origin is not None: + return str(annotation).replace("typing.", "") + return getattr(annotation, "__name__", repr(annotation)) + + +def _annotation_options(annotation: Any) -> list[Any]: + """Flatten simple union annotations into a list of options.""" + if annotation is inspect._empty: + return [Any] + + origin = get_origin(annotation) + if origin in (None,): + return [annotation] + + if origin in (list, tuple, dict): + return [annotation] + + if origin in (Callable,): + return [annotation] + + args = [arg for arg in get_args(annotation) if arg is not type(None)] + return args or [annotation] + + +def _is_enum_annotation(annotation: Any) -> type[enum.Enum] | None: + """Return the enum class inside *annotation*, if any.""" + for option in _annotation_options(annotation): + if inspect.isclass(option) and issubclass(option, enum.Enum): + return option + return None + + +def _annotation_includes_callable(annotation: Any) -> bool: + """Return whether *annotation* includes a callable type.""" + label = _annotation_label(annotation) + if "Callable" in label: + return True + for option in _annotation_options(annotation): + origin = get_origin(option) + if origin in (Callable,): + return True + return False + + +def _annotation_includes_custom_class(annotation: Any) -> bool: + """Return whether *annotation* includes a non-builtin class.""" + for option in _annotation_options(annotation): + if not inspect.isclass(option): + continue + if issubclass(option, enum.Enum): + return True + if option.__module__ == "builtins": + continue + if option in (date, datetime, time): + continue + return True + return False + + +def _schema_and_py_type(annotation: Any, *, param_name: str) -> tuple[dict[str, Any], Any]: + """Map Python annotations to JSON Schema and wrapper annotations.""" + enum_type = _is_enum_annotation(annotation) + if enum_type is not None: + raw_values = [member.value for member in enum_type] + if all(isinstance(item, str) for item in raw_values): + schema = {"type": "string", "enum": list(raw_values)} + return schema, str + if all(isinstance(item, int) for item in raw_values): + schema = {"type": "integer", "enum": list(raw_values)} + return schema, int + + label = _annotation_label(annotation) + lower = label.lower() + + if "bool" in lower: + return {"type": "boolean"}, bool + if "int" in lower and "point" not in lower: + return {"type": "integer"}, int + if ( + "float" in lower + or "number" in lower + or "scalar" in lower + or "ndarray" in lower + or "arraylike" in lower + ): + if "scalarorarray" in lower: + return { + "type": _JSON_ANY_TYPE, + "description": f"Parameter `{param_name}`. { _REFERENCE_HELP }", + }, Any + if "ndarray" in lower or "arraylike" in lower: + return {"type": "array", "items": {}}, list[Any] + return {"type": "number"}, float + if "list" in lower or "tuple" in lower or "sequence" in lower or "iterable" in lower: + return {"type": "array", "items": {}}, list[Any] + if "dict" in lower or "mapping" in lower: + return {"type": "object"}, dict[str, Any] + if "str" in lower or "date" in lower or "datetime" in lower or "time" in lower: + return {"type": "string"}, str + if _annotation_includes_callable(annotation) or _annotation_includes_custom_class(annotation): + return { + "type": _JSON_ANY_TYPE, + "description": f"Parameter `{param_name}`. {_REFERENCE_HELP}", + }, Any + return {"type": _JSON_ANY_TYPE}, Any + + +def _build_signature_and_schema( + signature: inspect.Signature, + *, + type_hints: dict[str, Any], +) -> tuple[inspect.Signature, dict[str, Any]]: + """Build a wrapper signature and JSON schema for *signature*.""" + wrapper_parameters: list[inspect.Parameter] = [] + properties: dict[str, Any] = {} + required: list[str] = [] + + for parameter in signature.parameters.values(): + if parameter.name in {"self", "cls"}: + continue + + if parameter.kind == inspect.Parameter.VAR_POSITIONAL: + properties["args"] = { + "type": "array", + "items": {}, + "description": "Extra positional arguments.", + } + wrapper_parameters.append( + inspect.Parameter( + "args", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[Any], + default=None, + ) + ) + continue + + if parameter.kind == inspect.Parameter.VAR_KEYWORD: + properties["kwargs"] = { + "type": "object", + "description": "Extra keyword arguments.", + } + wrapper_parameters.append( + inspect.Parameter( + "kwargs", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=dict[str, Any], + default=None, + ) + ) + continue + + annotation = type_hints.get(parameter.name, parameter.annotation) + schema, py_type = _schema_and_py_type(annotation, param_name=parameter.name) + default = _safe_default(parameter.default) + if default is not _NO_DEFAULT: + schema["default"] = default + else: + required.append(parameter.name) + + properties[parameter.name] = schema + wrapper_parameters.append( + inspect.Parameter( + parameter.name, + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=py_type, + default=( + inspect._empty + if parameter.default is inspect._empty + else _wrapper_default(parameter.default) + ), + ) + ) + + wrapper_signature = inspect.Signature(parameters=wrapper_parameters) + input_schema: dict[str, Any] = { + "type": "object", + "properties": properties, + "required": required, + "additionalProperties": False, + } + return wrapper_signature, input_schema + + +def _type_hints_for_target(target: Any) -> dict[str, Any]: + """Return best-effort type hints for *target*.""" + hinted = target.__init__ if inspect.isclass(target) else target + try: + return get_type_hints(hinted, include_extras=True) + except Exception: + return {} + + +def _resolve_public_callable(name: str) -> Any: + """Resolve a public ferro-ta callable by its exposed MCP name.""" + _, targets = _discover_public_callables() + if name in targets: + return targets[name] + aliases = { + "sma": ferro_ta.SMA, + "ema": ferro_ta.EMA, + "rsi": ferro_ta.RSI, + "macd": ferro_ta.MACD, + "backtest": run_backtest, + } + try: + return aliases[name] + except KeyError as exc: + raise KeyError(f"Unknown callable reference: {name!r}") from exc + + +def _coerce_enum(value: Any, enum_type: type[enum.Enum]) -> enum.Enum: + """Convert *value* into *enum_type*.""" + if isinstance(value, enum_type): + return value + if isinstance(value, str): + if value in enum_type.__members__: + return enum_type[value] + for member in enum_type: + if member.value == value: + return member + return enum_type(value) + + +def _decode_value(value: Any, annotation: Any = Any) -> Any: + """Resolve stored-object and callable references inside *value*.""" + if _is_instance_ref(value): + return _get_instance(str(value["instance_id"])) + + if _is_callable_ref(value): + return _resolve_public_callable(str(value["callable"])) + + enum_type = _is_enum_annotation(annotation) + if enum_type is not None: + try: + return _coerce_enum(value, enum_type) + except Exception: + pass + + if _annotation_includes_callable(annotation) and isinstance(value, str): + try: + return _resolve_public_callable(value) + except KeyError: + pass + + if isinstance(value, list): + return [_decode_value(item, Any) for item in value] + + if isinstance(value, tuple): + return tuple(_decode_value(item, Any) for item in value) + + if isinstance(value, dict): + return {str(key): _decode_value(item, Any) for key, item in value.items()} + + return value + + +def _invoke_target( + target: Any, + *, + signature: inspect.Signature, + type_hints: dict[str, Any], + arguments: dict[str, Any], +) -> Any: + """Call *target* with decoded arguments.""" + positional_args: list[Any] = [] + keyword_args: dict[str, Any] = {} + has_varargs = any( + parameter.kind == inspect.Parameter.VAR_POSITIONAL + for parameter in signature.parameters.values() + ) + before_varargs = True + + for parameter in signature.parameters.values(): + if parameter.name in {"self", "cls"}: + continue + + annotation = type_hints.get(parameter.name, parameter.annotation) + + if parameter.kind == inspect.Parameter.VAR_POSITIONAL: + before_varargs = False + extra_args = arguments.get("args", []) or [] + positional_args.extend(_decode_value(item, Any) for item in extra_args) + continue + + if parameter.kind == inspect.Parameter.VAR_KEYWORD: + extra_kwargs = arguments.get("kwargs", {}) or {} + if not isinstance(extra_kwargs, dict): + raise TypeError("kwargs must be a JSON object") + keyword_args.update( + {str(key): _decode_value(item, Any) for key, item in extra_kwargs.items()} + ) + continue + + expects_positional = ( + before_varargs + and has_varargs + and parameter.kind + in ( + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + ) + ) + + if parameter.name in arguments: + decoded = _decode_value(arguments[parameter.name], annotation) + elif parameter.default is not inspect._empty: + if expects_positional: + decoded = parameter.default + else: + continue + else: + raise KeyError(f"Missing required argument: {parameter.name}") + + if expects_positional or parameter.kind == inspect.Parameter.POSITIONAL_ONLY: + positional_args.append(decoded) + else: + keyword_args[parameter.name] = decoded + + return target(*positional_args, **keyword_args) + + +def _describe_instance_payload(identifier: str) -> dict[str, Any]: + """Return a detailed description for a stored instance.""" + value = _get_instance(identifier) + payload = dict(_INSTANCE_META.get(identifier, {})) + payload.update( + { + "instance_id": identifier, + "module": type(value).__module__, + "class_name": type(value).__name__, + "methods": _public_method_summaries(value), + } + ) + return payload + + +def _list_instances_payload() -> list[dict[str, Any]]: + """Return current stored-object metadata.""" + return [ + _describe_instance_payload(identifier) + for identifier in sorted(_INSTANCE_STORE) + ] + + +def _build_public_tool_spec(item: dict[str, str], target: Any) -> _ToolSpec: + """Create a generated tool spec for a public ferro-ta callable.""" + is_enum = inspect.isclass(target) and issubclass(target, enum.Enum) + type_hints = _type_hints_for_target(target) + + if is_enum: + signature = inspect.Signature( + [ + inspect.Parameter( + "value", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default=inspect._empty, + ) + ] + ) + wrapper_signature, input_schema = _build_signature_and_schema( + signature, + type_hints={"value": str}, + ) + description = ( + item["doc"] + or f"Construct a {item['name']} enum member. Returns a stored instance reference." + ) + + def invoke(arguments: dict[str, Any], *, enum_type: type[enum.Enum] = target) -> Any: + if "value" not in arguments: + raise KeyError("Missing required argument: value") + member = _coerce_enum(arguments["value"], enum_type) + return _instance_ref(member, source_tool=item["name"]) + + return _ToolSpec( + name=item["name"], + description=description, + input_schema=input_schema, + wrapper_signature=wrapper_signature, + invoke=invoke, + ) + + signature = inspect.signature(target) + wrapper_signature, input_schema = _build_signature_and_schema( + signature, + type_hints=type_hints, + ) + is_class = inspect.isclass(target) + description = item["doc"] or f"Call {item['name']}." + if is_class: + description = ( + item["doc"] + or f"Construct a {item['name']} instance. Returns a stored instance reference." + ) + + def invoke( + arguments: dict[str, Any], + *, + raw_target: Any = target, + raw_signature: inspect.Signature = signature, + raw_type_hints: dict[str, Any] = type_hints, + returns_instance: bool = is_class, + source_tool: str = item["name"], + ) -> Any: + result = _invoke_target( + raw_target, + signature=raw_signature, + type_hints=raw_type_hints, + arguments=arguments, + ) + if returns_instance: + return _instance_ref(result, source_tool=source_tool) + return _normalise_json(result) + + return _ToolSpec( + name=item["name"], + description=description, + input_schema=input_schema, + wrapper_signature=wrapper_signature, + invoke=invoke, + ) + + +def _legacy_series_tool( + name: str, + *, + indicator_name: str, + description: str, +) -> _ToolSpec: + """Return a legacy lowercase indicator alias tool.""" + wrapper_signature = inspect.Signature( + [ + inspect.Parameter( + "close", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[float], + default=inspect._empty, + ), + inspect.Parameter( + "timeperiod", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=int, + default=14, + ), + ] + ) + input_schema = { + "type": "object", + "properties": { + "close": { + "type": "array", + "items": {"type": "number"}, + "description": "Close price series.", + }, + "timeperiod": { + "type": "integer", + "description": "Look-back period.", + "default": 14, + }, + }, + "required": ["close"], + "additionalProperties": False, + } + + def invoke(arguments: dict[str, Any]) -> Any: + close = np.asarray(arguments["close"], dtype=np.float64) + timeperiod = int(arguments.get("timeperiod", 14)) + return _normalise_json( + compute_indicator(indicator_name, close, timeperiod=timeperiod) + ) + + return _ToolSpec( + name=name, + description=description, + input_schema=input_schema, + wrapper_signature=wrapper_signature, + invoke=invoke, + ) + + +def _legacy_macd_tool() -> _ToolSpec: + """Return the legacy lowercase MACD alias tool.""" + wrapper_signature = inspect.Signature( + [ + inspect.Parameter( + "close", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[float], + default=inspect._empty, + ), + inspect.Parameter( + "fastperiod", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=int, + default=12, + ), + inspect.Parameter( + "slowperiod", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=int, + default=26, + ), + inspect.Parameter( + "signalperiod", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=int, + default=9, + ), + ] + ) + input_schema = { + "type": "object", + "properties": { + "close": { + "type": "array", + "items": {"type": "number"}, + "description": "Close price series.", + }, + "fastperiod": { + "type": "integer", + "description": "Fast EMA period.", + "default": 12, + }, + "slowperiod": { + "type": "integer", + "description": "Slow EMA period.", + "default": 26, + }, + "signalperiod": { + "type": "integer", + "description": "Signal EMA period.", + "default": 9, + }, + }, + "required": ["close"], + "additionalProperties": False, + } + + def invoke(arguments: dict[str, Any]) -> Any: + close = np.asarray(arguments["close"], dtype=np.float64) + result = compute_indicator( + "MACD", + close, + fastperiod=int(arguments.get("fastperiod", 12)), + slowperiod=int(arguments.get("slowperiod", 26)), + signalperiod=int(arguments.get("signalperiod", 9)), + ) + return _normalise_json(result) + + return _ToolSpec( + name="macd", + description=( + "Compute MACD (Moving Average Convergence/Divergence). " + "Returns the line, signal, and histogram." + ), + input_schema=input_schema, + wrapper_signature=wrapper_signature, + invoke=invoke, + ) + + +def _legacy_backtest_tool() -> _ToolSpec: + """Return the legacy lowercase backtest alias tool.""" + wrapper_signature = inspect.Signature( + [ + inspect.Parameter( + "close", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[float], + default=inspect._empty, + ), + inspect.Parameter( + "strategy", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default="rsi_30_70", + ), + inspect.Parameter( + "commission_per_trade", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=float, + default=0.0, + ), + inspect.Parameter( + "slippage_bps", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=float, + default=0.0, + ), + ] + ) + input_schema = { + "type": "object", + "properties": { + "close": { + "type": "array", + "items": {"type": "number"}, + "description": "Close price series.", + }, + "strategy": { + "type": "string", + "description": ( + "Strategy name: 'rsi_30_70', 'sma_crossover', or 'macd_crossover'." + ), + "default": "rsi_30_70", + }, + "commission_per_trade": { + "type": "number", + "description": "Fixed commission per trade.", + "default": 0.0, + }, + "slippage_bps": { + "type": "number", + "description": "Slippage in basis points.", + "default": 0.0, + }, + }, + "required": ["close"], + "additionalProperties": False, + } + + def invoke(arguments: dict[str, Any]) -> Any: + close = np.asarray(arguments["close"], dtype=np.float64) + result = run_backtest( + str(arguments.get("strategy", "rsi_30_70")), + close, + commission_per_trade=float(arguments.get("commission_per_trade", 0.0)), + slippage_bps=float(arguments.get("slippage_bps", 0.0)), + ) + return _normalise_json(result) + + return _ToolSpec( + name="backtest", + description=( + "Run a vectorized backtest on close prices using a named strategy. " + "Returns final equity, trade count, and the equity curve." + ), + input_schema=input_schema, + wrapper_signature=wrapper_signature, + invoke=invoke, + ) + + +def _instance_management_specs() -> list[_ToolSpec]: + """Return generic stored-object management tools.""" + list_signature = inspect.Signature([]) + simple_schema = { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": False, + } + + describe_signature = inspect.Signature( + [ + inspect.Parameter( + "instance_id", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default=inspect._empty, + ) + ] + ) + describe_schema = { + "type": "object", + "properties": { + "instance_id": { + "type": "string", + "description": "Stored object identifier.", + } + }, + "required": ["instance_id"], + "additionalProperties": False, + } + + call_signature = inspect.Signature( + [ + inspect.Parameter( + "instance_id", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default=inspect._empty, + ), + inspect.Parameter( + "method", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default=inspect._empty, + ), + inspect.Parameter( + "args", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[Any], + default=None, + ), + inspect.Parameter( + "kwargs", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=dict[str, Any], + default=None, + ), + ] + ) + call_schema = { + "type": "object", + "properties": { + "instance_id": { + "type": "string", + "description": "Stored object identifier.", + }, + "method": { + "type": "string", + "description": "Public method name.", + }, + "args": { + "type": "array", + "items": {}, + "description": "Optional positional arguments.", + }, + "kwargs": { + "type": "object", + "description": f"Optional keyword arguments. {_REFERENCE_HELP}", + }, + }, + "required": ["instance_id", "method"], + "additionalProperties": False, + } + + callable_signature = inspect.Signature( + [ + inspect.Parameter( + "instance_id", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=str, + default=inspect._empty, + ), + inspect.Parameter( + "args", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=list[Any], + default=None, + ), + inspect.Parameter( + "kwargs", + kind=inspect.Parameter.KEYWORD_ONLY, + annotation=dict[str, Any], + default=None, + ), + ] + ) + callable_schema = { + "type": "object", + "properties": { + "instance_id": { + "type": "string", + "description": "Stored callable identifier.", + }, + "args": { + "type": "array", + "items": {}, + "description": "Optional positional arguments.", + }, + "kwargs": { + "type": "object", + "description": f"Optional keyword arguments. {_REFERENCE_HELP}", + }, + }, + "required": ["instance_id"], + "additionalProperties": False, + } + + def list_instances_tool(arguments: dict[str, Any]) -> Any: + del arguments + return _list_instances_payload() + + def describe_instance_tool(arguments: dict[str, Any]) -> Any: + return _describe_instance_payload(str(arguments["instance_id"])) + + def call_instance_method_tool(arguments: dict[str, Any]) -> Any: + value = _get_instance(str(arguments["instance_id"])) + method_name = str(arguments["method"]) + if method_name.startswith("_"): + raise ValueError("Only public methods can be called") + method = getattr(value, method_name) + if not callable(method): + raise TypeError(f"{method_name!r} is not callable on {arguments['instance_id']!r}") + args = [_decode_value(item, Any) for item in (arguments.get("args") or [])] + kwargs = { + str(key): _decode_value(item, Any) + for key, item in (arguments.get("kwargs") or {}).items() + } + return _normalise_json(method(*args, **kwargs)) + + def call_stored_callable_tool(arguments: dict[str, Any]) -> Any: + value = _get_instance(str(arguments["instance_id"])) + if not callable(value): + raise TypeError(f"{arguments['instance_id']!r} is not callable") + args = [_decode_value(item, Any) for item in (arguments.get("args") or [])] + kwargs = { + str(key): _decode_value(item, Any) + for key, item in (arguments.get("kwargs") or {}).items() + } + return _normalise_json(value(*args, **kwargs)) + + def delete_instance_tool(arguments: dict[str, Any]) -> Any: + identifier = str(arguments["instance_id"]) + payload = _describe_instance_payload(identifier) + _INSTANCE_STORE.pop(identifier) + _INSTANCE_META.pop(identifier, None) + return {"deleted": True, **payload} + + return [ + _ToolSpec( + name="list_instances", + description="List stored MCP object references created during this session.", + input_schema=simple_schema, + wrapper_signature=list_signature, + invoke=list_instances_tool, + ), + _ToolSpec( + name="describe_instance", + description="Describe a stored MCP object reference and list its public methods.", + input_schema=describe_schema, + wrapper_signature=describe_signature, + invoke=describe_instance_tool, + ), + _ToolSpec( + name="call_instance_method", + description="Call a public method on a stored MCP object reference.", + input_schema=call_schema, + wrapper_signature=call_signature, + invoke=call_instance_method_tool, + ), + _ToolSpec( + name="call_stored_callable", + description="Invoke a stored callable object reference.", + input_schema=callable_schema, + wrapper_signature=callable_signature, + invoke=call_stored_callable_tool, + ), + _ToolSpec( + name="delete_instance", + description="Delete a stored MCP object reference.", + input_schema=describe_schema, + wrapper_signature=describe_signature, + invoke=delete_instance_tool, + ), + ] + + +@lru_cache(maxsize=1) +def _tool_catalog() -> dict[str, _ToolSpec]: + """Return the full MCP tool catalog.""" + catalog: dict[str, _ToolSpec] = {} + + legacy_specs = [ + _legacy_series_tool( + "sma", + indicator_name="SMA", + description="Compute the Simple Moving Average (SMA) of a price series.", + ), + _legacy_series_tool( + "ema", + indicator_name="EMA", + description="Compute the Exponential Moving Average (EMA) of a price series.", + ), + _legacy_series_tool( + "rsi", + indicator_name="RSI", + description="Compute the Relative Strength Index (RSI) of a price series.", + ), + _legacy_macd_tool(), + _legacy_backtest_tool(), + ] + for spec in legacy_specs: + catalog[spec.name] = spec + + public_entries, targets = _discover_public_callables() + for item in public_entries: + spec = _build_public_tool_spec(item, targets[item["name"]]) + catalog[spec.name] = spec + + for spec in _instance_management_specs(): + catalog[spec.name] = spec + + return catalog + + +def _make_fastmcp_wrapper(spec: _ToolSpec) -> Callable[..., Any]: + """Create a FastMCP-friendly wrapper for *spec*.""" + + def wrapper(**kwargs: Any) -> Any: + return _to_call_tool_result(handle_call_tool(spec.name, dict(kwargs))) + + wrapper.__name__ = f"tool_{_slugify(spec.name).replace('-', '_')}" + wrapper.__doc__ = spec.description + wrapper.__signature__ = spec.wrapper_signature + wrapper.__annotations__ = { + parameter.name: ( + Any if parameter.annotation is inspect._empty else parameter.annotation + ) + for parameter in spec.wrapper_signature.parameters.values() + } + wrapper.__annotations__["return"] = Any + return wrapper def handle_list_tools() -> dict[str, Any]: - """Return the ListTools response.""" - return {"tools": _TOOLS} + """Return the MCP ListTools response.""" + return { + "tools": [ + { + "name": spec.name, + "description": spec.description, + "inputSchema": spec.input_schema, + } + for spec in _tool_catalog().values() + ] + } def handle_call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]: - """Dispatch a CallTool request and return the result. - - Parameters - ---------- - name : str - Tool name (one of the ``_TOOLS`` entries). - arguments : dict - Tool arguments as provided by the MCP client. - - Returns - ------- - dict - MCP content response with type ``"text"`` containing the JSON result. - """ + """Dispatch an MCP CallTool request and return helper-style content.""" try: - if name in ("sma", "ema", "rsi"): - close = np.asarray(arguments["close"], dtype=np.float64) - timeperiod = int(arguments.get("timeperiod", 14)) - result = compute_indicator(name.upper(), close, timeperiod=timeperiod) - # Replace NaN with None for JSON serialisation - payload = [None if np.isnan(v) else float(v) for v in result] - return {"content": [{"type": "text", "text": json.dumps(payload)}]} - - elif name == "macd": - close = np.asarray(arguments["close"], dtype=np.float64) - kwargs = { - "fastperiod": int(arguments.get("fastperiod", 12)), - "slowperiod": int(arguments.get("slowperiod", 26)), - "signalperiod": int(arguments.get("signalperiod", 9)), - } - result = compute_indicator("MACD", close, **kwargs) - assert isinstance(result, dict) - macd_payload = { - k: [None if np.isnan(v) else float(v) for v in arr] - for k, arr in result.items() - } - return {"content": [{"type": "text", "text": json.dumps(macd_payload)}]} - - elif name == "backtest": - close = np.asarray(arguments["close"], dtype=np.float64) - strategy = str(arguments.get("strategy", "rsi_30_70")) - commission = float(arguments.get("commission_per_trade", 0.0)) - slippage = float(arguments.get("slippage_bps", 0.0)) - summary = run_backtest( - strategy, - close, - commission_per_trade=commission, - slippage_bps=slippage, + spec = _tool_catalog().get(name) + if spec is None: + return _text_result( + f"Unknown tool: {name!r}", + structured={"error": f"Unknown tool: {name!r}"}, + is_error=True, ) - # JSON-serialise (equity is already a list) - return {"content": [{"type": "text", "text": json.dumps(summary)}]} - elif name == "list_indicators": - return { - "content": [{"type": "text", "text": json.dumps(list_indicators())}] - } - - elif name == "describe_indicator": - ind_name = str(arguments["name"]) - description = describe_indicator(ind_name) - return {"content": [{"type": "text", "text": description}]} - - else: - return { - "isError": True, - "content": [{"type": "text", "text": f"Unknown tool: {name!r}"}], - } + payload = spec.invoke(arguments) + return _response_from_payload(payload) except Exception as exc: - return { - "isError": True, - "content": [{"type": "text", "text": f"Error: {exc}"}], - } + return _text_result( + f"Error: {exc}", + structured={"error": str(exc)}, + is_error=True, + ) -# --------------------------------------------------------------------------- -# Stdio MCP server (JSON-RPC over stdin/stdout) -# --------------------------------------------------------------------------- +@lru_cache(maxsize=1) +def create_server() -> Any: + """Create the FastMCP server lazily so MCP stays optional.""" + fast_mcp_type, _, _ = _load_mcp_sdk() + app = fast_mcp_type( + "ferro-ta", + instructions=( + "Expose ferro-ta's public API over MCP. " + "Use exact public ferro-ta names such as SMA, RSI, compute_indicator, " + "TickAggregator, or AlertManager, or the legacy aliases sma, ema, rsi, " + "macd, and backtest. Use list_instances, describe_instance, " + "call_instance_method, call_stored_callable, and delete_instance for " + "stateful objects and stored callables." + ), + ) + + for spec in _tool_catalog().values(): + app.add_tool(_make_fastmcp_wrapper(spec), name=spec.name, description=spec.description) + + return app def run_server() -> None: # pragma: no cover - """Run the MCP server over stdin/stdout (JSON-RPC 2.0 protocol). - - This implements a minimal MCP server that handles ``initialize``, - ``tools/list``, and ``tools/call`` messages. It is compatible with the - MCP client built into Cursor (as of early 2025) and with the official - `mcp` Python SDK client. - - The server reads one JSON-RPC message per line from stdin and writes - one response per line to stdout. - """ - # Try to use official mcp SDK if available + """Run the stdio MCP server.""" try: - _run_with_sdk() - except ImportError: - _run_stdio_fallback() - - -def _run_with_sdk() -> None: # pragma: no cover - """Run using the official MCP Python SDK.""" - import mcp # type: ignore[import] - import mcp.server.stdio # type: ignore[import] - from mcp.server import Server # type: ignore[import] - from mcp.types import ( # type: ignore[import] - CallToolRequest, - ListToolsRequest, - ) - - app = Server("ferro-ta") - - @app.list_tools() - async def _list_tools(_req: ListToolsRequest): - return handle_list_tools()["tools"] - - @app.call_tool() - async def _call_tool(req: CallToolRequest): - return handle_call_tool(req.params.name, req.params.arguments or {}) - - import asyncio - - asyncio.run(mcp.server.stdio.stdio_server(app)) - - -def _run_stdio_fallback() -> None: # pragma: no cover - """Minimal stdin/stdout JSON-RPC MCP implementation (no SDK required).""" - import json as _json - - for raw_line in sys.stdin: - raw_line = raw_line.strip() - if not raw_line: - continue - try: - msg = _json.loads(raw_line) - except _json.JSONDecodeError: - continue - - msg_id = msg.get("id") - method = msg.get("method", "") - - if method == "initialize": - resp = { - "jsonrpc": "2.0", - "id": msg_id, - "result": { - "protocolVersion": "2024-11-05", - "capabilities": {"tools": {}}, - "serverInfo": {"name": "ferro-ta", "version": ft.__version__}, - }, - } - elif method == "tools/list": - resp = { - "jsonrpc": "2.0", - "id": msg_id, - "result": handle_list_tools(), - } - elif method == "tools/call": - params = msg.get("params", {}) - tool_name = params.get("name", "") - arguments = params.get("arguments", {}) - resp = { - "jsonrpc": "2.0", - "id": msg_id, - "result": handle_call_tool(tool_name, arguments), - } - else: - resp = { - "jsonrpc": "2.0", - "id": msg_id, - "error": {"code": -32601, "message": f"Method not found: {method!r}"}, - } - - sys.stdout.write(_json.dumps(resp) + "\n") - sys.stdout.flush() + create_server().run(transport="stdio") + except RuntimeError as exc: + raise SystemExit(str(exc)) from exc diff --git a/tests/unit/test_tools_and_api.py b/tests/unit/test_tools_and_api.py index 2b5f721..4f04869 100644 --- a/tests/unit/test_tools_and_api.py +++ b/tests/unit/test_tools_and_api.py @@ -4,6 +4,9 @@ regime detection, performance attribution, and dashboard helpers. from __future__ import annotations +import importlib +import runpy + import numpy as np import pytest @@ -1218,7 +1221,22 @@ class TestMCPListTools: result = handle_list_tools() names = [t["name"] for t in result["tools"]] - for expected in ("sma", "ema", "rsi", "macd", "backtest", "list_indicators"): + assert len(names) > 250 + for expected in ( + "sma", + "ema", + "rsi", + "macd", + "backtest", + "SMA", + "compute_indicator", + "about", + "check_cross", + "TickAggregator", + "call_instance_method", + "call_stored_callable", + "delete_instance", + ): assert expected in names, f"Expected tool '{expected}' not found" def test_each_tool_has_schema(self): @@ -1280,6 +1298,57 @@ class TestMCPCallTool: assert "final_equity" in payload assert "n_trades" in payload + def test_top_level_sma_call(self): + import json + + from ferro_ta.mcp import handle_call_tool + + close = list(np.linspace(100, 110, 30)) + result = handle_call_tool("SMA", {"close": close, "timeperiod": 5}) + payload = json.loads(result["content"][0]["text"]) + assert len(payload) == 30 + + def test_compute_indicator_call(self): + import json + + from ferro_ta.mcp import handle_call_tool + + close = list(_make_close(100)) + result = handle_call_tool( + "compute_indicator", + { + "name": "MACD", + "args": [close], + }, + ) + payload = json.loads(result["content"][0]["text"]) + assert "macd" in payload + + def test_about_call(self): + import json + + from ferro_ta.mcp import handle_call_tool + + result = handle_call_tool("about", {}) + payload = json.loads(result["content"][0]["text"]) + assert payload["indicator_count"] >= 200 + assert payload["method_count"] >= 400 + + def test_check_cross_call(self): + import json + + from ferro_ta.mcp import handle_call_tool + + result = handle_call_tool( + "check_cross", + { + "fast": [1.0, 2.0, 3.0, 2.0, 1.0], + "slow": [2.0, 2.0, 2.0, 2.0, 2.0], + }, + ) + payload = json.loads(result["content"][0]["text"]) + assert len(payload) == 5 + def test_list_indicators_call(self): import json @@ -1322,3 +1391,129 @@ class TestMCPCallTool: "backtest", {"close": close, "strategy": "no_strategy"} ) assert result.get("isError") is True or "content" in result + + def test_tick_aggregator_instance_lifecycle(self): + import json + + from ferro_ta.mcp import handle_call_tool + + created = json.loads( + handle_call_tool("TickAggregator", {"rule": "tick:2"})["content"][0]["text"] + ) + instance_id = created["instance_id"] + + described = json.loads( + handle_call_tool( + "describe_instance", {"instance_id": instance_id} + )["content"][0]["text"] + ) + method_names = [item["name"] for item in described["methods"]] + assert "aggregate" in method_names + + aggregated = json.loads( + handle_call_tool( + "call_instance_method", + { + "instance_id": instance_id, + "method": "aggregate", + "args": [ + { + "price": [1.0, 2.0, 3.0, 4.0], + "size": [1.0, 1.0, 1.0, 1.0], + } + ], + }, + )["content"][0]["text"] + ) + assert "open" in aggregated + assert "close" in aggregated + + deleted = json.loads( + handle_call_tool( + "delete_instance", {"instance_id": instance_id} + )["content"][0]["text"] + ) + assert deleted["deleted"] is True + + def test_stored_callable_can_be_invoked(self): + import json + + from ferro_ta.mcp import handle_call_tool + + wrapped = json.loads( + handle_call_tool( + "traced", {"func": {"callable": "SMA"}} + )["content"][0]["text"] + ) + instance_id = wrapped["instance_id"] + + called = json.loads( + handle_call_tool( + "call_stored_callable", + { + "instance_id": instance_id, + "args": [[1.0, 2.0, 3.0, 4.0, 5.0]], + "kwargs": {"timeperiod": 3}, + }, + )["content"][0]["text"] + ) + assert len(called) == 5 + + handle_call_tool("delete_instance", {"instance_id": instance_id}) + + def test_benchmark_accepts_callable_reference(self): + import json + + from ferro_ta.mcp import handle_call_tool + + result = handle_call_tool( + "benchmark", + { + "func": {"callable": "SMA"}, + "args": [[1.0, 2.0, 3.0, 4.0, 5.0]], + "kwargs": {"timeperiod": 3}, + "n": 2, + "warmup": 0, + }, + ) + payload = json.loads(result["content"][0]["text"]) + assert payload["n"] == 2.0 + assert "mean_ms" in payload + + +class TestMCPServer: + def test_create_server_requires_mcp_dependency(self, monkeypatch): + import ferro_ta.mcp as mcp_mod + + real_import_module = importlib.import_module + + def fake_import_module(name, package=None): + if name.startswith("mcp"): + raise ImportError("No module named 'mcp'") + return real_import_module(name, package) + + mcp_mod.create_server.cache_clear() + monkeypatch.setattr(importlib, "import_module", fake_import_module) + + with pytest.raises(RuntimeError, match='pip install "ferro-ta\\[mcp\\]"'): + mcp_mod.create_server() + + def test_main_entrypoint_invokes_run_server(self, monkeypatch): + import ferro_ta.mcp as mcp_mod + + calls: list[str] = [] + + monkeypatch.setattr(mcp_mod, "run_server", lambda: calls.append("called")) + runpy.run_module("ferro_ta.mcp.__main__", run_name="__main__") + + assert calls == ["called"] + + def test_create_server_registers_generated_tools(self): + import ferro_ta.mcp as mcp_mod + + server = mcp_mod.create_server() + tool_names = [tool.name for tool in server._tool_manager.list_tools()] + + assert "SMA" in tool_names + assert "TickAggregator" in tool_names + assert "call_instance_method" in tool_names diff --git a/uv.lock b/uv.lock index 37042fd..def2fa8 100644 --- a/uv.lock +++ b/uv.lock @@ -515,19 +515,24 @@ wheels = [ [[package]] name = "cuda-bindings" -version = "12.9.4" +version = "13.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-pathfinder", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/d8/b546104b8da3f562c1ff8ab36d130c8fe1dd6a045ced80b4f6ad74f7d4e1/cuda_bindings-12.9.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d3c842c2a4303b2a580fe955018e31aea30278be19795ae05226235268032e5", size = 12148218, upload-time = "2025-10-21T14:51:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, - { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fe/7351d7e586a8b4c9f89731bfe4cf0148223e8f9903ff09571f78b3fb0682/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556", size = 5744254, upload-time = "2026-03-11T00:12:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ef/184aa775e970fc089942cd9ec6302e6e44679d4c14549c6a7ea45bf7f798/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6f3682ec3c4769326aafc67c2ba669d97d688d0b7e63e659d36d2f8b72f32d6", size = 6329075, upload-time = "2026-03-11T00:12:32.319Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, ] [[package]] @@ -538,6 +543,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/02/59a5bc738a09def0b49aea0e460bdf97f65206d0d041246147cf6207e69c/cuda_pathfinder-1.4.1-py3-none-any.whl", hash = "sha256:40793006082de88e0950753655e55558a446bed9a7d9d0bcb48b2506d50ed82a", size = 43903, upload-time = "2026-03-06T21:05:24.372Z" }, ] +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +curand = [ + { name = "nvidia-curand", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or sys_platform == 'linux'" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -593,7 +641,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.135.1" +version = "0.135.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -602,9 +650,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/7b/f8e0211e9380f7195ba3f3d40c292594fd81ba8ec4629e3854c353aaca45/fastapi-0.135.1.tar.gz", hash = "sha256:d04115b508d936d254cea545b7312ecaa58a7b3a0f84952535b4c9afae7668cd", size = 394962, upload-time = "2026-03-01T18:18:29.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833, upload-time = "2026-03-23T14:12:41.697Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407, upload-time = "2026-03-23T14:12:43.284Z" }, ] [[package]] @@ -1583,137 +1631,152 @@ wheels = [ ] [[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" +name = "nvidia-cublas" +version = "13.1.0.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, ] [[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" +name = "nvidia-cuda-cupti" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, ] [[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" +name = "nvidia-cuda-nvrtc" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, ] [[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" +name = "nvidia-cuda-runtime" +version = "13.0.96" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, ] [[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, ] [[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" +name = "nvidia-cufft" +version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, ] [[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" +name = "nvidia-cufile" +version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, ] [[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" +name = "nvidia-curand" +version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, ] [[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" +name = "nvidia-cusolver" +version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "nvidia-cusparse-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, ] [[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" +name = "nvidia-cusparse" +version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, ] [[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" +name = "nvidia-cusparselt-cu13" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, ] [[package]] -name = "nvidia-nccl-cu12" -version = "2.27.5" +name = "nvidia-nccl-cu13" +version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, ] [[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" +name = "nvidia-nvjitlink" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, ] [[package]] -name = "nvidia-nvshmem-cu12" +name = "nvidia-nvshmem-cu13" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, ] [[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" +name = "nvidia-nvtx" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, ] [[package]] @@ -1994,30 +2057,30 @@ wheels = [ [[package]] name = "polars" -version = "1.38.1" +version = "1.39.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "polars-runtime-32" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/5e/208a24471a433bcd0e9a6889ac49025fd4daad2815c8220c5bd2576e5f1b/polars-1.38.1.tar.gz", hash = "sha256:803a2be5344ef880ad625addfb8f641995cfd777413b08a10de0897345778239", size = 717667, upload-time = "2026-02-06T18:13:23.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/ab/f19e592fce9e000da49c96bf35e77cef67f9cb4b040bfa538a2764c0263e/polars-1.39.3.tar.gz", hash = "sha256:2e016c7f3e8d14fa777ef86fe0477cec6c67023a20ba4c94d6e8431eefe4a63c", size = 728987, upload-time = "2026-03-20T11:16:24.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl", hash = "sha256:a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c", size = 810368, upload-time = "2026-02-06T18:11:55.819Z" }, + { url = "https://files.pythonhosted.org/packages/b4/db/08f4ca10c5018813e7e0b59e4472302328b3d2ab1512f5a2157a814540e0/polars-1.39.3-py3-none-any.whl", hash = "sha256:c2b955ccc0a08a2bc9259785decf3d5c007b489b523bf2390cf21cec2bb82a56", size = 823985, upload-time = "2026-03-20T11:14:23.619Z" }, ] [[package]] name = "polars-runtime-32" -version = "1.38.1" +version = "1.39.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/4b/04d6b3fb7cf336fbe12fbc4b43f36d1783e11bb0f2b1e3980ec44878df06/polars_runtime_32-1.38.1.tar.gz", hash = "sha256:04f20ed1f5c58771f34296a27029dc755a9e4b1390caeaef8f317e06fdfce2ec", size = 2812631, upload-time = "2026-02-06T18:13:25.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/39/c8688696bc22b6c501e3b82ef3be10e543c07a785af5660f30997cd22dd2/polars_runtime_32-1.39.3.tar.gz", hash = "sha256:c728e4f469cafab501947585f36311b8fb222d3e934c6209e83791e0df20b29d", size = 2872335, upload-time = "2026-03-20T11:16:26.581Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/a2/a00defbddadd8cf1042f52380dcba6b6592b03bac8e3b34c436b62d12d3b/polars_runtime_32-1.38.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:18154e96044724a0ac38ce155cf63aa03c02dd70500efbbf1a61b08cadd269ef", size = 44108001, upload-time = "2026-02-06T18:11:58.127Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac", size = 40230140, upload-time = "2026-02-06T18:12:01.181Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323", size = 41994039, upload-time = "2026-02-06T18:12:04.332Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba", size = 45755804, upload-time = "2026-02-06T18:12:07.846Z" }, - { url = "https://files.pythonhosted.org/packages/91/54/02cd4074c98c361ccd3fec3bcb0bd68dbc639c0550c42a4436b0ff0f3ccf/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10d19cd9863e129273b18b7fcaab625b5c8143c2d22b3e549067b78efa32e4fa", size = 42159605, upload-time = "2026-02-06T18:12:10.919Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f3/b2a5e720cc56eaa38b4518e63aa577b4bbd60e8b05a00fe43ca051be5879/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61e8d73c614b46a00d2f853625a7569a2e4a0999333e876354ac81d1bf1bb5e2", size = 45336615, upload-time = "2026-02-06T18:12:14.074Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8d/ee2e4b7de948090cfb3df37d401c521233daf97bfc54ddec5d61d1d31618/polars_runtime_32-1.38.1-cp310-abi3-win_amd64.whl", hash = "sha256:08c2b3b93509c1141ac97891294ff5c5b0c548a373f583eaaea873a4bf506437", size = 45680732, upload-time = "2026-02-06T18:12:19.097Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/72c216f4ab0c82b907009668f79183ae029116ff0dd245d56ef58aac48e7/polars_runtime_32-1.38.1-cp310-abi3-win_arm64.whl", hash = "sha256:6d07d0cc832bfe4fb54b6e04218c2c27afcfa6b9498f9f6bbf262a00d58cc7c4", size = 41639413, upload-time = "2026-02-06T18:12:22.044Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/1b41205f7368c9375ab1dea91178eaa20435fe3eff036390a53a7660b416/polars_runtime_32-1.39.3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:425c0b220b573fa097b4042edff73114cc6d23432a21dfd2dc41adf329d7d2e9", size = 45273243, upload-time = "2026-03-20T11:14:26.691Z" }, + { url = "https://files.pythonhosted.org/packages/90/bf/297716b3095fe719be20fcf7af1d2b6ab069c38199bbace2469608a69b3a/polars_runtime_32-1.39.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef5884711e3c617d7dc93519a7d038e242f5741cfe5fe9afd32d58845d86c562", size = 40842924, upload-time = "2026-03-20T11:14:31.154Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/e65236d9d0d9babfa0ecba593413c06530fca60a8feb8f66243aa5dba92e/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06b47f535eb1f97a9a1e5b0053ef50db3a4276e241178e37bbb1a38b1fa53b14", size = 43220650, upload-time = "2026-03-20T11:14:35.458Z" }, + { url = "https://files.pythonhosted.org/packages/b0/15/fc3e43f3fdf3f20b7dfb5abe871ab6162cf8fb4aeabf4cfad822d5dc4c79/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc9e13dc1d2e828331f2fe8ccbc9757554dc4933a8d3e85e906b988178f95ed", size = 46877498, upload-time = "2026-03-20T11:14:40.14Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/bd5f895919e32c6ab0a7786cd0c0ca961cb03152c47c3645808b54383f31/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:363d49e3a3e638fc943e2b9887940300a7d06789930855a178a4727949259dc2", size = 43380176, upload-time = "2026-03-20T11:14:45.566Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3e/c86433c3b5ec0315bdfc7640d0c15d41f1216c0103a0eab9a9b5147d6c4c/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7c206bdcc7bc62ea038d6adea8e44b02f0e675e0191a54c810703b4895208ea4", size = 46485933, upload-time = "2026-03-20T11:14:51.155Z" }, + { url = "https://files.pythonhosted.org/packages/54/ce/200b310cf91f98e652eb6ea09fdb3a9718aa0293ebf113dce325797c8572/polars_runtime_32-1.39.3-cp310-abi3-win_amd64.whl", hash = "sha256:d66ca522517554a883446957539c40dc7b75eb0c2220357fb28bc8940d305339", size = 46995458, upload-time = "2026-03-20T11:14:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/da/76/2d48927e0aa2abbdde08cbf4a2536883b73277d47fbeca95e952de86df34/polars_runtime_32-1.39.3-cp310-abi3-win_arm64.whl", hash = "sha256:f49f51461de63f13e5dd4eb080421c8f23f856945f3f8bd5b2b1f59da52c2860", size = 41857648, upload-time = "2026-03-20T11:15:01.142Z" }, ] [[package]] @@ -2557,36 +2620,36 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.5" +version = "0.15.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, - { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, - { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, - { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] name = "setuptools" -version = "82.0.0" +version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, ] [[package]] @@ -2963,75 +3026,54 @@ wheels = [ [[package]] name = "torch" -version = "2.10.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/30/bfebdd8ec77db9a79775121789992d6b3b75ee5494971294d7b4b7c999bc/torch-2.10.0-2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:2b980edd8d7c0a68c4e951ee1856334a43193f98730d97408fbd148c1a933313", size = 79411457, upload-time = "2026-02-10T21:44:59.189Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/16/ee/efbd56687be60ef9af0c9c0ebe106964c07400eade5b0af8902a1d8cd58c/torch-2.10.0-3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a1ff626b884f8c4e897c4c33782bdacdff842a165fee79817b1dd549fdda1321", size = 915510070, upload-time = "2026-03-11T14:16:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/7b562f1808d3f65414cd80a4f7d4bb00979d9355616c034c171249e1a303/torch-2.10.0-3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac5bdcbb074384c66fa160c15b1ead77839e3fe7ed117d667249afce0acabfac", size = 915518691, upload-time = "2026-03-11T14:15:43.147Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c6/4dfe238342ffdcec5aef1c96c457548762d33c40b45a1ab7033bb26d2ff2/torch-2.10.0-3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b", size = 915627275, upload-time = "2026-03-11T14:16:11.325Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f0/72bf18847f58f877a6a8acf60614b14935e2f156d942483af1ffc081aea0/torch-2.10.0-3-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49", size = 915523474, upload-time = "2026-03-11T14:17:44.422Z" }, - { url = "https://files.pythonhosted.org/packages/f4/39/590742415c3030551944edc2ddc273ea1fdfe8ffb2780992e824f1ebee98/torch-2.10.0-3-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b1d5e2aba4eb7f8e87fbe04f86442887f9167a35f092afe4c237dfcaaef6e328", size = 915632474, upload-time = "2026-03-11T14:15:13.666Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8e/34949484f764dde5b222b7fe3fede43e4a6f0da9d7f8c370bb617d629ee2/torch-2.10.0-3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:0228d20b06701c05a8f978357f657817a4a63984b0c90745def81c18aedfa591", size = 915523882, upload-time = "2026-03-11T14:14:46.311Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/c61f36cfd446170ec27b3a4984f072fd06dab6b5d7ce27e11adb35d6c838/torch-2.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5276fa790a666ee8becaffff8acb711922252521b28fbce5db7db5cf9cb2026d", size = 145992962, upload-time = "2026-01-21T16:24:14.04Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/6662535354191e2d1555296045b63e4279e5a9dbad49acf55a5d38655a39/torch-2.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:aaf663927bcd490ae971469a624c322202a2a1e68936eb952535ca4cd3b90444", size = 915599237, upload-time = "2026-01-21T16:23:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/66bbe96f0d79be2b5c697b2e0b187ed792a15c6c4b8904613454651db848/torch-2.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:a4be6a2a190b32ff5c8002a0977a25ea60e64f7ba46b1be37093c141d9c49aeb", size = 113720931, upload-time = "2026-01-21T16:24:23.743Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/d820f90e69cda6c8169b32a0c6a3ab7b17bf7990b8f2c680077c24a3c14c/torch-2.10.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:35e407430795c8d3edb07a1d711c41cc1f9eaddc8b2f1cc0a165a6767a8fb73d", size = 79411450, upload-time = "2026-01-21T16:25:30.692Z" }, - { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/15b9d9d3a6b0c01b883787bd056acbe5cc321090d4b216d3ea89a8fcfdf3/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b", size = 79423461, upload-time = "2026-01-21T16:24:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" }, - { url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" }, - { url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" }, - { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, - { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, - { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, - { url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" }, - { url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" }, - { url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" }, - { url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" }, - { url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" }, - { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f2/c1690994afe461aae2d0cac62251e6802a703dec0a6c549c02ecd0de92a9/torch-2.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c0d7fcfbc0c4e8bb5ebc3907cbc0c6a0da1b8f82b1fc6e14e914fa0b9baf74e", size = 80526521, upload-time = "2026-03-23T18:12:06.86Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f0/98ae802fa8c09d3149b0c8690741f3f5753c90e779bd28c9613257295945/torch-2.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4cf8687f4aec3900f748d553483ef40e0ac38411c3c48d0a86a438f6d7a99b18", size = 419723025, upload-time = "2026-03-23T18:11:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/18a9b10b4bd34f12d4e561c52b0ae7158707b8193c6cfc0aad2b48167090/torch-2.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1b32ceda909818a03b112006709b02be1877240c31750a8d9c6b7bf5f2d8a6e5", size = 530589207, upload-time = "2026-03-23T18:11:23.756Z" }, + { url = "https://files.pythonhosted.org/packages/35/40/2d532e8c0e23705be9d1debce5bc37b68d59a39bda7584c26fe9668076fe/torch-2.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3c712ae6fb8e7a949051a953fc412fe0a6940337336c3b6f905e905dac5157f", size = 114518313, upload-time = "2026-03-23T18:11:58.281Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, ] [[package]] @@ -3051,12 +3093,19 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ + { url = "https://files.pythonhosted.org/packages/44/ba/b1b04f4b291a3205d95ebd24465de0e5bf010a2df27a4e58a9b5f039d8f2/triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781", size = 175972180, upload-time = "2026-01-20T16:15:53.664Z" }, { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] @@ -3101,16 +3150,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.41.0" +version = "0.42.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, { name = "h11", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, ] [[package]]