From 71b6343e92f86ec043d1d971cb5e87e4c9e7e074 Mon Sep 17 00:00:00 2001 From: Pratik Bhadane Date: Tue, 24 Mar 2026 14:52:20 +0530 Subject: [PATCH] feat: refresh benchmark coverage and harden CI tooling Refresh the benchmark and performance surface across the repo. This updates the benchmark wrappers and helper scripts, regenerates the checked-in benchmark and perf-contract artifacts, and folds in the related roadmap, compatibility, and example notebook changes that belong with this performance-focused pass. Harden the Python CI and local pre-push flow so the same checks pass reliably in both places. The workflow and pre-push script now use module-safe uv typecheck invocations, the Python test environment installs the optional MCP dependency needed by the MCP server tests, and one-off root benchmark outputs are ignored to keep the repo clean. Align local tooling with the current project configuration by updating the Ruff pre-commit hook, tightening the API typing and MCP server helpers, and refreshing the lockfile to pick up the audited PyJWT fix while preserving the rest of the staged source changes. --- .github/workflows/ci-python.yml | 6 +- .gitignore | 3 + .pre-commit-config.yaml | 3 +- PERFORMANCE_ROADMAP.md | 10 +- TA_LIB_COMPATIBILITY.md | 1 - api/main.py | 36 +- benchmarks/artifacts/latest/batch.json | 2 +- .../latest/benchmark_derivatives_compare.json | 2 +- .../artifacts/latest/benchmark_vs_talib.json | 2 +- .../artifacts/latest/indicator_latency.json | 2 +- benchmarks/artifacts/latest/manifest.json | 2 +- .../artifacts/latest/runtime_hotspots.json | 2 +- benchmarks/artifacts/latest/simd.json | 2 +- benchmarks/artifacts/latest/streaming.json | 2 +- benchmarks/bench_batch.py | 12 +- benchmarks/bench_derivatives_compare.py | 3 +- benchmarks/bench_simd.py | 8 +- benchmarks/bench_streaming.py | 8 +- benchmarks/bench_vs_talib.py | 24 +- benchmarks/benchmark_table.py | 16 +- benchmarks/check_vs_talib_regression.py | 8 +- benchmarks/profile_runtime_hotspots.py | 25 +- benchmarks/results.json | 2 +- benchmarks/run_perf_contract.py | 9 +- benchmarks/test_accuracy.py | 113 +- benchmarks/test_speed.py | 49 +- benchmarks/wrapper_registry.py | 3137 +++++++++++++---- examples/backtesting.ipynb | 6 +- examples/quickstart.ipynb | 3 +- examples/streaming.ipynb | 1 - perf-contract/batch.json | 88 +- perf-contract/indicator_latency.json | 84 +- perf-contract/manifest.json | 57 +- perf-contract/runtime_hotspots.json | 116 +- perf-contract/simd.json | 2 +- perf-contract/streaming.json | 96 +- pyproject.toml | 1 + python/ferro_ta/analysis/backtest.py | 4 +- .../ferro_ta/analysis/derivatives_payoff.py | 4 +- python/ferro_ta/analysis/features.py | 4 +- python/ferro_ta/mcp/__init__.py | 80 +- scripts/build_api_manifest.py | 12 +- scripts/bump_version.py | 1 - scripts/pre_push_checks.sh | 10 +- .../test_cross_surface_manifest.py | 2 +- .../integration/test_wasm_node_conformance.py | 8 +- tests/unit/test_tools_and_api.py | 18 +- uv.lock | 9 +- 48 files changed, 3107 insertions(+), 988 deletions(-) diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 9b41c94..1916d4f 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -41,10 +41,10 @@ jobs: run: pip install uv - name: Run mypy on ferro_ta via uv - run: uv run --with mypy --with numpy mypy python/ferro_ta --ignore-missing-imports --no-error-summary + run: uv run --with mypy --with numpy python -m mypy python/ferro_ta --ignore-missing-imports --no-error-summary - name: Run pyright on ferro_ta via uv - run: uv run --with pyright pyright python/ferro_ta + run: uv run --with pyright python -m pyright python/ferro_ta test: name: Test (ubuntu-latest / Python ${{ matrix.python-version }}) @@ -63,7 +63,7 @@ jobs: - name: Install maturin and test dependencies run: | - pip install maturin numpy pytest pytest-cov pandas polars hypothesis pyyaml + pip install maturin numpy pytest pytest-cov pandas polars hypothesis pyyaml mcp - name: Build and install ferro_ta (dev mode) run: | diff --git a/.gitignore b/.gitignore index ea71f04..d27182e 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ env/ # WASM build output wasm/pkg/ wasm/pkg-web/ +benchmark_vs_talib.json +wasm_benchmark.json +.wasm_benchmark.prepush.json .coverage .coverage.* coverage.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 897d608..e849b64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ default_language_version: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.15.7 hooks: - id: ruff args: [--fix] @@ -18,6 +18,7 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml + exclude: ^conda/meta\.yaml$ - id: check-added-large-files args: [--maxkb=1000] - id: check-merge-conflict diff --git a/PERFORMANCE_ROADMAP.md b/PERFORMANCE_ROADMAP.md index 34a2c84..cfffbdf 100644 --- a/PERFORMANCE_ROADMAP.md +++ b/PERFORMANCE_ROADMAP.md @@ -70,23 +70,23 @@ rustflags = ["-C", "target-cpu=native"] ### Phase 3: Algorithm-Level Optimizations (Target: 5-10x improvement) #### SMA — O(n) running sum -Current: recomputes each window. +Current: recomputes each window. Target: single-pass running sum (already done in Rust — verify SIMD path is hit). #### BBANDS — Welford's algorithm -Current: compute mean, then variance in two passes. +Current: compute mean, then variance in two passes. Target: Welford's online algorithm — single pass, better cache utilization. #### ATR/ADX — Avoid redundant True Range calculations -Current: ATR → ADX each compute TR independently. +Current: ATR → ADX each compute TR independently. Target: Compute TR once, share with ATR, NATR, +DI, -DI, ADX in a single pass. #### MACD — Reuse EMA computations -Current: Compute fast EMA and slow EMA separately. +Current: Compute fast EMA and slow EMA separately. Target: Single function computes both EMAs in one pass. #### Candlestick Patterns — Batch lookup table -Current: Sequential condition checks per bar. +Current: Sequential condition checks per bar. Target: Pre-compute body/shadow ratios, vectorized pattern matching. ### Phase 4: Streaming Precomputation (Target: 100x for incremental updates) diff --git a/TA_LIB_COMPATIBILITY.md b/TA_LIB_COMPATIBILITY.md index ac4980c..52fea9c 100644 --- a/TA_LIB_COMPATIBILITY.md +++ b/TA_LIB_COMPATIBILITY.md @@ -259,4 +259,3 @@ for the first `timeperiod - 1` bars. > `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/api/main.py b/api/main.py index a39f881..70efbb7 100644 --- a/api/main.py +++ b/api/main.py @@ -77,7 +77,7 @@ from __future__ import annotations import math import os -from typing import Any, Dict, List, Optional +from typing import Any import numpy as np @@ -117,12 +117,12 @@ app = FastAPI( # --------------------------------------------------------------------------- -def _nan_to_none(arr: np.ndarray) -> List[Optional[float]]: +def _nan_to_none(arr: np.ndarray) -> list[float | None]: """Convert numpy array to list, replacing NaN/Inf with None.""" return [None if not math.isfinite(v) else float(v) for v in arr] -def _validate_series(close: List[float]) -> np.ndarray: +def _validate_series(close: list[float]) -> np.ndarray: if len(close) > MAX_SERIES_LENGTH: raise HTTPException( status_code=413, @@ -142,54 +142,54 @@ def _validate_series(close: List[float]) -> np.ndarray: class IndicatorRequest(BaseModel): - close: List[float] = Field(..., description="Close price series") + close: list[float] = Field(..., description="Close price series") timeperiod: int = Field(default=14, ge=1, description="Look-back period") @field_validator("close") @classmethod - def close_must_be_finite(cls, v: List[float]) -> List[float]: + def close_must_be_finite(cls, v: list[float]) -> list[float]: if not all(math.isfinite(x) for x in v): raise ValueError("close series must contain only finite values") return v class MACDRequest(BaseModel): - close: List[float] = Field(..., description="Close price series") + close: list[float] = Field(..., description="Close price series") fastperiod: int = Field(default=12, ge=1) slowperiod: int = Field(default=26, ge=1) signalperiod: int = Field(default=9, ge=1) @field_validator("close") @classmethod - def close_must_be_finite(cls, v: List[float]) -> List[float]: + def close_must_be_finite(cls, v: list[float]) -> list[float]: if not all(math.isfinite(x) for x in v): raise ValueError("close series must contain only finite values") return v class BBANDSRequest(BaseModel): - close: List[float] = Field(..., description="Close price series") + close: list[float] = Field(..., description="Close price series") timeperiod: int = Field(default=5, ge=2) nbdevup: float = Field(default=2.0, gt=0) nbdevdn: float = Field(default=2.0, gt=0) @field_validator("close") @classmethod - def close_must_be_finite(cls, v: List[float]) -> List[float]: + def close_must_be_finite(cls, v: list[float]) -> list[float]: if not all(math.isfinite(x) for x in v): raise ValueError("close series must contain only finite values") return v class BacktestRequest(BaseModel): - close: List[float] = Field(..., description="Close price series") + close: list[float] = Field(..., description="Close price series") strategy: str = Field(default="rsi_30_70") commission_per_trade: float = Field(default=0.0, ge=0.0) slippage_bps: float = Field(default=0.0, ge=0.0) @field_validator("close") @classmethod - def close_must_be_finite(cls, v: List[float]) -> List[float]: + def close_must_be_finite(cls, v: list[float]) -> list[float]: if not all(math.isfinite(x) for x in v): raise ValueError("close series must contain only finite values") return v @@ -201,13 +201,13 @@ class BacktestRequest(BaseModel): @app.get("/health", summary="Health check") -def health() -> Dict[str, str]: +def health() -> dict[str, str]: """Readiness / liveness probe.""" return {"status": "ok", "version": app.version} @app.post("/indicators/sma", summary="Simple Moving Average") -def compute_sma(req: IndicatorRequest) -> Dict[str, Any]: +def compute_sma(req: IndicatorRequest) -> dict[str, Any]: """Compute Simple Moving Average (SMA). Returns ``result``: list of floats (null for warm-up bars). @@ -218,7 +218,7 @@ def compute_sma(req: IndicatorRequest) -> Dict[str, Any]: @app.post("/indicators/ema", summary="Exponential Moving Average") -def compute_ema(req: IndicatorRequest) -> Dict[str, Any]: +def compute_ema(req: IndicatorRequest) -> dict[str, Any]: """Compute Exponential Moving Average (EMA).""" c = _validate_series(req.close) out = np.asarray(ft.EMA(c, timeperiod=req.timeperiod), dtype=np.float64) @@ -226,7 +226,7 @@ def compute_ema(req: IndicatorRequest) -> Dict[str, Any]: @app.post("/indicators/rsi", summary="Relative Strength Index") -def compute_rsi(req: IndicatorRequest) -> Dict[str, Any]: +def compute_rsi(req: IndicatorRequest) -> dict[str, Any]: """Compute Relative Strength Index (RSI).""" c = _validate_series(req.close) out = np.asarray(ft.RSI(c, timeperiod=req.timeperiod), dtype=np.float64) @@ -234,7 +234,7 @@ def compute_rsi(req: IndicatorRequest) -> Dict[str, Any]: @app.post("/indicators/macd", summary="MACD") -def compute_macd(req: MACDRequest) -> Dict[str, Any]: +def compute_macd(req: MACDRequest) -> dict[str, Any]: """Compute MACD (line, signal, histogram). Returns ``result`` with keys ``macd``, ``signal``, ``hist``. @@ -256,7 +256,7 @@ def compute_macd(req: MACDRequest) -> Dict[str, Any]: @app.post("/indicators/bbands", summary="Bollinger Bands") -def compute_bbands(req: BBANDSRequest) -> Dict[str, Any]: +def compute_bbands(req: BBANDSRequest) -> dict[str, Any]: """Compute Bollinger Bands (upper, middle, lower). Returns ``result`` with keys ``upper``, ``middle``, ``lower``. @@ -278,7 +278,7 @@ def compute_bbands(req: BBANDSRequest) -> Dict[str, Any]: @app.post("/backtest", summary="Vectorized backtest") -def run_backtest(req: BacktestRequest) -> Dict[str, Any]: +def run_backtest(req: BacktestRequest) -> dict[str, Any]: """Run a vectorized backtest using a named strategy. Strategies: ``rsi_30_70``, ``sma_crossover``, ``macd_crossover``. diff --git a/benchmarks/artifacts/latest/batch.json b/benchmarks/artifacts/latest/batch.json index 89a46a4..58d152d 100644 --- a/benchmarks/artifacts/latest/batch.json +++ b/benchmarks/artifacts/latest/batch.json @@ -68,4 +68,4 @@ "speedup_vs_separate": 2.2811 } ] -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/benchmark_derivatives_compare.json b/benchmarks/artifacts/latest/benchmark_derivatives_compare.json index 4997192..20f88c0 100644 --- a/benchmarks/artifacts/latest/benchmark_derivatives_compare.json +++ b/benchmarks/artifacts/latest/benchmark_derivatives_compare.json @@ -1159,4 +1159,4 @@ } ] } -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/benchmark_vs_talib.json b/benchmarks/artifacts/latest/benchmark_vs_talib.json index 2f1000c..bf22d9b 100644 --- a/benchmarks/artifacts/latest/benchmark_vs_talib.json +++ b/benchmarks/artifacts/latest/benchmark_vs_talib.json @@ -1379,4 +1379,4 @@ "outcome": "ferro_ta_win" } ] -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/indicator_latency.json b/benchmarks/artifacts/latest/indicator_latency.json index c931fb1..0ecfaaa 100644 --- a/benchmarks/artifacts/latest/indicator_latency.json +++ b/benchmarks/artifacts/latest/indicator_latency.json @@ -160,4 +160,4 @@ "elapsed_ms": 0.0026 } ] -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/manifest.json b/benchmarks/artifacts/latest/manifest.json index a9325c5..b285812 100644 --- a/benchmarks/artifacts/latest/manifest.json +++ b/benchmarks/artifacts/latest/manifest.json @@ -59,4 +59,4 @@ "sha256": "f31fd871990c44e24a2259d618ae40a52866d20b95aa6047af3d38b9371c2ab7" } } -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/runtime_hotspots.json b/benchmarks/artifacts/latest/runtime_hotspots.json index 6c32fe8..a56f261 100644 --- a/benchmarks/artifacts/latest/runtime_hotspots.json +++ b/benchmarks/artifacts/latest/runtime_hotspots.json @@ -93,4 +93,4 @@ "share_of_suite_pct": 0.09 } ] -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/simd.json b/benchmarks/artifacts/latest/simd.json index 01d8d65..3a64e88 100644 --- a/benchmarks/artifacts/latest/simd.json +++ b/benchmarks/artifacts/latest/simd.json @@ -282,4 +282,4 @@ ] } } -} \ No newline at end of file +} diff --git a/benchmarks/artifacts/latest/streaming.json b/benchmarks/artifacts/latest/streaming.json index c635596..e8494b6 100644 --- a/benchmarks/artifacts/latest/streaming.json +++ b/benchmarks/artifacts/latest/streaming.json @@ -70,4 +70,4 @@ "stream_over_batch_ratio": 121.7603 } ] -} \ No newline at end of file +} diff --git a/benchmarks/bench_batch.py b/benchmarks/bench_batch.py index 31d9b65..c008c41 100644 --- a/benchmarks/bench_batch.py +++ b/benchmarks/bench_batch.py @@ -48,13 +48,17 @@ def run_batch_benchmark( "SMA", lambda: ferro_ta.batch.batch_sma(close2d, timeperiod=14, parallel=True), lambda: ferro_ta.batch.batch_sma(close2d, timeperiod=14, parallel=False), - lambda: [ferro_ta.SMA(close2d[:, j], timeperiod=14) for j in range(n_series)], + lambda: [ + ferro_ta.SMA(close2d[:, j], timeperiod=14) for j in range(n_series) + ], ), ( "RSI", lambda: ferro_ta.batch.batch_rsi(close2d, timeperiod=14, parallel=True), lambda: ferro_ta.batch.batch_rsi(close2d, timeperiod=14, parallel=False), - lambda: [ferro_ta.RSI(close2d[:, j], timeperiod=14) for j in range(n_series)], + lambda: [ + ferro_ta.RSI(close2d[:, j], timeperiod=14) for j in range(n_series) + ], ), ( "ATR", @@ -201,7 +205,9 @@ def main() -> int: if payload["grouped_results"]: print("\nGrouped Multi-Indicator Calls") print("-" * 64) - print(f"{'Case':<18} {'Grouped (ms)':>14} {'Separate (ms)':>16} {'Speedup':>12}") + print( + f"{'Case':<18} {'Grouped (ms)':>14} {'Separate (ms)':>16} {'Speedup':>12}" + ) print("-" * 64) for row in payload["grouped_results"]: print( diff --git a/benchmarks/bench_derivatives_compare.py b/benchmarks/bench_derivatives_compare.py index 5ee95d0..388f200 100644 --- a/benchmarks/bench_derivatives_compare.py +++ b/benchmarks/bench_derivatives_compare.py @@ -26,9 +26,10 @@ import math import sys import time import tracemalloc +from collections.abc import Callable from dataclasses import dataclass from pathlib import Path -from typing import Any, Callable +from typing import Any import numpy as np diff --git a/benchmarks/bench_simd.py b/benchmarks/bench_simd.py index 82a27de..13340d5 100644 --- a/benchmarks/bench_simd.py +++ b/benchmarks/bench_simd.py @@ -70,9 +70,7 @@ def run_simd_benchmark( for label, args in variants } - portable_rows = { - row["name"]: row for row in reports["portable_release"]["results"] - } + portable_rows = {row["name"]: row for row in reports["portable_release"]["results"]} simd_rows = {row["name"]: row for row in reports["simd_release"]["results"]} comparison: list[dict[str, Any]] = [] @@ -136,9 +134,7 @@ def main() -> int: window=args.window, ) - print( - f"{'Case':<20} {'Portable (ms)':>14} {'SIMD (ms)':>12} {'SIMD speedup':>14}" - ) + print(f"{'Case':<20} {'Portable (ms)':>14} {'SIMD (ms)':>12} {'SIMD speedup':>14}") print("-" * 64) for row in payload["results"]: print( diff --git a/benchmarks/bench_streaming.py b/benchmarks/bench_streaming.py index 10a071d..afc78d5 100644 --- a/benchmarks/bench_streaming.py +++ b/benchmarks/bench_streaming.py @@ -57,7 +57,9 @@ def _stream_hlcv( ) -> float: streamer = factory() last = np.nan - for high_value, low_value, close_value, volume_value in zip(high, low, close, volume): + for high_value, low_value, close_value, volume_value in zip( + high, low, close, volume + ): last = streamer.update( float(high_value), float(low_value), @@ -154,7 +156,9 @@ def run_streaming_benchmark( def main() -> int: - parser = argparse.ArgumentParser(description="Benchmark streaming indicator execution.") + parser = argparse.ArgumentParser( + description="Benchmark streaming indicator execution." + ) parser.add_argument("--bars", type=int, default=100_000) parser.add_argument("--seed", type=int, default=2026) parser.add_argument("--json", dest="json_path") diff --git a/benchmarks/bench_vs_talib.py b/benchmarks/bench_vs_talib.py index 18e3738..3dae1d5 100644 --- a/benchmarks/bench_vs_talib.py +++ b/benchmarks/bench_vs_talib.py @@ -309,9 +309,13 @@ def run_comparison(sizes: list[int], json_path: str | None) -> list[dict[str, An if not TALIB_AVAILABLE: print("Note: ta-lib not installed. Reporting ferro_ta timings only.") - print("Install with: pip install ta-lib (or conda install ta-lib) for comparison.\n") + print( + "Install with: pip install ta-lib (or conda install ta-lib) for comparison.\n" + ) - print(f"\nferro_ta vs TA-Lib — median of {N_RUNS} measured runs after {N_WARMUP} warmup") + print( + f"\nferro_ta vs TA-Lib — median of {N_RUNS} measured runs after {N_WARMUP} warmup" + ) print(f"Sizes: {sizes}") print() @@ -328,11 +332,15 @@ def run_comparison(sizes: list[int], json_path: str | None) -> list[dict[str, An if size == 1_000_000 and name in SKIP_1M_FOR: continue - ft_samples_ms = _timed_runs_ms(ft_run, open_, high, low, close, volume, size) + ft_samples_ms = _timed_runs_ms( + ft_run, open_, high, low, close, volume, size + ) ft_stats = _summary_stats(ft_samples_ms) ft_median_ms = float(ft_stats["median_ms"]) ft_m_bars_s = _throughput_m_bars_s(size, ft_median_ms) - ft_peak_bytes = _python_peak_bytes(ft_run, open_, high, low, close, volume, size) + ft_peak_bytes = _python_peak_bytes( + ft_run, open_, high, low, close, volume, size + ) row: dict[str, Any] = { "indicator": name, @@ -351,11 +359,15 @@ def run_comparison(sizes: list[int], json_path: str | None) -> list[dict[str, An } if TALIB_AVAILABLE: - ta_samples_ms = _timed_runs_ms(ta_run, open_, high, low, close, volume, size) + ta_samples_ms = _timed_runs_ms( + ta_run, open_, high, low, close, volume, size + ) ta_stats = _summary_stats(ta_samples_ms) ta_median_ms = float(ta_stats["median_ms"]) ta_m_bars_s = _throughput_m_bars_s(size, ta_median_ms) - speedup = ta_median_ms / ft_median_ms if ft_median_ms > 0 else float("inf") + speedup = ( + ta_median_ms / ft_median_ms if ft_median_ms > 0 else float("inf") + ) outcome = _outcome(speedup) ta_peak_bytes = _python_peak_bytes( ta_run, open_, high, low, close, volume, size diff --git a/benchmarks/benchmark_table.py b/benchmarks/benchmark_table.py index 9845c85..c7a06bf 100644 --- a/benchmarks/benchmark_table.py +++ b/benchmarks/benchmark_table.py @@ -9,7 +9,9 @@ Reads results.json and prints a markdown table: all indicators × all libraries. Unsupported (indicator, library) pairs show N/A. Supported pairs missing benchmark data show ERR (indicating the benchmark run was incomplete or failed). """ + from __future__ import annotations + import json import sys from pathlib import Path @@ -21,9 +23,11 @@ if _root not in (Path(p).resolve() for p in sys.path): from benchmarks.wrapper_registry import ( INDICATOR_CATEGORIES, - LIBRARY_NAMES as LIBS, is_supported, ) +from benchmarks.wrapper_registry import ( + LIBRARY_NAMES as LIBS, +) def _all_indicators() -> list[str]: @@ -34,11 +38,17 @@ def _all_indicators() -> list[str]: def main(): p = Path(__file__).parent / "results.json" if not p.exists(): - print("Run: pytest benchmarks/test_speed.py --benchmark-only --benchmark-json=benchmarks/results.json -v", file=sys.stderr) + print( + "Run: pytest benchmarks/test_speed.py --benchmark-only --benchmark-json=benchmarks/results.json -v", + file=sys.stderr, + ) sys.exit(1) raw = p.read_text().strip() if not raw: - print("results.json is empty. Run the full benchmark suite first.", file=sys.stderr) + print( + "results.json is empty. Run the full benchmark suite first.", + file=sys.stderr, + ) sys.exit(1) try: data = json.loads(raw) diff --git a/benchmarks/check_vs_talib_regression.py b/benchmarks/check_vs_talib_regression.py index 2257b97..5077b72 100644 --- a/benchmarks/check_vs_talib_regression.py +++ b/benchmarks/check_vs_talib_regression.py @@ -88,7 +88,9 @@ def main() -> int: data = json.loads(path.read_text(encoding="utf-8")) if not data.get("talib_available", False): - print("ERROR: TA-Lib was not available; cannot enforce TA-Lib regression policy.") + print( + "ERROR: TA-Lib was not available; cannot enforce TA-Lib regression policy." + ) return 1 summary_by_size = { @@ -133,9 +135,7 @@ def main() -> int: ) if rows < args.min_rows: - failures.append( - f"size={size} rows {rows} < min_rows {args.min_rows}" - ) + failures.append(f"size={size} rows {rows} < min_rows {args.min_rows}") if med < median_floor.get(size, float("-inf")): failures.append( f"size={size} median_speedup {med:.4f} < floor {median_floor[size]:.4f}" diff --git a/benchmarks/profile_runtime_hotspots.py b/benchmarks/profile_runtime_hotspots.py index ba92797..bdd6fcf 100644 --- a/benchmarks/profile_runtime_hotspots.py +++ b/benchmarks/profile_runtime_hotspots.py @@ -50,11 +50,17 @@ def _naive_beta(x: np.ndarray, y: np.ndarray, window: int) -> np.ndarray: for end in range(window, len(x)): start = end - window rx = np.array( - [x[idx + 1] / x[idx] - 1.0 if x[idx] != 0.0 else np.nan for idx in range(start, end)], + [ + x[idx + 1] / x[idx] - 1.0 if x[idx] != 0.0 else np.nan + for idx in range(start, end) + ], dtype=np.float64, ) ry = np.array( - [y[idx + 1] / y[idx] - 1.0 if y[idx] != 0.0 else np.nan for idx in range(start, end)], + [ + y[idx + 1] / y[idx] - 1.0 if y[idx] != 0.0 else np.nan + for idx in range(start, end) + ], dtype=np.float64, ) mean_x = float(np.sum(rx)) / window @@ -120,7 +126,12 @@ def build_hotspot_report( high = close + rng.uniform(0.1, 2.0, price_bars) low = close - rng.uniform(0.1, 2.0, price_bars) iv = rng.uniform(10.0, 40.0, iv_bars).astype(np.float64) - ohlcv = {"close": close, "high": high, "low": low, "volume": np.full(price_bars, 1000.0)} + ohlcv = { + "close": close, + "high": high, + "low": low, + "volume": np.full(price_bars, 1000.0), + } rows = [ ( @@ -218,7 +229,9 @@ def build_hotspot_report( results.sort(key=lambda row: row["fast_ms"], reverse=True) total_fast_ms = sum(float(row["fast_ms"]) for row in results) or 1.0 for row in results: - row["share_of_suite_pct"] = round(float(row["fast_ms"]) / total_fast_ms * 100.0, 2) + row["share_of_suite_pct"] = round( + float(row["fast_ms"]) / total_fast_ms * 100.0, 2 + ) return { "metadata": benchmark_metadata( @@ -249,7 +262,9 @@ def main() -> int: window=args.window, ) - print(f"{'Category':<16} {'Case':<18} {'Fast (ms)':>10} {'Ref (ms)':>10} {'Speedup':>10}") + print( + f"{'Category':<16} {'Case':<18} {'Fast (ms)':>10} {'Ref (ms)':>10} {'Speedup':>10}" + ) print("-" * 70) for row in payload["results"]: print( diff --git a/benchmarks/results.json b/benchmarks/results.json index 728e3b7..834428c 100644 --- a/benchmarks/results.json +++ b/benchmarks/results.json @@ -16094,4 +16094,4 @@ ], "datetime": "2026-03-23T17:14:05.427766+00:00", "version": "5.2.3" -} \ No newline at end of file +} diff --git a/benchmarks/run_perf_contract.py b/benchmarks/run_perf_contract.py index 682fbd7..2a926fa 100644 --- a/benchmarks/run_perf_contract.py +++ b/benchmarks/run_perf_contract.py @@ -52,7 +52,9 @@ def build_indicator_latency_report(*, rounds: int = 5) -> dict[str, Any]: rows: list[dict[str, Any]] = [] for entry in INDICATOR_SUITE: - elapsed_ms = _time_min(lambda entry=entry: _run_indicator(entry, ohlcv), rounds=rounds) + elapsed_ms = _time_min( + lambda entry=entry: _run_indicator(entry, ohlcv), rounds=rounds + ) rows.append( { "name": entry["name"], @@ -192,10 +194,7 @@ def main() -> int: fixtures=[FIXTURE_PATH], extra={"output_dir": str(output_dir)}, ), - "artifacts": { - name: file_info(path) - for name, path in artifacts.items() - }, + "artifacts": {name: file_info(path) for name, path in artifacts.items()}, } manifest_path = output_dir / "manifest.json" _write_json(manifest_path, manifest) diff --git a/benchmarks/test_accuracy.py b/benchmarks/test_accuracy.py index 9d71187..c2d2e73 100644 --- a/benchmarks/test_accuracy.py +++ b/benchmarks/test_accuracy.py @@ -5,65 +5,66 @@ For each indicator we compare ferro_ta output against every available reference Tolerances are based on known algorithmic differences (e.g. Wilder vs SMA seed). We only compare the overlapping (valid) suffix of each output array. """ + from __future__ import annotations + import numpy as np import pytest from benchmarks.data_generator import MEDIUM from benchmarks.wrapper_registry import ( - execute_indicator, - INDICATOR_NAMES, - INDICATOR_CATEGORIES, - CUMULATIVE_INDICATORS, BINARY_INDICATORS, + CUMULATIVE_INDICATORS, + INDICATOR_CATEGORIES, + INDICATOR_NAMES, available_libraries, + execute_indicator, is_supported, ) # Reference = ferro_ta; compare against each library that has a non-empty result. REFERENCE_LIB = "ferro_ta" -COMPARISON_LIBS = [l for l in available_libraries() if l != REFERENCE_LIB] +COMPARISON_LIBS = [ + library for library in available_libraries() if library != REFERENCE_LIB +] # Per-indicator tolerances (rtol, atol) _TOLERANCES: dict[str, tuple[float, float]] = { - "ATR": (1e-3, 0.05), # Wilder's smoothing seed differs - "NATR": (1e-3, 0.10), - "BBANDS": (1e-3, 0.20), # ddof=0 vs ddof=1 + "ATR": (1e-3, 0.05), # Wilder's smoothing seed differs + "NATR": (1e-3, 0.10), + "BBANDS": (1e-3, 0.20), # ddof=0 vs ddof=1 "STDDEV": (1e-3, 0.20), - "VAR": (1e-3, 0.50), - "MACD": (1e-3, 1e-3), # double EMA seed - "KAMA": (1e-3, 1e-3), - "STOCH": (1e-3, 0.10), # smoothing method differences - "SAR": (1e-3, 0.20), - "ADOSC": (1e-3, 0.20), - "ADX": (1e-3, 0.50), # Wilder's ADX - "PLUS_DI":(1e-3, 0.50), - "MINUS_DI":(1e-3, 0.50), - "PPO": (1e-2, 1e-3), - "CMO": (1e-3, 0.10), - "TRIX": (1e-3, 1e-3), - "CCI": (1e-3, 0.10), + "VAR": (1e-3, 0.50), + "MACD": (1e-3, 1.00), # seed differences across libraries + "KAMA": (1e-3, 1e-3), + "STOCH": (1e-3, 0.10), # smoothing method differences + "SAR": (1e-3, 0.20), + "ADOSC": (1e-3, 0.20), + "ADX": (1e-3, 0.50), # Wilder's ADX + "PLUS_DI": (1e-3, 0.50), + "MINUS_DI": (1e-3, 0.50), + "PPO": (1e-2, 1e-3), + "CMO": (1e-3, 0.10), + "TRIX": (1e-3, 0.05), + "CCI": (1e-3, 0.10), "SUPERTREND": (1e-2, 0.50), "KELTNER_CHANNELS": (1e-2, 0.50), "DONCHIAN": (1e-4, 1e-4), - "HT_DCPERIOD": (1e-2, 1.0), - "VWAP": (1e-3, 0.10), - "AROON": (1e-4, 1e-3), + "HT_DCPERIOD": (1e-2, 2.0), + "VWAP": (1e-3, 0.10), + "AROON": (1e-4, 1e-3), "LINEARREG": (1e-4, 1e-4), "LINEARREG_SLOPE": (1e-4, 1e-4), "CORREL": (1e-4, 1e-3), - "BETA": (1e-3, 1e-3), - "TSF": (1e-4, 1e-4), - "EMA": (1e-3, 0.30), # ta library uses different EMA seed - "DEMA": (1e-3, 0.50), - "TEMA": (1e-3, 0.50), - "T3": (1e-3, 0.50), - "HULL_MA":(1e-3, 0.10), - "WMA": (1e-4, 1e-4), - "TRIMA": (1e-4, 1e-4), - "MACD": (1e-3, 1.00), # seed differences across libraries - "TRIX": (1e-3, 0.05), - "HT_DCPERIOD": (1e-2, 2.0), + "BETA": (1e-3, 1e-3), + "TSF": (1e-4, 1e-4), + "EMA": (1e-3, 0.30), # ta library uses different EMA seed + "DEMA": (1e-3, 0.50), + "TEMA": (1e-3, 0.50), + "T3": (1e-3, 0.50), + "HULL_MA": (1e-3, 0.10), + "WMA": (1e-4, 1e-4), + "TRIMA": (1e-4, 1e-4), } _DEFAULT_TOL = (1e-4, 1e-5) @@ -71,33 +72,33 @@ _DEFAULT_TOL = (1e-4, 1e-5) # Pairs that use correlation check (>=0.95) due to known algorithmic divergence # Format: (indicator, library) or just indicator (applies to all libs) _CORRELATION_PAIRS: set[tuple[str, str]] = { - ("PPO", "talib"), # different PPO formula normalization + ("PPO", "talib"), # different PPO formula normalization ("PPO", "pandas_ta"), ("PPO", "tulipy"), ("STOCH", "ta"), ("SUPERTREND", "pandas_ta"), ("KELTNER_CHANNELS", "pandas_ta"), ("KELTNER_CHANNELS", "ta"), - ("EMA", "finta"), # finta EMA uses different initialization - ("KAMA", "pandas_ta"), # pandas_ta KAMA has slightly different seed - ("RSI", "ta"), # ta uses SMA warmup vs Wilder - ("RSI", "finta"), # same + ("EMA", "finta"), # finta EMA uses different initialization + ("KAMA", "pandas_ta"), # pandas_ta KAMA has slightly different seed + ("RSI", "ta"), # ta uses SMA warmup vs Wilder + ("RSI", "finta"), # same } # Pairs that are skipped because they are structurally incompatible _SKIP_PAIRS: set[tuple[str, str]] = { - ("BBANDS", "finta"), # finta normalizes band differently - ("ATR", "finta"), # finta ATR uses simple TR not Wilder - ("STDDEV", "finta"), # finta uses population std - ("TRIMA", "finta"), # finta TRIMA uses different formula - ("PPO", "finta"), # finta PPO scaling incompatible - ("STOCH", "finta"), # finta STOCH formula differs - ("VWAP", "pandas_ta"), # pandas_ta VWAP anchors to session start + ("BBANDS", "finta"), # finta normalizes band differently + ("ATR", "finta"), # finta ATR uses simple TR not Wilder + ("STDDEV", "finta"), # finta uses population std + ("TRIMA", "finta"), # finta TRIMA uses different formula + ("PPO", "finta"), # finta PPO scaling incompatible + ("STOCH", "finta"), # finta STOCH formula differs + ("VWAP", "pandas_ta"), # pandas_ta VWAP anchors to session start ("HT_TRENDMODE", "talib"), # binary; Hilbert seed diverges - ("CMO", "talib"), # ferro_ta CMO smoothing variant corr < 0.90 + ("CMO", "talib"), # ferro_ta CMO smoothing variant corr < 0.90 ("CMO", "pandas_ta"), ("CMO", "finta"), - ("PLUS_DI", "pandas_ta"), # pandas_ta ADX column naming corr < 0.70 + ("PLUS_DI", "pandas_ta"), # pandas_ta ADX column naming corr < 0.70 } MIN_OVERLAP = 30 # minimum points to make comparison meaningful @@ -114,12 +115,14 @@ def _compare(ref: np.ndarray, cmp: np.ndarray, indicator: str, library: str) -> c = cmp[-n:] if indicator in BINARY_INDICATORS or (indicator, library) in _CORRELATION_PAIRS: # Use correlation check for structurally different algorithms - corr = np.corrcoef(r, c)[0, 1] if not indicator in BINARY_INDICATORS else None + corr = np.corrcoef(r, c)[0, 1] if indicator not in BINARY_INDICATORS else None if indicator in BINARY_INDICATORS: agree = np.mean(r == c) assert agree >= 0.80, f"Binary agreement {agree:.1%} < 80%" else: - assert corr >= 0.90, f"Correlation {corr:.4f} < 0.90 (structural divergence)" + assert corr >= 0.90, ( + f"Correlation {corr:.4f} < 0.90 (structural divergence)" + ) elif indicator in CUMULATIVE_INDICATORS: dr, dc = np.diff(r), np.diff(c) if len(dr) < 5 or len(dc) < 5: @@ -136,6 +139,7 @@ def _compare(ref: np.ndarray, cmp: np.ndarray, indicator: str, library: str) -> # ── dynamically generate one test per (indicator, library) pair ───────────── + def pytest_generate_tests(metafunc): if "indicator" in metafunc.fixturenames and "library" in metafunc.fixturenames: params = [] @@ -172,6 +176,7 @@ class TestAccuracy: # ── quick smoke tests that always run (no skip) ────────────────────────────── + class TestSmoke: """Sanity checks that ferro_ta returns non-empty finite arrays.""" @@ -182,7 +187,9 @@ class TestSmoke: arr = execute_indicator("ferro_ta", indicator, MEDIUM) assert len(arr) > 0, f"ferro_ta {indicator} returned empty array" - assert np.all(np.isfinite(arr)), f"ferro_ta {indicator} has non-finite values: {arr[~np.isfinite(arr)][:5]}" + assert np.all(np.isfinite(arr)), ( + f"ferro_ta {indicator} has non-finite values: {arr[~np.isfinite(arr)][:5]}" + ) @pytest.mark.parametrize("category,indicators", INDICATOR_CATEGORIES.items()) def test_category_coverage(self, category, indicators): diff --git a/benchmarks/test_speed.py b/benchmarks/test_speed.py index a480be9..f6fbb8d 100644 --- a/benchmarks/test_speed.py +++ b/benchmarks/test_speed.py @@ -6,31 +6,36 @@ Run: pytest benchmarks/test_speed.py --benchmark-only -v Streaming benchmarks are in test_streaming_speed.py """ + from __future__ import annotations + import pytest from benchmarks.data_generator import LARGE from benchmarks.wrapper_registry import ( - execute_indicator, INDICATOR_CATEGORIES, available_libraries, + execute_indicator, is_supported, ) -BENCH_DATA = LARGE # 100k bars for main benchmarks -BENCH_LIBS = available_libraries() +BENCH_DATA = LARGE # 100k bars for main benchmarks +BENCH_LIBS = available_libraries() def _make_bench(indicator: str, library: str): """Return a benchmark function that runs indicator on library (uses BENCH_DATA).""" + def _fn(): execute_indicator(library, indicator, BENCH_DATA) + _fn.__name__ = f"{library}_{indicator}" return _fn # ── Parametrize over all (indicator, library) combinations ─────────────────── + def pytest_generate_tests(metafunc): if "indicator" in metafunc.fixturenames and "library" in metafunc.fixturenames: params = [] @@ -53,20 +58,24 @@ class TestSpeed: # ── Standalone head-to-head for the most important indicators ───────────────── -@pytest.mark.parametrize("indicator,libs", [ - ("SMA", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("EMA", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("RSI", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("MACD", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("BBANDS",["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("ATR", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("CCI", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("WILLR", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("OBV", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("ADX", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("MFI", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), - ("STOCH", ["ferro_ta","talib","tulipy","pandas_ta","ta","finta"]), -]) + +@pytest.mark.parametrize( + "indicator,libs", + [ + ("SMA", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("EMA", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("RSI", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("MACD", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("BBANDS", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("ATR", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("CCI", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("WILLR", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("OBV", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("ADX", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("MFI", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ("STOCH", ["ferro_ta", "talib", "tulipy", "pandas_ta", "ta", "finta"]), + ], +) def test_head_to_head(benchmark, indicator, libs): """Benchmark ferro_ta vs all peers — for README table generation.""" if not is_supported("ferro_ta", indicator): @@ -77,7 +86,11 @@ def test_head_to_head(benchmark, indicator, libs): # ── Large dataset benchmarks (100k bars) ───────────────────────────────────── -@pytest.mark.parametrize("indicator", ["SMA","EMA","RSI","MACD","ATR","BBANDS","OBV","CCI","ADX","MFI"]) + +@pytest.mark.parametrize( + "indicator", + ["SMA", "EMA", "RSI", "MACD", "ATR", "BBANDS", "OBV", "CCI", "ADX", "MFI"], +) def test_large_dataset(benchmark, indicator): """Scaling benchmark at 100k bars for ferro_ta.""" if not is_supported("ferro_ta", indicator): diff --git a/benchmarks/wrapper_registry.py b/benchmarks/wrapper_registry.py index 723e950..77ecde9 100644 --- a/benchmarks/wrapper_registry.py +++ b/benchmarks/wrapper_registry.py @@ -5,764 +5,2453 @@ Unified interface: execute_indicator(library, indicator, data, df=None, **kwargs Supported libraries: ferro_ta, talib, pandas_ta, ta, tulipy, finta 50+ indicators across all categories. """ + from __future__ import annotations + from typing import Any + import numpy as np + def _try_import(name): try: - import importlib; return importlib.import_module(name) - except ImportError: return None + import importlib + + return importlib.import_module(name) + except ImportError: + return None + _talib = _try_import("talib") -_pta = _try_import("pandas_ta") -_ta = _try_import("ta") -_tl = _try_import("tulipy") -_fi_m = _try_import("finta") -_fi = getattr(_fi_m, "TA", None) if _fi_m else None +_pta = _try_import("pandas_ta") +_ta = _try_import("ta") +_tl = _try_import("tulipy") +_fi_m = _try_import("finta") +_fi = getattr(_fi_m, "TA", None) if _fi_m else None + def available_libraries(): libs = ["ferro_ta"] - if _talib: libs.append("talib") - if _pta: libs.append("pandas_ta") - if _ta: libs.append("ta") - if _tl: libs.append("tulipy") - if _fi: libs.append("finta") + if _talib: + libs.append("talib") + if _pta: + libs.append("pandas_ta") + if _ta: + libs.append("ta") + if _tl: + libs.append("tulipy") + if _fi: + libs.append("finta") return libs + def is_supported(library: str, indicator: str) -> bool: """Return True if a wrapper exists for the given (library, indicator) pair.""" if library not in available_libraries(): return False return (library, indicator) in REGISTRY + def _strip_nan(arr): a = np.asarray(arr, dtype=np.float64).ravel() return a[np.isfinite(a)] + def _c64(a): return np.ascontiguousarray(a, dtype=np.float64) + def _empty(): return np.array([], dtype=np.float64) + def _first_col(df, prefix): col = next((c for c in df.columns if c.startswith(prefix)), None) return _strip_nan(df[col].values) if col is not None else _empty() + # ============================================================ # OVERLAP # ============================================================ -def _sma_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.SMA(d["close"],timeperiod=timeperiod)) -def _sma_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.SMA(d["close"],timeperiod=timeperiod)) -def _sma_pt(d,df,timeperiod=20,**_): return _strip_nan(_pta.sma(df["close"],length=timeperiod).values) -def _sma_ta(d,df,timeperiod=20,**_): - from ta.trend import SMAIndicator; return _strip_nan(SMAIndicator(df["close"],window=timeperiod).sma_indicator().values) -def _sma_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.sma(_c64(d["close"]),period=timeperiod)) -def _sma_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.SMA(df,timeperiod).values) +def _sma_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.SMA(d["close"], timeperiod=timeperiod)) + + +def _sma_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.SMA(d["close"], timeperiod=timeperiod)) + + +def _sma_pt(d, df, timeperiod=20, **_): + return _strip_nan(_pta.sma(df["close"], length=timeperiod).values) + + +def _sma_ta(d, df, timeperiod=20, **_): + from ta.trend import SMAIndicator + + return _strip_nan( + SMAIndicator(df["close"], window=timeperiod).sma_indicator().values + ) + + +def _sma_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.sma(_c64(d["close"]), period=timeperiod)) + + +def _sma_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.SMA(df, timeperiod).values) + + +def _ema_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.EMA(d["close"], timeperiod=timeperiod)) + + +def _ema_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.EMA(d["close"], timeperiod=timeperiod)) + + +def _ema_pt(d, df, timeperiod=20, **_): + return _strip_nan(_pta.ema(df["close"], length=timeperiod).values) + + +def _ema_ta(d, df, timeperiod=20, **_): + from ta.trend import EMAIndicator + + return _strip_nan( + EMAIndicator(df["close"], window=timeperiod).ema_indicator().values + ) + + +def _ema_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.ema(_c64(d["close"]), period=timeperiod)) + + +def _ema_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.EMA(df, timeperiod).values) + + +def _wma_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.WMA(d["close"], timeperiod=timeperiod)) + + +def _wma_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.WMA(d["close"], timeperiod=timeperiod)) + + +def _wma_pt(d, df, timeperiod=14, **_): + return _strip_nan(_pta.wma(df["close"], length=timeperiod).values) + + +def _wma_ta(d, df, **_): + return _empty() -def _ema_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.EMA(d["close"],timeperiod=timeperiod)) -def _ema_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.EMA(d["close"],timeperiod=timeperiod)) -def _ema_pt(d,df,timeperiod=20,**_): return _strip_nan(_pta.ema(df["close"],length=timeperiod).values) -def _ema_ta(d,df,timeperiod=20,**_): - from ta.trend import EMAIndicator; return _strip_nan(EMAIndicator(df["close"],window=timeperiod).ema_indicator().values) -def _ema_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.ema(_c64(d["close"]),period=timeperiod)) -def _ema_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.EMA(df,timeperiod).values) -def _wma_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.WMA(d["close"],timeperiod=timeperiod)) -def _wma_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.WMA(d["close"],timeperiod=timeperiod)) -def _wma_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.wma(df["close"],length=timeperiod).values) -def _wma_ta(d,df,**_): return _empty() _wma_ta._stub = True -def _wma_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.wma(_c64(d["close"]),period=timeperiod)) -def _wma_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.WMA(df,timeperiod).values) -def _dema_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.DEMA(d["close"],timeperiod=timeperiod)) -def _dema_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.DEMA(d["close"],timeperiod=timeperiod)) -def _dema_pt(d,df,timeperiod=20,**_): return _strip_nan(_pta.dema(df["close"],length=timeperiod).values) -def _dema_ta(d,df,**_): return _empty() + +def _wma_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.wma(_c64(d["close"]), period=timeperiod)) + + +def _wma_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.WMA(df, timeperiod).values) + + +def _dema_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.DEMA(d["close"], timeperiod=timeperiod)) + + +def _dema_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.DEMA(d["close"], timeperiod=timeperiod)) + + +def _dema_pt(d, df, timeperiod=20, **_): + return _strip_nan(_pta.dema(df["close"], length=timeperiod).values) + + +def _dema_ta(d, df, **_): + return _empty() + + _dema_ta._stub = True -def _dema_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.dema(_c64(d["close"]),period=timeperiod)) -def _dema_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.DEMA(df,timeperiod).values) -def _tema_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.TEMA(d["close"],timeperiod=timeperiod)) -def _tema_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.TEMA(d["close"],timeperiod=timeperiod)) -def _tema_pt(d,df,timeperiod=20,**_): return _strip_nan(_pta.tema(df["close"],length=timeperiod).values) -def _tema_ta(d,df,**_): return _empty() + +def _dema_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.dema(_c64(d["close"]), period=timeperiod)) + + +def _dema_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.DEMA(df, timeperiod).values) + + +def _tema_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TEMA(d["close"], timeperiod=timeperiod)) + + +def _tema_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.TEMA(d["close"], timeperiod=timeperiod)) + + +def _tema_pt(d, df, timeperiod=20, **_): + return _strip_nan(_pta.tema(df["close"], length=timeperiod).values) + + +def _tema_ta(d, df, **_): + return _empty() + + _tema_ta._stub = True -def _tema_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.tema(_c64(d["close"]),period=timeperiod)) -def _tema_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.TEMA(df,timeperiod).values) -def _t3_ft(d,df,timeperiod=5,**_): - import ferro_ta; return _strip_nan(ferro_ta.T3(d["close"],timeperiod=timeperiod)) -def _t3_tl(d,df,timeperiod=5,**_): return _strip_nan(_talib.T3(d["close"],timeperiod=timeperiod)) -def _t3_pt(d,df,timeperiod=5,**_): return _strip_nan(_pta.t3(df["close"],length=timeperiod).values) -def _t3_ta(d,df,**_): return _empty() + +def _tema_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.tema(_c64(d["close"]), period=timeperiod)) + + +def _tema_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.TEMA(df, timeperiod).values) + + +def _t3_ft(d, df, timeperiod=5, **_): + import ferro_ta + + return _strip_nan(ferro_ta.T3(d["close"], timeperiod=timeperiod)) + + +def _t3_tl(d, df, timeperiod=5, **_): + return _strip_nan(_talib.T3(d["close"], timeperiod=timeperiod)) + + +def _t3_pt(d, df, timeperiod=5, **_): + return _strip_nan(_pta.t3(df["close"], length=timeperiod).values) + + +def _t3_ta(d, df, **_): + return _empty() + + _t3_ta._stub = True -def _t3_tu(d,df,**_): return _empty() + + +def _t3_tu(d, df, **_): + return _empty() + + _t3_tu._stub = True -def _t3_fi(d,df,**_): return _empty() + + +def _t3_fi(d, df, **_): + return _empty() + + _t3_fi._stub = True -def _trima_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.TRIMA(d["close"],timeperiod=timeperiod)) -def _trima_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.TRIMA(d["close"],timeperiod=timeperiod)) -def _trima_pt(d,df,timeperiod=20,**_): return _strip_nan(_pta.trima(df["close"],length=timeperiod).values) -def _trima_ta(d,df,**_): return _empty() -_trima_ta._stub = True -def _trima_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.trima(_c64(d["close"]),period=timeperiod)) -def _trima_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.TRIMA(df,timeperiod).values) -def _kama_ft(d,df,timeperiod=10,**_): - import ferro_ta; return _strip_nan(ferro_ta.KAMA(d["close"],timeperiod=timeperiod)) -def _kama_tl(d,df,timeperiod=10,**_): return _strip_nan(_talib.KAMA(d["close"],timeperiod=timeperiod)) -def _kama_pt(d,df,timeperiod=10,**_): return _strip_nan(_pta.kama(df["close"],length=timeperiod).values) -def _kama_ta(d,df,**_): return _empty() +def _trima_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TRIMA(d["close"], timeperiod=timeperiod)) + + +def _trima_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.TRIMA(d["close"], timeperiod=timeperiod)) + + +def _trima_pt(d, df, timeperiod=20, **_): + return _strip_nan(_pta.trima(df["close"], length=timeperiod).values) + + +def _trima_ta(d, df, **_): + return _empty() + + +_trima_ta._stub = True + + +def _trima_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.trima(_c64(d["close"]), period=timeperiod)) + + +def _trima_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.TRIMA(df, timeperiod).values) + + +def _kama_ft(d, df, timeperiod=10, **_): + import ferro_ta + + return _strip_nan(ferro_ta.KAMA(d["close"], timeperiod=timeperiod)) + + +def _kama_tl(d, df, timeperiod=10, **_): + return _strip_nan(_talib.KAMA(d["close"], timeperiod=timeperiod)) + + +def _kama_pt(d, df, timeperiod=10, **_): + return _strip_nan(_pta.kama(df["close"], length=timeperiod).values) + + +def _kama_ta(d, df, **_): + return _empty() + + _kama_ta._stub = True -def _kama_tu(d,df,timeperiod=10,**_): return _strip_nan(_tl.kama(_c64(d["close"]),period=timeperiod)) -def _kama_fi(d,df,**_): return _empty() + + +def _kama_tu(d, df, timeperiod=10, **_): + return _strip_nan(_tl.kama(_c64(d["close"]), period=timeperiod)) + + +def _kama_fi(d, df, **_): + return _empty() + + _kama_fi._stub = True -def _hma_ft(d,df,timeperiod=16,**_): - import ferro_ta; return _strip_nan(ferro_ta.HULL_MA(d["close"],timeperiod=timeperiod)) -def _hma_tl(d,df,**_): return _empty() -_hma_tl._stub = True -def _hma_pt(d,df,timeperiod=16,**_): return _strip_nan(_pta.hma(df["close"],length=timeperiod).values) -def _hma_ta(d,df,**_): return _empty() -_hma_ta._stub = True -def _hma_tu(d,df,timeperiod=16,**_): return _strip_nan(_tl.hma(_c64(d["close"]),period=timeperiod)) -def _hma_fi(d,df,timeperiod=16,**_): return _strip_nan(_fi.HMA(df,timeperiod).values) -def _vwma_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.VWMA(d["close"],d["volume"],timeperiod=timeperiod)) -def _vwma_tl(d,df,**_): return _empty() +def _hma_ft(d, df, timeperiod=16, **_): + import ferro_ta + + return _strip_nan(ferro_ta.HULL_MA(d["close"], timeperiod=timeperiod)) + + +def _hma_tl(d, df, **_): + return _empty() + + +_hma_tl._stub = True + + +def _hma_pt(d, df, timeperiod=16, **_): + return _strip_nan(_pta.hma(df["close"], length=timeperiod).values) + + +def _hma_ta(d, df, **_): + return _empty() + + +_hma_ta._stub = True + + +def _hma_tu(d, df, timeperiod=16, **_): + return _strip_nan(_tl.hma(_c64(d["close"]), period=timeperiod)) + + +def _hma_fi(d, df, timeperiod=16, **_): + return _strip_nan(_fi.HMA(df, timeperiod).values) + + +def _vwma_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.VWMA(d["close"], d["volume"], timeperiod=timeperiod)) + + +def _vwma_tl(d, df, **_): + return _empty() + + _vwma_tl._stub = True -def _vwma_pt(d,df,timeperiod=20,**_): - r=_pta.vwma(df["close"],df["volume"],length=timeperiod); return _strip_nan(r.values) if r is not None else _empty() -def _vwma_ta(d,df,**_): return _empty() + + +def _vwma_pt(d, df, timeperiod=20, **_): + r = _pta.vwma(df["close"], df["volume"], length=timeperiod) + return _strip_nan(r.values) if r is not None else _empty() + + +def _vwma_ta(d, df, **_): + return _empty() + + _vwma_ta._stub = True -def _vwma_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.vwma(_c64(d["close"]),_c64(d["volume"]),period=timeperiod)) -def _vwma_fi(d,df,**_): return _empty() + + +def _vwma_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.vwma(_c64(d["close"]), _c64(d["volume"]), period=timeperiod)) + + +def _vwma_fi(d, df, **_): + return _empty() + + _vwma_fi._stub = True -def _midpoint_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.MIDPOINT(d["close"],timeperiod=timeperiod)) -def _midpoint_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.MIDPOINT(d["close"],timeperiod=timeperiod)) -def _midpoint_pt(d,df,**_): return _empty() + +def _midpoint_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.MIDPOINT(d["close"], timeperiod=timeperiod)) + + +def _midpoint_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.MIDPOINT(d["close"], timeperiod=timeperiod)) + + +def _midpoint_pt(d, df, **_): + return _empty() + + _midpoint_pt._stub = True -def _midpoint_ta(d,df,**_): return _empty() + + +def _midpoint_ta(d, df, **_): + return _empty() + + _midpoint_ta._stub = True -def _midpoint_tu(d,df,**_): return _empty() + + +def _midpoint_tu(d, df, **_): + return _empty() + + _midpoint_tu._stub = True -def _midpoint_fi(d,df,**_): return _empty() + + +def _midpoint_fi(d, df, **_): + return _empty() + + _midpoint_fi._stub = True -def _midprice_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.MIDPRICE(d["high"],d["low"],timeperiod=timeperiod)) -def _midprice_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.MIDPRICE(d["high"],d["low"],timeperiod=timeperiod)) -def _midprice_pt(d,df,**_): return _empty() + +def _midprice_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.MIDPRICE(d["high"], d["low"], timeperiod=timeperiod)) + + +def _midprice_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.MIDPRICE(d["high"], d["low"], timeperiod=timeperiod)) + + +def _midprice_pt(d, df, **_): + return _empty() + + _midprice_pt._stub = True -def _midprice_ta(d,df,**_): return _empty() + + +def _midprice_ta(d, df, **_): + return _empty() + + _midprice_ta._stub = True -def _midprice_tu(d,df,**_): return _empty() + + +def _midprice_tu(d, df, **_): + return _empty() + + _midprice_tu._stub = True -def _midprice_fi(d,df,**_): return _empty() + + +def _midprice_fi(d, df, **_): + return _empty() + + _midprice_fi._stub = True + # ============================================================ # MOMENTUM # ============================================================ -def _rsi_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.RSI(d["close"],timeperiod=timeperiod)) -def _rsi_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.RSI(d["close"],timeperiod=timeperiod)) -def _rsi_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.rsi(df["close"],length=timeperiod).values) -def _rsi_ta(d,df,timeperiod=14,**_): - from ta.momentum import RSIIndicator; return _strip_nan(RSIIndicator(df["close"],window=timeperiod).rsi().values) -def _rsi_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.rsi(_c64(d["close"]),period=timeperiod)) -def _rsi_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.RSI(df,timeperiod).values) +def _rsi_ft(d, df, timeperiod=14, **_): + import ferro_ta -def _macd_ft(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - import ferro_ta; m,s,h=ferro_ta.MACD(d["close"],fastperiod=fastperiod,slowperiod=slowperiod,signalperiod=signalperiod); return _strip_nan(m) -def _macd_tl(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - m,s,h=_talib.MACD(d["close"],fastperiod=fastperiod,slowperiod=slowperiod,signalperiod=signalperiod); return _strip_nan(m) -def _macd_pt(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - r=_pta.macd(df["close"],fast=fastperiod,slow=slowperiod,signal=signalperiod); return _first_col(r,"MACD_") -def _macd_ta(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - from ta.trend import MACD; return _strip_nan(MACD(df["close"],window_fast=fastperiod,window_slow=slowperiod,window_sign=signalperiod).macd().values) -def _macd_tu(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - m,s,h=_tl.macd(_c64(d["close"]),short_period=fastperiod,long_period=slowperiod,signal_period=signalperiod); return _strip_nan(m) -def _macd_fi(d,df,fastperiod=12,slowperiod=26,signalperiod=9,**_): - return _strip_nan(_fi.MACD(df,fastperiod,slowperiod,signalperiod)["MACD"].values) + return _strip_nan(ferro_ta.RSI(d["close"], timeperiod=timeperiod)) -def _stoch_ft(d,df,fastk_period=14,slowk_period=3,slowd_period=3,**_): - import ferro_ta; k,dd=ferro_ta.STOCH(d["high"],d["low"],d["close"],fastk_period=fastk_period,slowk_period=slowk_period,slowd_period=slowd_period); return _strip_nan(k) -def _stoch_tl(d,df,fastk_period=14,slowk_period=3,slowd_period=3,**_): - k,dd=_talib.STOCH(d["high"],d["low"],d["close"],fastk_period=fastk_period,slowk_period=slowk_period,slowd_period=slowd_period); return _strip_nan(k) -def _stoch_pt(d,df,fastk_period=14,slowk_period=3,slowd_period=3,**_): - r=_pta.stoch(df["high"],df["low"],df["close"],k=fastk_period,d=slowd_period) - return _first_col(r,"STOCHk_") if r is not None else _empty() -def _stoch_ta(d,df,fastk_period=14,**_): - from ta.momentum import StochasticOscillator; return _strip_nan(StochasticOscillator(df["high"],df["low"],df["close"],window=fastk_period).stoch().values) -def _stoch_tu(d,df,fastk_period=14,slowk_period=3,slowd_period=3,**_): - k,dd=_tl.stoch(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),pct_k_period=fastk_period,pct_k_slowing_period=slowk_period,pct_d_period=slowd_period); return _strip_nan(k) -def _stoch_fi(d,df,fastk_period=14,**_): return _strip_nan(_fi.STOCH(df,fastk_period).values) -def _cci_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.CCI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _cci_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.CCI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _cci_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.cci(df["high"],df["low"],df["close"],length=timeperiod).values) -def _cci_ta(d,df,timeperiod=14,**_): - from ta.trend import CCIIndicator; return _strip_nan(CCIIndicator(df["high"],df["low"],df["close"],window=timeperiod).cci().values) -def _cci_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.cci(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod)) -def _cci_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.CCI(df,timeperiod).values) +def _rsi_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.RSI(d["close"], timeperiod=timeperiod)) + + +def _rsi_pt(d, df, timeperiod=14, **_): + return _strip_nan(_pta.rsi(df["close"], length=timeperiod).values) + + +def _rsi_ta(d, df, timeperiod=14, **_): + from ta.momentum import RSIIndicator + + return _strip_nan(RSIIndicator(df["close"], window=timeperiod).rsi().values) + + +def _rsi_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.rsi(_c64(d["close"]), period=timeperiod)) + + +def _rsi_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.RSI(df, timeperiod).values) + + +def _macd_ft(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + import ferro_ta + + m, s, h = ferro_ta.MACD( + d["close"], + fastperiod=fastperiod, + slowperiod=slowperiod, + signalperiod=signalperiod, + ) + return _strip_nan(m) + + +def _macd_tl(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + m, s, h = _talib.MACD( + d["close"], + fastperiod=fastperiod, + slowperiod=slowperiod, + signalperiod=signalperiod, + ) + return _strip_nan(m) + + +def _macd_pt(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + r = _pta.macd(df["close"], fast=fastperiod, slow=slowperiod, signal=signalperiod) + return _first_col(r, "MACD_") + + +def _macd_ta(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + from ta.trend import MACD + + return _strip_nan( + MACD( + df["close"], + window_fast=fastperiod, + window_slow=slowperiod, + window_sign=signalperiod, + ) + .macd() + .values + ) + + +def _macd_tu(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + m, s, h = _tl.macd( + _c64(d["close"]), + short_period=fastperiod, + long_period=slowperiod, + signal_period=signalperiod, + ) + return _strip_nan(m) + + +def _macd_fi(d, df, fastperiod=12, slowperiod=26, signalperiod=9, **_): + return _strip_nan(_fi.MACD(df, fastperiod, slowperiod, signalperiod)["MACD"].values) + + +def _stoch_ft(d, df, fastk_period=14, slowk_period=3, slowd_period=3, **_): + import ferro_ta + + k, dd = ferro_ta.STOCH( + d["high"], + d["low"], + d["close"], + fastk_period=fastk_period, + slowk_period=slowk_period, + slowd_period=slowd_period, + ) + return _strip_nan(k) + + +def _stoch_tl(d, df, fastk_period=14, slowk_period=3, slowd_period=3, **_): + k, dd = _talib.STOCH( + d["high"], + d["low"], + d["close"], + fastk_period=fastk_period, + slowk_period=slowk_period, + slowd_period=slowd_period, + ) + return _strip_nan(k) + + +def _stoch_pt(d, df, fastk_period=14, slowk_period=3, slowd_period=3, **_): + r = _pta.stoch(df["high"], df["low"], df["close"], k=fastk_period, d=slowd_period) + return _first_col(r, "STOCHk_") if r is not None else _empty() + + +def _stoch_ta(d, df, fastk_period=14, **_): + from ta.momentum import StochasticOscillator + + return _strip_nan( + StochasticOscillator(df["high"], df["low"], df["close"], window=fastk_period) + .stoch() + .values + ) + + +def _stoch_tu(d, df, fastk_period=14, slowk_period=3, slowd_period=3, **_): + k, dd = _tl.stoch( + _c64(d["high"]), + _c64(d["low"]), + _c64(d["close"]), + pct_k_period=fastk_period, + pct_k_slowing_period=slowk_period, + pct_d_period=slowd_period, + ) + return _strip_nan(k) + + +def _stoch_fi(d, df, fastk_period=14, **_): + return _strip_nan(_fi.STOCH(df, fastk_period).values) + + +def _cci_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.CCI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _cci_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.CCI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _cci_pt(d, df, timeperiod=14, **_): + return _strip_nan( + _pta.cci(df["high"], df["low"], df["close"], length=timeperiod).values + ) + + +def _cci_ta(d, df, timeperiod=14, **_): + from ta.trend import CCIIndicator + + return _strip_nan( + CCIIndicator(df["high"], df["low"], df["close"], window=timeperiod).cci().values + ) + + +def _cci_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.cci(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod) + ) + + +def _cci_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.CCI(df, timeperiod).values) + + +def _willr_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.WILLR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _willr_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.WILLR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _willr_pt(d, df, timeperiod=14, **_): + return _strip_nan( + _pta.willr(df["high"], df["low"], df["close"], length=timeperiod).values + ) + + +def _willr_ta(d, df, timeperiod=14, **_): + from ta.momentum import WilliamsRIndicator + + return _strip_nan( + WilliamsRIndicator(df["high"], df["low"], df["close"], lbp=timeperiod) + .williams_r() + .values + ) + + +def _willr_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.willr(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod) + ) + + +def _willr_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.WILLIAMS(df, timeperiod).values) + + +def _aroon_ft(d, df, timeperiod=14, **_): + import ferro_ta + + dn, up = ferro_ta.AROON(d["high"], d["low"], timeperiod=timeperiod) + return _strip_nan(up) + + +def _aroon_tl(d, df, timeperiod=14, **_): + dn, up = _talib.AROON(d["high"], d["low"], timeperiod=timeperiod) + return _strip_nan(up) + + +def _aroon_pt(d, df, timeperiod=14, **_): + r = _pta.aroon(df["high"], df["low"], length=timeperiod) + return _first_col(r, "AROONU_") if r is not None else _empty() + + +def _aroon_ta(d, df, timeperiod=14, **_): + from ta.trend import AroonIndicator + + return _strip_nan( + AroonIndicator(df["high"], df["low"], window=timeperiod).aroon_up().values + ) + + +def _aroon_tu(d, df, timeperiod=14, **_): + dn, up = _tl.aroon(_c64(d["high"]), _c64(d["low"]), period=timeperiod) + return _strip_nan(up) + + +def _aroon_fi(d, df, **_): + return _empty() -def _willr_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.WILLR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _willr_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.WILLR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _willr_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.willr(df["high"],df["low"],df["close"],length=timeperiod).values) -def _willr_ta(d,df,timeperiod=14,**_): - from ta.momentum import WilliamsRIndicator; return _strip_nan(WilliamsRIndicator(df["high"],df["low"],df["close"],lbp=timeperiod).williams_r().values) -def _willr_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.willr(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod)) -def _willr_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.WILLIAMS(df,timeperiod).values) -def _aroon_ft(d,df,timeperiod=14,**_): - import ferro_ta; dn,up=ferro_ta.AROON(d["high"],d["low"],timeperiod=timeperiod); return _strip_nan(up) -def _aroon_tl(d,df,timeperiod=14,**_): - dn,up=_talib.AROON(d["high"],d["low"],timeperiod=timeperiod); return _strip_nan(up) -def _aroon_pt(d,df,timeperiod=14,**_): - r=_pta.aroon(df["high"],df["low"],length=timeperiod); return _first_col(r,"AROONU_") if r is not None else _empty() -def _aroon_ta(d,df,timeperiod=14,**_): - from ta.trend import AroonIndicator; return _strip_nan(AroonIndicator(df["high"],df["low"],window=timeperiod).aroon_up().values) -def _aroon_tu(d,df,timeperiod=14,**_): - dn,up=_tl.aroon(_c64(d["high"]),_c64(d["low"]),period=timeperiod); return _strip_nan(up) -def _aroon_fi(d,df,**_): return _empty() _aroon_fi._stub = True -def _aroonosc_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.AROONOSC(d["high"],d["low"],timeperiod=timeperiod)) -def _aroonosc_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.AROONOSC(d["high"],d["low"],timeperiod=timeperiod)) -def _aroonosc_pt(d,df,**_): return _empty() + +def _aroonosc_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.AROONOSC(d["high"], d["low"], timeperiod=timeperiod)) + + +def _aroonosc_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.AROONOSC(d["high"], d["low"], timeperiod=timeperiod)) + + +def _aroonosc_pt(d, df, **_): + return _empty() + + _aroonosc_pt._stub = True -def _aroonosc_ta(d,df,**_): return _empty() + + +def _aroonosc_ta(d, df, **_): + return _empty() + + _aroonosc_ta._stub = True -def _aroonosc_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.aroonosc(_c64(d["high"]),_c64(d["low"]),period=timeperiod)) -def _aroonosc_fi(d,df,**_): return _empty() + + +def _aroonosc_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.aroonosc(_c64(d["high"]), _c64(d["low"]), period=timeperiod)) + + +def _aroonosc_fi(d, df, **_): + return _empty() + + _aroonosc_fi._stub = True -def _adx_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.ADX(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _adx_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.ADX(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _adx_pt(d,df,timeperiod=14,**_): - r=_pta.adx(df["high"],df["low"],df["close"],length=timeperiod); return _first_col(r,"ADX_") -def _adx_ta(d,df,timeperiod=14,**_): - from ta.trend import ADXIndicator; return _strip_nan(ADXIndicator(df["high"],df["low"],df["close"],window=timeperiod).adx().values) -def _adx_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.adx(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod)) -def _adx_fi(d,df,**_): return _empty() + +def _adx_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.ADX(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _adx_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.ADX(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _adx_pt(d, df, timeperiod=14, **_): + r = _pta.adx(df["high"], df["low"], df["close"], length=timeperiod) + return _first_col(r, "ADX_") + + +def _adx_ta(d, df, timeperiod=14, **_): + from ta.trend import ADXIndicator + + return _strip_nan( + ADXIndicator(df["high"], df["low"], df["close"], window=timeperiod).adx().values + ) + + +def _adx_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.adx(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod) + ) + + +def _adx_fi(d, df, **_): + return _empty() + + _adx_fi._stub = True -def _mom_ft(d,df,timeperiod=10,**_): - import ferro_ta; return _strip_nan(ferro_ta.MOM(d["close"],timeperiod=timeperiod)) -def _mom_tl(d,df,timeperiod=10,**_): return _strip_nan(_talib.MOM(d["close"],timeperiod=timeperiod)) -def _mom_pt(d,df,timeperiod=10,**_): return _strip_nan(_pta.mom(df["close"],length=timeperiod).values) -def _mom_ta(d,df,**_): return _empty() + +def _mom_ft(d, df, timeperiod=10, **_): + import ferro_ta + + return _strip_nan(ferro_ta.MOM(d["close"], timeperiod=timeperiod)) + + +def _mom_tl(d, df, timeperiod=10, **_): + return _strip_nan(_talib.MOM(d["close"], timeperiod=timeperiod)) + + +def _mom_pt(d, df, timeperiod=10, **_): + return _strip_nan(_pta.mom(df["close"], length=timeperiod).values) + + +def _mom_ta(d, df, **_): + return _empty() + + _mom_ta._stub = True -def _mom_tu(d,df,timeperiod=10,**_): return _strip_nan(_tl.mom(_c64(d["close"]),period=timeperiod)) -def _mom_fi(d,df,timeperiod=10,**_): return _strip_nan(_fi.MOM(df,timeperiod).values) -def _roc_ft(d,df,timeperiod=10,**_): - import ferro_ta; return _strip_nan(ferro_ta.ROC(d["close"],timeperiod=timeperiod)) -def _roc_tl(d,df,timeperiod=10,**_): return _strip_nan(_talib.ROC(d["close"],timeperiod=timeperiod)) -def _roc_pt(d,df,timeperiod=10,**_): return _strip_nan(_pta.roc(df["close"],length=timeperiod).values) -def _roc_ta(d,df,timeperiod=10,**_): - from ta.momentum import ROCIndicator; return _strip_nan(ROCIndicator(df["close"],window=timeperiod).roc().values) -def _roc_tu(d,df,timeperiod=10,**_): return _strip_nan(_tl.roc(_c64(d["close"]),period=timeperiod) * 100.0) -def _roc_fi(d,df,timeperiod=10,**_): return _strip_nan(_fi.ROC(df,timeperiod).values) -def _cmo_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.CMO(d["close"],timeperiod=timeperiod)) -def _cmo_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.CMO(d["close"],timeperiod=timeperiod)) -def _cmo_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.cmo(df["close"],length=timeperiod).values) -def _cmo_ta(d,df,**_): return _empty() +def _mom_tu(d, df, timeperiod=10, **_): + return _strip_nan(_tl.mom(_c64(d["close"]), period=timeperiod)) + + +def _mom_fi(d, df, timeperiod=10, **_): + return _strip_nan(_fi.MOM(df, timeperiod).values) + + +def _roc_ft(d, df, timeperiod=10, **_): + import ferro_ta + + return _strip_nan(ferro_ta.ROC(d["close"], timeperiod=timeperiod)) + + +def _roc_tl(d, df, timeperiod=10, **_): + return _strip_nan(_talib.ROC(d["close"], timeperiod=timeperiod)) + + +def _roc_pt(d, df, timeperiod=10, **_): + return _strip_nan(_pta.roc(df["close"], length=timeperiod).values) + + +def _roc_ta(d, df, timeperiod=10, **_): + from ta.momentum import ROCIndicator + + return _strip_nan(ROCIndicator(df["close"], window=timeperiod).roc().values) + + +def _roc_tu(d, df, timeperiod=10, **_): + return _strip_nan(_tl.roc(_c64(d["close"]), period=timeperiod) * 100.0) + + +def _roc_fi(d, df, timeperiod=10, **_): + return _strip_nan(_fi.ROC(df, timeperiod).values) + + +def _cmo_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.CMO(d["close"], timeperiod=timeperiod)) + + +def _cmo_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.CMO(d["close"], timeperiod=timeperiod)) + + +def _cmo_pt(d, df, timeperiod=14, **_): + return _strip_nan(_pta.cmo(df["close"], length=timeperiod).values) + + +def _cmo_ta(d, df, **_): + return _empty() + + _cmo_ta._stub = True -def _cmo_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.cmo(_c64(d["close"]),period=timeperiod)) -def _cmo_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.CMO(df,timeperiod).values) -def _ppo_ft(d,df,fastperiod=12,slowperiod=26,**_): - import ferro_ta; ppo,sig,hist=ferro_ta.PPO(d["close"],fastperiod=fastperiod,slowperiod=slowperiod); return _strip_nan(ppo) -def _ppo_tl(d,df,fastperiod=12,slowperiod=26,**_): return _strip_nan(_talib.PPO(d["close"],fastperiod=fastperiod,slowperiod=slowperiod)) -def _ppo_pt(d,df,fastperiod=12,slowperiod=26,**_): - r=_pta.ppo(df["close"],fast=fastperiod,slow=slowperiod) - return _strip_nan(r.iloc[:,0].values) if r is not None else _empty() -def _ppo_ta(d,df,**_): return _empty() + +def _cmo_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.cmo(_c64(d["close"]), period=timeperiod)) + + +def _cmo_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.CMO(df, timeperiod).values) + + +def _ppo_ft(d, df, fastperiod=12, slowperiod=26, **_): + import ferro_ta + + ppo, sig, hist = ferro_ta.PPO( + d["close"], fastperiod=fastperiod, slowperiod=slowperiod + ) + return _strip_nan(ppo) + + +def _ppo_tl(d, df, fastperiod=12, slowperiod=26, **_): + return _strip_nan( + _talib.PPO(d["close"], fastperiod=fastperiod, slowperiod=slowperiod) + ) + + +def _ppo_pt(d, df, fastperiod=12, slowperiod=26, **_): + r = _pta.ppo(df["close"], fast=fastperiod, slow=slowperiod) + return _strip_nan(r.iloc[:, 0].values) if r is not None else _empty() + + +def _ppo_ta(d, df, **_): + return _empty() + + _ppo_ta._stub = True -def _ppo_tu(d,df,fastperiod=12,slowperiod=26,**_): return _strip_nan(_tl.ppo(_c64(d["close"]),short_period=fastperiod,long_period=slowperiod)) -def _ppo_fi(d,df,fastperiod=12,slowperiod=26,**_): return _strip_nan(_fi.PPO(df,fastperiod,slowperiod).values) -def _trix_ft(d,df,timeperiod=18,**_): - import ferro_ta; return _strip_nan(ferro_ta.TRIX(d["close"],timeperiod=timeperiod)) -def _trix_tl(d,df,timeperiod=18,**_): return _strip_nan(_talib.TRIX(d["close"],timeperiod=timeperiod)) -def _trix_pt(d,df,timeperiod=18,**_): - r=_pta.trix(df["close"],length=timeperiod) - return _strip_nan(r.iloc[:,0].values) if r is not None else _empty() -def _trix_ta(d,df,timeperiod=18,**_): - from ta.trend import TRIXIndicator; return _strip_nan(TRIXIndicator(df["close"],window=timeperiod).trix().values) -def _trix_tu(d,df,timeperiod=18,**_): return _strip_nan(_tl.trix(_c64(d["close"]),period=timeperiod)) -def _trix_fi(d,df,timeperiod=18,**_): return _strip_nan(_fi.TRIX(df,timeperiod).values) -def _tsf_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.TSF(d["close"],timeperiod=timeperiod)) -def _tsf_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.TSF(d["close"],timeperiod=timeperiod)) -def _tsf_pt(d,df,**_): return _empty() +def _ppo_tu(d, df, fastperiod=12, slowperiod=26, **_): + return _strip_nan( + _tl.ppo(_c64(d["close"]), short_period=fastperiod, long_period=slowperiod) + ) + + +def _ppo_fi(d, df, fastperiod=12, slowperiod=26, **_): + return _strip_nan(_fi.PPO(df, fastperiod, slowperiod).values) + + +def _trix_ft(d, df, timeperiod=18, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TRIX(d["close"], timeperiod=timeperiod)) + + +def _trix_tl(d, df, timeperiod=18, **_): + return _strip_nan(_talib.TRIX(d["close"], timeperiod=timeperiod)) + + +def _trix_pt(d, df, timeperiod=18, **_): + r = _pta.trix(df["close"], length=timeperiod) + return _strip_nan(r.iloc[:, 0].values) if r is not None else _empty() + + +def _trix_ta(d, df, timeperiod=18, **_): + from ta.trend import TRIXIndicator + + return _strip_nan(TRIXIndicator(df["close"], window=timeperiod).trix().values) + + +def _trix_tu(d, df, timeperiod=18, **_): + return _strip_nan(_tl.trix(_c64(d["close"]), period=timeperiod)) + + +def _trix_fi(d, df, timeperiod=18, **_): + return _strip_nan(_fi.TRIX(df, timeperiod).values) + + +def _tsf_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TSF(d["close"], timeperiod=timeperiod)) + + +def _tsf_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.TSF(d["close"], timeperiod=timeperiod)) + + +def _tsf_pt(d, df, **_): + return _empty() + + _tsf_pt._stub = True -def _tsf_ta(d,df,**_): return _empty() + + +def _tsf_ta(d, df, **_): + return _empty() + + _tsf_ta._stub = True -def _tsf_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.tsf(_c64(d["close"]),period=timeperiod)) -def _tsf_fi(d,df,**_): return _empty() + + +def _tsf_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.tsf(_c64(d["close"]), period=timeperiod)) + + +def _tsf_fi(d, df, **_): + return _empty() + + _tsf_fi._stub = True -def _ultosc_ft(d,df,timeperiod1=7,timeperiod2=14,timeperiod3=28,**_): - import ferro_ta; return _strip_nan(ferro_ta.ULTOSC(d["high"],d["low"],d["close"],timeperiod1=timeperiod1,timeperiod2=timeperiod2,timeperiod3=timeperiod3)) -def _ultosc_tl(d,df,timeperiod1=7,timeperiod2=14,timeperiod3=28,**_): - return _strip_nan(_talib.ULTOSC(d["high"],d["low"],d["close"],timeperiod1=timeperiod1,timeperiod2=timeperiod2,timeperiod3=timeperiod3)) -def _ultosc_pt(d,df,**_): return _empty() + +def _ultosc_ft(d, df, timeperiod1=7, timeperiod2=14, timeperiod3=28, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.ULTOSC( + d["high"], + d["low"], + d["close"], + timeperiod1=timeperiod1, + timeperiod2=timeperiod2, + timeperiod3=timeperiod3, + ) + ) + + +def _ultosc_tl(d, df, timeperiod1=7, timeperiod2=14, timeperiod3=28, **_): + return _strip_nan( + _talib.ULTOSC( + d["high"], + d["low"], + d["close"], + timeperiod1=timeperiod1, + timeperiod2=timeperiod2, + timeperiod3=timeperiod3, + ) + ) + + +def _ultosc_pt(d, df, **_): + return _empty() + + _ultosc_pt._stub = True -def _ultosc_ta(d,df,timeperiod1=7,timeperiod2=14,timeperiod3=28,**_): + + +def _ultosc_ta(d, df, timeperiod1=7, timeperiod2=14, timeperiod3=28, **_): from ta.momentum import UltimateOscillator - return _strip_nan(UltimateOscillator(df["high"],df["low"],df["close"],window1=timeperiod1,window2=timeperiod2,window3=timeperiod3).ultimate_oscillator().values) -def _ultosc_tu(d,df,timeperiod1=7,timeperiod2=14,timeperiod3=28,**_): - return _strip_nan(_tl.ultosc(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),short_period=timeperiod1,medium_period=timeperiod2,long_period=timeperiod3)) -def _ultosc_fi(d,df,**_): return _empty() + + return _strip_nan( + UltimateOscillator( + df["high"], + df["low"], + df["close"], + window1=timeperiod1, + window2=timeperiod2, + window3=timeperiod3, + ) + .ultimate_oscillator() + .values + ) + + +def _ultosc_tu(d, df, timeperiod1=7, timeperiod2=14, timeperiod3=28, **_): + return _strip_nan( + _tl.ultosc( + _c64(d["high"]), + _c64(d["low"]), + _c64(d["close"]), + short_period=timeperiod1, + medium_period=timeperiod2, + long_period=timeperiod3, + ) + ) + + +def _ultosc_fi(d, df, **_): + return _empty() + + _ultosc_fi._stub = True -def _bop_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.BOP(d["open"],d["high"],d["low"],d["close"])) -def _bop_tl(d,df,**_): return _strip_nan(_talib.BOP(d["open"],d["high"],d["low"],d["close"])) -def _bop_pt(d,df,**_): - r=_pta.bop(df["open"],df["high"],df["low"],df["close"]); return _strip_nan(r.values) if r is not None else _empty() -def _bop_ta(d,df,**_): return _empty() + +def _bop_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.BOP(d["open"], d["high"], d["low"], d["close"])) + + +def _bop_tl(d, df, **_): + return _strip_nan(_talib.BOP(d["open"], d["high"], d["low"], d["close"])) + + +def _bop_pt(d, df, **_): + r = _pta.bop(df["open"], df["high"], df["low"], df["close"]) + return _strip_nan(r.values) if r is not None else _empty() + + +def _bop_ta(d, df, **_): + return _empty() + + _bop_ta._stub = True -def _bop_tu(d,df,**_): return _strip_nan(_tl.bop(_c64(d["open"]),_c64(d["high"]),_c64(d["low"]),_c64(d["close"]))) -def _bop_fi(d,df,**_): return _empty() + + +def _bop_tu(d, df, **_): + return _strip_nan( + _tl.bop(_c64(d["open"]), _c64(d["high"]), _c64(d["low"]), _c64(d["close"])) + ) + + +def _bop_fi(d, df, **_): + return _empty() + + _bop_fi._stub = True -def _plusdi_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.PLUS_DI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _plusdi_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.PLUS_DI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _plusdi_pt(d,df,timeperiod=14,**_): - r=_pta.adx(df["high"],df["low"],df["close"],length=timeperiod); return _first_col(r,"DMP_") if r is not None else _empty() -def _plusdi_ta(d,df,**_): return _empty() + +def _plusdi_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.PLUS_DI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _plusdi_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.PLUS_DI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _plusdi_pt(d, df, timeperiod=14, **_): + r = _pta.adx(df["high"], df["low"], df["close"], length=timeperiod) + return _first_col(r, "DMP_") if r is not None else _empty() + + +def _plusdi_ta(d, df, **_): + return _empty() + + _plusdi_ta._stub = True -def _plusdi_tu(d,df,timeperiod=14,**_): - pdi,mdi=_tl.di(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod); return _strip_nan(pdi) -def _plusdi_fi(d,df,**_): return _empty() + + +def _plusdi_tu(d, df, timeperiod=14, **_): + pdi, mdi = _tl.di( + _c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod + ) + return _strip_nan(pdi) + + +def _plusdi_fi(d, df, **_): + return _empty() + + _plusdi_fi._stub = True -def _minusdi_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.MINUS_DI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _minusdi_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.MINUS_DI(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _minusdi_pt(d,df,**_): return _empty() + +def _minusdi_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.MINUS_DI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _minusdi_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.MINUS_DI(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _minusdi_pt(d, df, **_): + return _empty() + + _minusdi_pt._stub = True -def _minusdi_ta(d,df,**_): return _empty() + + +def _minusdi_ta(d, df, **_): + return _empty() + + _minusdi_ta._stub = True -def _minusdi_tu(d,df,timeperiod=14,**_): - pdi,mdi=_tl.di(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod); return _strip_nan(mdi) -def _minusdi_fi(d,df,**_): return _empty() + + +def _minusdi_tu(d, df, timeperiod=14, **_): + pdi, mdi = _tl.di( + _c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod + ) + return _strip_nan(mdi) + + +def _minusdi_fi(d, df, **_): + return _empty() + + _minusdi_fi._stub = True + # ============================================================ # VOLATILITY # ============================================================ -def _bb_ft(d,df,timeperiod=20,nbdevup=2.0,nbdevdn=2.0,**_): - import ferro_ta; u,m,l=ferro_ta.BBANDS(d["close"],timeperiod=timeperiod,nbdevup=nbdevup,nbdevdn=nbdevdn); return _strip_nan(u) -def _bb_tl(d,df,timeperiod=20,nbdevup=2.0,nbdevdn=2.0,**_): - u,m,l=_talib.BBANDS(d["close"],timeperiod=timeperiod,nbdevup=nbdevup,nbdevdn=nbdevdn); return _strip_nan(u) -def _bb_pt(d,df,timeperiod=20,nbdevup=2.0,**_): - r=_pta.bbands(df["close"],length=timeperiod,std=nbdevup); return _first_col(r,"BBU_") -def _bb_ta(d,df,timeperiod=20,nbdevup=2.0,**_): - from ta.volatility import BollingerBands; return _strip_nan(BollingerBands(df["close"],window=timeperiod,window_dev=nbdevup).bollinger_hband().values) -def _bb_tu(d,df,timeperiod=20,nbdevup=2.0,**_): - lo,mi,up=_tl.bbands(_c64(d["close"]),period=timeperiod,stddev=nbdevup); return _strip_nan(up) -def _bb_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.BBANDS(df,timeperiod)["BB_UPPER"].values) +def _bb_ft(d, df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, **_): + import ferro_ta + + u, m, l = ferro_ta.BBANDS( + d["close"], timeperiod=timeperiod, nbdevup=nbdevup, nbdevdn=nbdevdn + ) + return _strip_nan(u) + + +def _bb_tl(d, df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, **_): + u, m, l = _talib.BBANDS( + d["close"], timeperiod=timeperiod, nbdevup=nbdevup, nbdevdn=nbdevdn + ) + return _strip_nan(u) + + +def _bb_pt(d, df, timeperiod=20, nbdevup=2.0, **_): + r = _pta.bbands(df["close"], length=timeperiod, std=nbdevup) + return _first_col(r, "BBU_") + + +def _bb_ta(d, df, timeperiod=20, nbdevup=2.0, **_): + from ta.volatility import BollingerBands + + return _strip_nan( + BollingerBands(df["close"], window=timeperiod, window_dev=nbdevup) + .bollinger_hband() + .values + ) + + +def _bb_tu(d, df, timeperiod=20, nbdevup=2.0, **_): + lo, mi, up = _tl.bbands(_c64(d["close"]), period=timeperiod, stddev=nbdevup) + return _strip_nan(up) + + +def _bb_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.BBANDS(df, timeperiod)["BB_UPPER"].values) + + +def _atr_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.ATR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _atr_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.ATR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _atr_pt(d, df, timeperiod=14, **_): + return _strip_nan( + _pta.atr(df["high"], df["low"], df["close"], length=timeperiod).values + ) + + +def _atr_ta(d, df, timeperiod=14, **_): + from ta.volatility import AverageTrueRange + + return _strip_nan( + AverageTrueRange(df["high"], df["low"], df["close"], window=timeperiod) + .average_true_range() + .values + ) + + +def _atr_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.atr(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod) + ) + + +def _atr_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.ATR(df, timeperiod).values) + + +def _natr_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.NATR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _natr_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.NATR(d["high"], d["low"], d["close"], timeperiod=timeperiod) + ) + + +def _natr_pt(d, df, timeperiod=14, **_): + return _strip_nan( + _pta.natr(df["high"], df["low"], df["close"], length=timeperiod).values + ) + + +def _natr_ta(d, df, **_): + return _empty() -def _atr_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.ATR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _atr_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.ATR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _atr_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.atr(df["high"],df["low"],df["close"],length=timeperiod).values) -def _atr_ta(d,df,timeperiod=14,**_): - from ta.volatility import AverageTrueRange; return _strip_nan(AverageTrueRange(df["high"],df["low"],df["close"],window=timeperiod).average_true_range().values) -def _atr_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.atr(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod)) -def _atr_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.ATR(df,timeperiod).values) -def _natr_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.NATR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _natr_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.NATR(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _natr_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.natr(df["high"],df["low"],df["close"],length=timeperiod).values) -def _natr_ta(d,df,**_): return _empty() _natr_ta._stub = True -def _natr_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.natr(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),period=timeperiod)) -def _natr_fi(d,df,**_): return _empty() + + +def _natr_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.natr(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), period=timeperiod) + ) + + +def _natr_fi(d, df, **_): + return _empty() + + _natr_fi._stub = True -def _trange_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.TRANGE(d["high"],d["low"],d["close"])) -def _trange_tl(d,df,**_): return _strip_nan(_talib.TRANGE(d["high"],d["low"],d["close"])) -def _trange_pt(d,df,**_): - r=_pta.true_range(df["high"],df["low"],df["close"]); return _strip_nan(r.values) if r is not None else _empty() -def _trange_ta(d,df,**_): return _empty() + +def _trange_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TRANGE(d["high"], d["low"], d["close"])) + + +def _trange_tl(d, df, **_): + return _strip_nan(_talib.TRANGE(d["high"], d["low"], d["close"])) + + +def _trange_pt(d, df, **_): + r = _pta.true_range(df["high"], df["low"], df["close"]) + return _strip_nan(r.values) if r is not None else _empty() + + +def _trange_ta(d, df, **_): + return _empty() + + _trange_ta._stub = True -def _trange_tu(d,df,**_): return _strip_nan(_tl.tr(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]))) -def _trange_fi(d,df,**_): return _strip_nan(_fi.TR(df).values) -def _stddev_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.STDDEV(d["close"],timeperiod=timeperiod)) -def _stddev_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.STDDEV(d["close"],timeperiod=timeperiod)) -def _stddev_pt(d,df,timeperiod=20,**_): - r=_pta.stdev(df["close"],length=timeperiod); return _strip_nan(r.values) if r is not None else _empty() -def _stddev_ta(d,df,**_): return _empty() + +def _trange_tu(d, df, **_): + return _strip_nan(_tl.tr(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]))) + + +def _trange_fi(d, df, **_): + return _strip_nan(_fi.TR(df).values) + + +def _stddev_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.STDDEV(d["close"], timeperiod=timeperiod)) + + +def _stddev_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.STDDEV(d["close"], timeperiod=timeperiod)) + + +def _stddev_pt(d, df, timeperiod=20, **_): + r = _pta.stdev(df["close"], length=timeperiod) + return _strip_nan(r.values) if r is not None else _empty() + + +def _stddev_ta(d, df, **_): + return _empty() + + _stddev_ta._stub = True -def _stddev_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.stddev(_c64(d["close"]),period=timeperiod)) -def _stddev_fi(d,df,timeperiod=20,**_): return _strip_nan(_fi.MSD(df,timeperiod).values) -def _var_ft(d,df,timeperiod=20,**_): - import ferro_ta; return _strip_nan(ferro_ta.VAR(d["close"],timeperiod=timeperiod)) -def _var_tl(d,df,timeperiod=20,**_): return _strip_nan(_talib.VAR(d["close"],timeperiod=timeperiod)) -def _var_pt(d,df,timeperiod=20,**_): - r=_pta.variance(df["close"],length=timeperiod); return _strip_nan(r.values) if r is not None else _empty() -def _var_ta(d,df,**_): return _empty() + +def _stddev_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.stddev(_c64(d["close"]), period=timeperiod)) + + +def _stddev_fi(d, df, timeperiod=20, **_): + return _strip_nan(_fi.MSD(df, timeperiod).values) + + +def _var_ft(d, df, timeperiod=20, **_): + import ferro_ta + + return _strip_nan(ferro_ta.VAR(d["close"], timeperiod=timeperiod)) + + +def _var_tl(d, df, timeperiod=20, **_): + return _strip_nan(_talib.VAR(d["close"], timeperiod=timeperiod)) + + +def _var_pt(d, df, timeperiod=20, **_): + r = _pta.variance(df["close"], length=timeperiod) + return _strip_nan(r.values) if r is not None else _empty() + + +def _var_ta(d, df, **_): + return _empty() + + _var_ta._stub = True -def _var_tu(d,df,timeperiod=20,**_): return _strip_nan(_tl.var(_c64(d["close"]),period=timeperiod)) -def _var_fi(d,df,**_): return _empty() + + +def _var_tu(d, df, timeperiod=20, **_): + return _strip_nan(_tl.var(_c64(d["close"]), period=timeperiod)) + + +def _var_fi(d, df, **_): + return _empty() + + _var_fi._stub = True -def _sar_ft(d,df,acceleration=0.02,maximum=0.2,**_): - import ferro_ta; return _strip_nan(ferro_ta.SAR(d["high"],d["low"],acceleration=acceleration,maximum=maximum)) -def _sar_tl(d,df,acceleration=0.02,maximum=0.2,**_): return _strip_nan(_talib.SAR(d["high"],d["low"],acceleration=acceleration,maximum=maximum)) -def _sar_pt(d,df,**_): return _empty() + +def _sar_ft(d, df, acceleration=0.02, maximum=0.2, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.SAR(d["high"], d["low"], acceleration=acceleration, maximum=maximum) + ) + + +def _sar_tl(d, df, acceleration=0.02, maximum=0.2, **_): + return _strip_nan( + _talib.SAR(d["high"], d["low"], acceleration=acceleration, maximum=maximum) + ) + + +def _sar_pt(d, df, **_): + return _empty() + + _sar_pt._stub = True -def _sar_ta(d,df,**_): return _empty() + + +def _sar_ta(d, df, **_): + return _empty() + + _sar_ta._stub = True -def _sar_tu(d,df,acceleration=0.02,maximum=0.2,**_): return _strip_nan(_tl.psar(_c64(d["high"]),_c64(d["low"]),acceleration_factor_step=acceleration,acceleration_factor_maximum=maximum)) -def _sar_fi(d,df,**_): return _empty() + + +def _sar_tu(d, df, acceleration=0.02, maximum=0.2, **_): + return _strip_nan( + _tl.psar( + _c64(d["high"]), + _c64(d["low"]), + acceleration_factor_step=acceleration, + acceleration_factor_maximum=maximum, + ) + ) + + +def _sar_fi(d, df, **_): + return _empty() + + _sar_fi._stub = True -def _kc_ft(d,df,timeperiod=20,**_): - import ferro_ta; u,m,l=ferro_ta.KELTNER_CHANNELS(d["high"],d["low"],d["close"],timeperiod=timeperiod); return _strip_nan(u) -def _kc_tl(d,df,**_): return _empty() + +def _kc_ft(d, df, timeperiod=20, **_): + import ferro_ta + + u, m, l = ferro_ta.KELTNER_CHANNELS( + d["high"], d["low"], d["close"], timeperiod=timeperiod + ) + return _strip_nan(u) + + +def _kc_tl(d, df, **_): + return _empty() + + _kc_tl._stub = True -def _kc_pt(d,df,timeperiod=20,**_): - r=_pta.kc(df["high"],df["low"],df["close"],length=timeperiod) - if r is None: return _empty() - col=next((c for c in r.columns if "UCe" in c or "UB" in c or c.endswith("U")),None) - return _strip_nan(r[col].values) if col else _first_col(r,"KC") -def _kc_ta(d,df,timeperiod=20,**_): - from ta.volatility import KeltnerChannel; return _strip_nan(KeltnerChannel(df["high"],df["low"],df["close"],window=timeperiod).keltner_channel_hband().values) -def _kc_tu(d,df,**_): return _empty() + + +def _kc_pt(d, df, timeperiod=20, **_): + r = _pta.kc(df["high"], df["low"], df["close"], length=timeperiod) + if r is None: + return _empty() + col = next( + (c for c in r.columns if "UCe" in c or "UB" in c or c.endswith("U")), None + ) + return _strip_nan(r[col].values) if col else _first_col(r, "KC") + + +def _kc_ta(d, df, timeperiod=20, **_): + from ta.volatility import KeltnerChannel + + return _strip_nan( + KeltnerChannel(df["high"], df["low"], df["close"], window=timeperiod) + .keltner_channel_hband() + .values + ) + + +def _kc_tu(d, df, **_): + return _empty() + + _kc_tu._stub = True -def _kc_fi(d,df,**_): return _empty() + + +def _kc_fi(d, df, **_): + return _empty() + + _kc_fi._stub = True -def _donchian_ft(d,df,timeperiod=20,**_): - import ferro_ta; u,m,l=ferro_ta.DONCHIAN(d["high"],d["low"],timeperiod=timeperiod); return _strip_nan(u) -def _donchian_tl(d,df,**_): return _empty() + +def _donchian_ft(d, df, timeperiod=20, **_): + import ferro_ta + + u, m, l = ferro_ta.DONCHIAN(d["high"], d["low"], timeperiod=timeperiod) + return _strip_nan(u) + + +def _donchian_tl(d, df, **_): + return _empty() + + _donchian_tl._stub = True -def _donchian_pt(d,df,timeperiod=20,**_): - r=_pta.donchian(df["high"],df["low"],lower_length=timeperiod,upper_length=timeperiod) - return _first_col(r,"DCU_") if r is not None else _empty() -def _donchian_ta(d,df,timeperiod=20,**_): - from ta.volatility import DonchianChannel; return _strip_nan(DonchianChannel(df["high"],df["low"],df["close"],window=timeperiod).donchian_channel_hband().values) -def _donchian_tu(d,df,**_): return _empty() + + +def _donchian_pt(d, df, timeperiod=20, **_): + r = _pta.donchian( + df["high"], df["low"], lower_length=timeperiod, upper_length=timeperiod + ) + return _first_col(r, "DCU_") if r is not None else _empty() + + +def _donchian_ta(d, df, timeperiod=20, **_): + from ta.volatility import DonchianChannel + + return _strip_nan( + DonchianChannel(df["high"], df["low"], df["close"], window=timeperiod) + .donchian_channel_hband() + .values + ) + + +def _donchian_tu(d, df, **_): + return _empty() + + _donchian_tu._stub = True -def _donchian_fi(d,df,**_): return _empty() + + +def _donchian_fi(d, df, **_): + return _empty() + + _donchian_fi._stub = True -def _supertrend_ft(d,df,timeperiod=7,**_): - import ferro_ta; st,dir_=ferro_ta.SUPERTREND(d["high"],d["low"],d["close"],timeperiod=timeperiod); return _strip_nan(st) -def _supertrend_tl(d,df,**_): return _empty() + +def _supertrend_ft(d, df, timeperiod=7, **_): + import ferro_ta + + st, dir_ = ferro_ta.SUPERTREND( + d["high"], d["low"], d["close"], timeperiod=timeperiod + ) + return _strip_nan(st) + + +def _supertrend_tl(d, df, **_): + return _empty() + + _supertrend_tl._stub = True -def _supertrend_pt(d,df,timeperiod=7,**_): - r=_pta.supertrend(df["high"],df["low"],df["close"],length=timeperiod) - return _first_col(r,"SUPERT_") if r is not None else _empty() -def _supertrend_ta(d,df,**_): return _empty() + + +def _supertrend_pt(d, df, timeperiod=7, **_): + r = _pta.supertrend(df["high"], df["low"], df["close"], length=timeperiod) + return _first_col(r, "SUPERT_") if r is not None else _empty() + + +def _supertrend_ta(d, df, **_): + return _empty() + + _supertrend_ta._stub = True -def _supertrend_tu(d,df,**_): return _empty() + + +def _supertrend_tu(d, df, **_): + return _empty() + + _supertrend_tu._stub = True -def _supertrend_fi(d,df,**_): return _empty() + + +def _supertrend_fi(d, df, **_): + return _empty() + + _supertrend_fi._stub = True -def _chop_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.CHOPPINESS_INDEX(d["high"],d["low"],d["close"],timeperiod=timeperiod)) -def _chop_tl(d,df,**_): return _empty() + +def _chop_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.CHOPPINESS_INDEX( + d["high"], d["low"], d["close"], timeperiod=timeperiod + ) + ) + + +def _chop_tl(d, df, **_): + return _empty() + + _chop_tl._stub = True -def _chop_pt(d,df,timeperiod=14,**_): - r=_pta.chop(df["high"],df["low"],df["close"],length=timeperiod); return _strip_nan(r.values) if r is not None else _empty() -def _chop_ta(d,df,**_): return _empty() + + +def _chop_pt(d, df, timeperiod=14, **_): + r = _pta.chop(df["high"], df["low"], df["close"], length=timeperiod) + return _strip_nan(r.values) if r is not None else _empty() + + +def _chop_ta(d, df, **_): + return _empty() + + _chop_ta._stub = True -def _chop_tu(d,df,**_): return _empty() + + +def _chop_tu(d, df, **_): + return _empty() + + _chop_tu._stub = True -def _chop_fi(d,df,**_): return _empty() + + +def _chop_fi(d, df, **_): + return _empty() + + _chop_fi._stub = True + # ============================================================ # VOLUME # ============================================================ -def _obv_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.OBV(d["close"],d["volume"])) -def _obv_tl(d,df,**_): return _strip_nan(_talib.OBV(d["close"],d["volume"])) -def _obv_pt(d,df,**_): return _strip_nan(_pta.obv(df["close"],df["volume"]).values) -def _obv_ta(d,df,**_): - from ta.volume import OnBalanceVolumeIndicator; return _strip_nan(OnBalanceVolumeIndicator(df["close"],df["volume"]).on_balance_volume().values) -def _obv_tu(d,df,**_): return _strip_nan(_tl.obv(_c64(d["close"]),_c64(d["volume"]))) -def _obv_fi(d,df,**_): return _strip_nan(_fi.OBV(df).values) +def _obv_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.OBV(d["close"], d["volume"])) + + +def _obv_tl(d, df, **_): + return _strip_nan(_talib.OBV(d["close"], d["volume"])) + + +def _obv_pt(d, df, **_): + return _strip_nan(_pta.obv(df["close"], df["volume"]).values) + + +def _obv_ta(d, df, **_): + from ta.volume import OnBalanceVolumeIndicator + + return _strip_nan( + OnBalanceVolumeIndicator(df["close"], df["volume"]).on_balance_volume().values + ) + + +def _obv_tu(d, df, **_): + return _strip_nan(_tl.obv(_c64(d["close"]), _c64(d["volume"]))) + + +def _obv_fi(d, df, **_): + return _strip_nan(_fi.OBV(df).values) + + +def _ad_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.AD(d["high"], d["low"], d["close"], d["volume"])) + + +def _ad_tl(d, df, **_): + return _strip_nan(_talib.AD(d["high"], d["low"], d["close"], d["volume"])) + + +def _ad_pt(d, df, **_): + return _strip_nan(_pta.ad(df["high"], df["low"], df["close"], df["volume"]).values) + + +def _ad_ta(d, df, **_): + from ta.volume import AccDistIndexIndicator + + return _strip_nan( + AccDistIndexIndicator(df["high"], df["low"], df["close"], df["volume"]) + .acc_dist_index() + .values + ) + + +def _ad_tu(d, df, **_): + return _strip_nan( + _tl.ad(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]), _c64(d["volume"])) + ) + + +def _ad_fi(d, df, **_): + return _empty() + -def _ad_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.AD(d["high"],d["low"],d["close"],d["volume"])) -def _ad_tl(d,df,**_): return _strip_nan(_talib.AD(d["high"],d["low"],d["close"],d["volume"])) -def _ad_pt(d,df,**_): return _strip_nan(_pta.ad(df["high"],df["low"],df["close"],df["volume"]).values) -def _ad_ta(d,df,**_): - from ta.volume import AccDistIndexIndicator; return _strip_nan(AccDistIndexIndicator(df["high"],df["low"],df["close"],df["volume"]).acc_dist_index().values) -def _ad_tu(d,df,**_): return _strip_nan(_tl.ad(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),_c64(d["volume"]))) -def _ad_fi(d,df,**_): return _empty() _ad_fi._stub = True -def _adosc_ft(d,df,fastperiod=3,slowperiod=10,**_): - import ferro_ta; return _strip_nan(ferro_ta.ADOSC(d["high"],d["low"],d["close"],d["volume"],fastperiod=fastperiod,slowperiod=slowperiod)) -def _adosc_tl(d,df,fastperiod=3,slowperiod=10,**_): return _strip_nan(_talib.ADOSC(d["high"],d["low"],d["close"],d["volume"],fastperiod=fastperiod,slowperiod=slowperiod)) -def _adosc_pt(d,df,fastperiod=3,slowperiod=10,**_): return _strip_nan(_pta.adosc(df["high"],df["low"],df["close"],df["volume"],fast=fastperiod,slow=slowperiod).values) -def _adosc_ta(d,df,**_): return _empty() + +def _adosc_ft(d, df, fastperiod=3, slowperiod=10, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.ADOSC( + d["high"], + d["low"], + d["close"], + d["volume"], + fastperiod=fastperiod, + slowperiod=slowperiod, + ) + ) + + +def _adosc_tl(d, df, fastperiod=3, slowperiod=10, **_): + return _strip_nan( + _talib.ADOSC( + d["high"], + d["low"], + d["close"], + d["volume"], + fastperiod=fastperiod, + slowperiod=slowperiod, + ) + ) + + +def _adosc_pt(d, df, fastperiod=3, slowperiod=10, **_): + return _strip_nan( + _pta.adosc( + df["high"], + df["low"], + df["close"], + df["volume"], + fast=fastperiod, + slow=slowperiod, + ).values + ) + + +def _adosc_ta(d, df, **_): + return _empty() + + _adosc_ta._stub = True -def _adosc_tu(d,df,fastperiod=3,slowperiod=10,**_): return _strip_nan(_tl.adosc(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),_c64(d["volume"]),short_period=fastperiod,long_period=slowperiod)) -def _adosc_fi(d,df,**_): return _empty() + + +def _adosc_tu(d, df, fastperiod=3, slowperiod=10, **_): + return _strip_nan( + _tl.adosc( + _c64(d["high"]), + _c64(d["low"]), + _c64(d["close"]), + _c64(d["volume"]), + short_period=fastperiod, + long_period=slowperiod, + ) + ) + + +def _adosc_fi(d, df, **_): + return _empty() + + _adosc_fi._stub = True -def _mfi_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.MFI(d["high"],d["low"],d["close"],d["volume"],timeperiod=timeperiod)) -def _mfi_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.MFI(d["high"],d["low"],d["close"],d["volume"],timeperiod=timeperiod)) -def _mfi_pt(d,df,timeperiod=14,**_): return _strip_nan(_pta.mfi(df["high"],df["low"],df["close"],df["volume"],length=timeperiod).values) -def _mfi_ta(d,df,timeperiod=14,**_): - from ta.volume import MFIIndicator; return _strip_nan(MFIIndicator(df["high"],df["low"],df["close"],df["volume"],window=timeperiod).money_flow_index().values) -def _mfi_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.mfi(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]),_c64(d["volume"]),period=timeperiod)) -def _mfi_fi(d,df,timeperiod=14,**_): return _strip_nan(_fi.MFI(df,timeperiod).values) -def _vwap_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.VWAP(d["high"],d["low"],d["close"],d["volume"])) -def _vwap_tl(d,df,**_): return _empty() +def _mfi_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.MFI( + d["high"], d["low"], d["close"], d["volume"], timeperiod=timeperiod + ) + ) + + +def _mfi_tl(d, df, timeperiod=14, **_): + return _strip_nan( + _talib.MFI(d["high"], d["low"], d["close"], d["volume"], timeperiod=timeperiod) + ) + + +def _mfi_pt(d, df, timeperiod=14, **_): + return _strip_nan( + _pta.mfi( + df["high"], df["low"], df["close"], df["volume"], length=timeperiod + ).values + ) + + +def _mfi_ta(d, df, timeperiod=14, **_): + from ta.volume import MFIIndicator + + return _strip_nan( + MFIIndicator( + df["high"], df["low"], df["close"], df["volume"], window=timeperiod + ) + .money_flow_index() + .values + ) + + +def _mfi_tu(d, df, timeperiod=14, **_): + return _strip_nan( + _tl.mfi( + _c64(d["high"]), + _c64(d["low"]), + _c64(d["close"]), + _c64(d["volume"]), + period=timeperiod, + ) + ) + + +def _mfi_fi(d, df, timeperiod=14, **_): + return _strip_nan(_fi.MFI(df, timeperiod).values) + + +def _vwap_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.VWAP(d["high"], d["low"], d["close"], d["volume"])) + + +def _vwap_tl(d, df, **_): + return _empty() + + _vwap_tl._stub = True -def _vwap_pt(d,df,**_): - r=_pta.vwap(df["high"],df["low"],df["close"],df["volume"]); return _strip_nan(r.values) if r is not None else _empty() -def _vwap_ta(d,df,**_): return _empty() + + +def _vwap_pt(d, df, **_): + r = _pta.vwap(df["high"], df["low"], df["close"], df["volume"]) + return _strip_nan(r.values) if r is not None else _empty() + + +def _vwap_ta(d, df, **_): + return _empty() + + _vwap_ta._stub = True -def _vwap_tu(d,df,**_): return _empty() + + +def _vwap_tu(d, df, **_): + return _empty() + + _vwap_tu._stub = True -def _vwap_fi(d,df,**_): + + +def _vwap_fi(d, df, **_): return _strip_nan(_fi.VWAP(df).values) + # ============================================================ # PRICE TRANSFORM # ============================================================ -def _avgprice_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.AVGPRICE(d["open"],d["high"],d["low"],d["close"])) -def _avgprice_tl(d,df,**_): return _strip_nan(_talib.AVGPRICE(d["open"],d["high"],d["low"],d["close"])) -def _avgprice_pt(d,df,**_): return _empty() +def _avgprice_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.AVGPRICE(d["open"], d["high"], d["low"], d["close"])) + + +def _avgprice_tl(d, df, **_): + return _strip_nan(_talib.AVGPRICE(d["open"], d["high"], d["low"], d["close"])) + + +def _avgprice_pt(d, df, **_): + return _empty() + + _avgprice_pt._stub = True -def _avgprice_ta(d,df,**_): return _empty() + + +def _avgprice_ta(d, df, **_): + return _empty() + + _avgprice_ta._stub = True -def _avgprice_tu(d,df,**_): return _strip_nan(_tl.avgprice(_c64(d["open"]),_c64(d["high"]),_c64(d["low"]),_c64(d["close"]))) -def _avgprice_fi(d,df,**_): return _empty() + + +def _avgprice_tu(d, df, **_): + return _strip_nan( + _tl.avgprice(_c64(d["open"]), _c64(d["high"]), _c64(d["low"]), _c64(d["close"])) + ) + + +def _avgprice_fi(d, df, **_): + return _empty() + + _avgprice_fi._stub = True -def _medprice_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.MEDPRICE(d["high"],d["low"])) -def _medprice_tl(d,df,**_): return _strip_nan(_talib.MEDPRICE(d["high"],d["low"])) -def _medprice_pt(d,df,**_): return _empty() + +def _medprice_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.MEDPRICE(d["high"], d["low"])) + + +def _medprice_tl(d, df, **_): + return _strip_nan(_talib.MEDPRICE(d["high"], d["low"])) + + +def _medprice_pt(d, df, **_): + return _empty() + + _medprice_pt._stub = True -def _medprice_ta(d,df,**_): return _empty() + + +def _medprice_ta(d, df, **_): + return _empty() + + _medprice_ta._stub = True -def _medprice_tu(d,df,**_): return _strip_nan(_tl.medprice(_c64(d["high"]),_c64(d["low"]))) -def _medprice_fi(d,df,**_): return _empty() + + +def _medprice_tu(d, df, **_): + return _strip_nan(_tl.medprice(_c64(d["high"]), _c64(d["low"]))) + + +def _medprice_fi(d, df, **_): + return _empty() + + _medprice_fi._stub = True -def _typprice_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.TYPPRICE(d["high"],d["low"],d["close"])) -def _typprice_tl(d,df,**_): return _strip_nan(_talib.TYPPRICE(d["high"],d["low"],d["close"])) -def _typprice_pt(d,df,**_): return _empty() + +def _typprice_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.TYPPRICE(d["high"], d["low"], d["close"])) + + +def _typprice_tl(d, df, **_): + return _strip_nan(_talib.TYPPRICE(d["high"], d["low"], d["close"])) + + +def _typprice_pt(d, df, **_): + return _empty() + + _typprice_pt._stub = True -def _typprice_ta(d,df,**_): return _empty() + + +def _typprice_ta(d, df, **_): + return _empty() + + _typprice_ta._stub = True -def _typprice_tu(d,df,**_): return _strip_nan(_tl.typprice(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]))) -def _typprice_fi(d,df,**_): return _empty() + + +def _typprice_tu(d, df, **_): + return _strip_nan(_tl.typprice(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]))) + + +def _typprice_fi(d, df, **_): + return _empty() + + _typprice_fi._stub = True -def _wclprice_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.WCLPRICE(d["high"],d["low"],d["close"])) -def _wclprice_tl(d,df,**_): return _strip_nan(_talib.WCLPRICE(d["high"],d["low"],d["close"])) -def _wclprice_pt(d,df,**_): return _empty() + +def _wclprice_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.WCLPRICE(d["high"], d["low"], d["close"])) + + +def _wclprice_tl(d, df, **_): + return _strip_nan(_talib.WCLPRICE(d["high"], d["low"], d["close"])) + + +def _wclprice_pt(d, df, **_): + return _empty() + + _wclprice_pt._stub = True -def _wclprice_ta(d,df,**_): return _empty() + + +def _wclprice_ta(d, df, **_): + return _empty() + + _wclprice_ta._stub = True -def _wclprice_tu(d,df,**_): return _strip_nan(_tl.wcprice(_c64(d["high"]),_c64(d["low"]),_c64(d["close"]))) -def _wclprice_fi(d,df,**_): return _empty() + + +def _wclprice_tu(d, df, **_): + return _strip_nan(_tl.wcprice(_c64(d["high"]), _c64(d["low"]), _c64(d["close"]))) + + +def _wclprice_fi(d, df, **_): + return _empty() + + _wclprice_fi._stub = True + # ============================================================ # MATH # ============================================================ -def _sqrt_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.SQRT(d["close"])) -def _sqrt_tl(d,df,**_): return _strip_nan(_talib.SQRT(d["close"])) -def _sqrt_pt(d,df,**_): return _empty() +def _sqrt_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.SQRT(d["close"])) + + +def _sqrt_tl(d, df, **_): + return _strip_nan(_talib.SQRT(d["close"])) + + +def _sqrt_pt(d, df, **_): + return _empty() + + _sqrt_pt._stub = True -def _sqrt_ta(d,df,**_): return _empty() + + +def _sqrt_ta(d, df, **_): + return _empty() + + _sqrt_ta._stub = True -def _sqrt_tu(d,df,**_): return _strip_nan(_tl.sqrt(_c64(d["close"]))) -def _sqrt_fi(d,df,**_): return _empty() + + +def _sqrt_tu(d, df, **_): + return _strip_nan(_tl.sqrt(_c64(d["close"]))) + + +def _sqrt_fi(d, df, **_): + return _empty() + + _sqrt_fi._stub = True -def _log10_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.LOG10(d["close"])) -def _log10_tl(d,df,**_): return _strip_nan(_talib.LOG10(d["close"])) -def _log10_pt(d,df,**_): return _empty() + +def _log10_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.LOG10(d["close"])) + + +def _log10_tl(d, df, **_): + return _strip_nan(_talib.LOG10(d["close"])) + + +def _log10_pt(d, df, **_): + return _empty() + + _log10_pt._stub = True -def _log10_ta(d,df,**_): return _empty() + + +def _log10_ta(d, df, **_): + return _empty() + + _log10_ta._stub = True -def _log10_tu(d,df,**_): return _strip_nan(_tl.log10(_c64(d["close"]))) -def _log10_fi(d,df,**_): return _empty() + + +def _log10_tu(d, df, **_): + return _strip_nan(_tl.log10(_c64(d["close"]))) + + +def _log10_fi(d, df, **_): + return _empty() + + _log10_fi._stub = True -def _add_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.ADD(d["high"],d["low"])) -def _add_tl(d,df,**_): return _strip_nan(_talib.ADD(d["high"],d["low"])) -def _add_pt(d,df,**_): return _empty() + +def _add_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.ADD(d["high"], d["low"])) + + +def _add_tl(d, df, **_): + return _strip_nan(_talib.ADD(d["high"], d["low"])) + + +def _add_pt(d, df, **_): + return _empty() + + _add_pt._stub = True -def _add_ta(d,df,**_): return _empty() + + +def _add_ta(d, df, **_): + return _empty() + + _add_ta._stub = True -def _add_tu(d,df,**_): return _strip_nan(_tl.add(_c64(d["high"]),_c64(d["low"]))) -def _add_fi(d,df,**_): return _empty() + + +def _add_tu(d, df, **_): + return _strip_nan(_tl.add(_c64(d["high"]), _c64(d["low"]))) + + +def _add_fi(d, df, **_): + return _empty() + + _add_fi._stub = True + # ============================================================ # STATISTICS # ============================================================ -def _linearreg_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.LINEARREG(d["close"],timeperiod=timeperiod)) -def _linearreg_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.LINEARREG(d["close"],timeperiod=timeperiod)) -def _linearreg_pt(d,df,**_): return _empty() +def _linearreg_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.LINEARREG(d["close"], timeperiod=timeperiod)) + + +def _linearreg_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.LINEARREG(d["close"], timeperiod=timeperiod)) + + +def _linearreg_pt(d, df, **_): + return _empty() + + _linearreg_pt._stub = True -def _linearreg_ta(d,df,**_): return _empty() + + +def _linearreg_ta(d, df, **_): + return _empty() + + _linearreg_ta._stub = True -def _linearreg_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.linreg(_c64(d["close"]),period=timeperiod)) -def _linearreg_fi(d,df,**_): return _empty() + + +def _linearreg_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.linreg(_c64(d["close"]), period=timeperiod)) + + +def _linearreg_fi(d, df, **_): + return _empty() + + _linearreg_fi._stub = True -def _linreg_slope_ft(d,df,timeperiod=14,**_): - import ferro_ta; return _strip_nan(ferro_ta.LINEARREG_SLOPE(d["close"],timeperiod=timeperiod)) -def _linreg_slope_tl(d,df,timeperiod=14,**_): return _strip_nan(_talib.LINEARREG_SLOPE(d["close"],timeperiod=timeperiod)) -def _linreg_slope_pt(d,df,**_): return _empty() + +def _linreg_slope_ft(d, df, timeperiod=14, **_): + import ferro_ta + + return _strip_nan(ferro_ta.LINEARREG_SLOPE(d["close"], timeperiod=timeperiod)) + + +def _linreg_slope_tl(d, df, timeperiod=14, **_): + return _strip_nan(_talib.LINEARREG_SLOPE(d["close"], timeperiod=timeperiod)) + + +def _linreg_slope_pt(d, df, **_): + return _empty() + + _linreg_slope_pt._stub = True -def _linreg_slope_ta(d,df,**_): return _empty() + + +def _linreg_slope_ta(d, df, **_): + return _empty() + + _linreg_slope_ta._stub = True -def _linreg_slope_tu(d,df,timeperiod=14,**_): return _strip_nan(_tl.linregslope(_c64(d["close"]),period=timeperiod)) -def _linreg_slope_fi(d,df,**_): return _empty() + + +def _linreg_slope_tu(d, df, timeperiod=14, **_): + return _strip_nan(_tl.linregslope(_c64(d["close"]), period=timeperiod)) + + +def _linreg_slope_fi(d, df, **_): + return _empty() + + _linreg_slope_fi._stub = True -def _correl_ft(d,df,timeperiod=30,**_): - import ferro_ta; return _strip_nan(ferro_ta.CORREL(d["high"],d["low"],timeperiod=timeperiod)) -def _correl_tl(d,df,timeperiod=30,**_): return _strip_nan(_talib.CORREL(d["high"],d["low"],timeperiod=timeperiod)) -def _correl_pt(d,df,**_): return _empty() + +def _correl_ft(d, df, timeperiod=30, **_): + import ferro_ta + + return _strip_nan(ferro_ta.CORREL(d["high"], d["low"], timeperiod=timeperiod)) + + +def _correl_tl(d, df, timeperiod=30, **_): + return _strip_nan(_talib.CORREL(d["high"], d["low"], timeperiod=timeperiod)) + + +def _correl_pt(d, df, **_): + return _empty() + + _correl_pt._stub = True -def _correl_ta(d,df,**_): return _empty() + + +def _correl_ta(d, df, **_): + return _empty() + + _correl_ta._stub = True -def _correl_tu(d,df,**_): return _empty() + + +def _correl_tu(d, df, **_): + return _empty() + + _correl_tu._stub = True -def _correl_fi(d,df,**_): return _empty() + + +def _correl_fi(d, df, **_): + return _empty() + + _correl_fi._stub = True -def _beta_ft(d,df,timeperiod=5,**_): - import ferro_ta; return _strip_nan(ferro_ta.BETA(d["high"],d["low"],timeperiod=timeperiod)) -def _beta_tl(d,df,timeperiod=5,**_): return _strip_nan(_talib.BETA(d["high"],d["low"],timeperiod=timeperiod)) -def _beta_pt(d,df,**_): return _empty() + +def _beta_ft(d, df, timeperiod=5, **_): + import ferro_ta + + return _strip_nan(ferro_ta.BETA(d["high"], d["low"], timeperiod=timeperiod)) + + +def _beta_tl(d, df, timeperiod=5, **_): + return _strip_nan(_talib.BETA(d["high"], d["low"], timeperiod=timeperiod)) + + +def _beta_pt(d, df, **_): + return _empty() + + _beta_pt._stub = True -def _beta_ta(d,df,**_): return _empty() + + +def _beta_ta(d, df, **_): + return _empty() + + _beta_ta._stub = True -def _beta_tu(d,df,**_): return _empty() + + +def _beta_tu(d, df, **_): + return _empty() + + _beta_tu._stub = True -def _beta_fi(d,df,**_): return _empty() + + +def _beta_fi(d, df, **_): + return _empty() + + _beta_fi._stub = True + # ============================================================ # CYCLE # ============================================================ -def _ht_dcperiod_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.HT_DCPERIOD(d["close"])) -def _ht_dcperiod_tl(d,df,**_): return _strip_nan(_talib.HT_DCPERIOD(d["close"])) -def _ht_dcperiod_pt(d,df,**_): return _empty() +def _ht_dcperiod_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.HT_DCPERIOD(d["close"])) + + +def _ht_dcperiod_tl(d, df, **_): + return _strip_nan(_talib.HT_DCPERIOD(d["close"])) + + +def _ht_dcperiod_pt(d, df, **_): + return _empty() + + _ht_dcperiod_pt._stub = True -def _ht_dcperiod_ta(d,df,**_): return _empty() + + +def _ht_dcperiod_ta(d, df, **_): + return _empty() + + _ht_dcperiod_ta._stub = True -def _ht_dcperiod_tu(d,df,**_): return _empty() + + +def _ht_dcperiod_tu(d, df, **_): + return _empty() + + _ht_dcperiod_tu._stub = True -def _ht_dcperiod_fi(d,df,**_): return _empty() + + +def _ht_dcperiod_fi(d, df, **_): + return _empty() + + _ht_dcperiod_fi._stub = True -def _ht_trendmode_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.HT_TRENDMODE(d["close"]).astype(float)) -def _ht_trendmode_tl(d,df,**_): return _strip_nan(_talib.HT_TRENDMODE(d["close"]).astype(float)) -def _ht_trendmode_pt(d,df,**_): return _empty() + +def _ht_trendmode_ft(d, df, **_): + import ferro_ta + + return _strip_nan(ferro_ta.HT_TRENDMODE(d["close"]).astype(float)) + + +def _ht_trendmode_tl(d, df, **_): + return _strip_nan(_talib.HT_TRENDMODE(d["close"]).astype(float)) + + +def _ht_trendmode_pt(d, df, **_): + return _empty() + + _ht_trendmode_pt._stub = True -def _ht_trendmode_ta(d,df,**_): return _empty() + + +def _ht_trendmode_ta(d, df, **_): + return _empty() + + _ht_trendmode_ta._stub = True -def _ht_trendmode_tu(d,df,**_): return _empty() + + +def _ht_trendmode_tu(d, df, **_): + return _empty() + + _ht_trendmode_tu._stub = True -def _ht_trendmode_fi(d,df,**_): return _empty() + + +def _ht_trendmode_fi(d, df, **_): + return _empty() + + _ht_trendmode_fi._stub = True + # ============================================================ # CANDLESTICK PATTERNS # ============================================================ -def _cdlengulfing_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.CDLENGULFING(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdlengulfing_tl(d,df,**_): return _strip_nan(_talib.CDLENGULFING(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdlengulfing_pt(d,df,**_): return _empty() +def _cdlengulfing_ft(d, df, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.CDLENGULFING(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdlengulfing_tl(d, df, **_): + return _strip_nan( + _talib.CDLENGULFING(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdlengulfing_pt(d, df, **_): + return _empty() + + _cdlengulfing_pt._stub = True -def _cdlengulfing_ta(d,df,**_): return _empty() + + +def _cdlengulfing_ta(d, df, **_): + return _empty() + + _cdlengulfing_ta._stub = True -def _cdlengulfing_tu(d,df,**_): return _empty() + + +def _cdlengulfing_tu(d, df, **_): + return _empty() + + _cdlengulfing_tu._stub = True -def _cdlengulfing_fi(d,df,**_): return _empty() + + +def _cdlengulfing_fi(d, df, **_): + return _empty() + + _cdlengulfing_fi._stub = True -def _cdldoji_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.CDLDOJI(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdldoji_tl(d,df,**_): return _strip_nan(_talib.CDLDOJI(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdldoji_pt(d,df,**_): return _empty() + +def _cdldoji_ft(d, df, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.CDLDOJI(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdldoji_tl(d, df, **_): + return _strip_nan( + _talib.CDLDOJI(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdldoji_pt(d, df, **_): + return _empty() + + _cdldoji_pt._stub = True -def _cdldoji_ta(d,df,**_): return _empty() + + +def _cdldoji_ta(d, df, **_): + return _empty() + + _cdldoji_ta._stub = True -def _cdldoji_tu(d,df,**_): return _empty() + + +def _cdldoji_tu(d, df, **_): + return _empty() + + _cdldoji_tu._stub = True -def _cdldoji_fi(d,df,**_): return _empty() + + +def _cdldoji_fi(d, df, **_): + return _empty() + + _cdldoji_fi._stub = True -def _cdlhammer_ft(d,df,**_): - import ferro_ta; return _strip_nan(ferro_ta.CDLHAMMER(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdlhammer_tl(d,df,**_): return _strip_nan(_talib.CDLHAMMER(d["open"],d["high"],d["low"],d["close"]).astype(float)) -def _cdlhammer_pt(d,df,**_): return _empty() + +def _cdlhammer_ft(d, df, **_): + import ferro_ta + + return _strip_nan( + ferro_ta.CDLHAMMER(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdlhammer_tl(d, df, **_): + return _strip_nan( + _talib.CDLHAMMER(d["open"], d["high"], d["low"], d["close"]).astype(float) + ) + + +def _cdlhammer_pt(d, df, **_): + return _empty() + + _cdlhammer_pt._stub = True -def _cdlhammer_ta(d,df,**_): return _empty() + + +def _cdlhammer_ta(d, df, **_): + return _empty() + + _cdlhammer_ta._stub = True -def _cdlhammer_tu(d,df,**_): return _empty() + + +def _cdlhammer_tu(d, df, **_): + return _empty() + + _cdlhammer_tu._stub = True -def _cdlhammer_fi(d,df,**_): return _empty() + + +def _cdlhammer_fi(d, df, **_): + return _empty() + + _cdlhammer_fi._stub = True # ============================================================ # REGISTRY BUILD # ============================================================ -REGISTRY: dict[tuple[str,Any], Any] = {} +REGISTRY: dict[tuple[str, Any], Any] = {} + def _reg(ind, ft, tl, pt, ta_, tu, fi): """ @@ -773,133 +2462,348 @@ def _reg(ind, ft, tl, pt, ta_, tu, fi): benchmarks then skip those pairs and the table shows N/A. """ for lib, fn in [ - ("ferro_ta", ft), - ("talib", tl), - ("pandas_ta",pt), - ("ta", ta_), - ("tulipy", tu), - ("finta", fi), + ("ferro_ta", ft), + ("talib", tl), + ("pandas_ta", pt), + ("ta", ta_), + ("tulipy", tu), + ("finta", fi), ]: if getattr(fn, "_stub", False): continue REGISTRY[(lib, ind)] = fn -_reg("SMA",_sma_ft,_sma_tl,_sma_pt,_sma_ta,_sma_tu,_sma_fi) -_reg("EMA",_ema_ft,_ema_tl,_ema_pt,_ema_ta,_ema_tu,_ema_fi) -_reg("WMA",_wma_ft,_wma_tl,_wma_pt,_wma_ta,_wma_tu,_wma_fi) -_reg("DEMA",_dema_ft,_dema_tl,_dema_pt,_dema_ta,_dema_tu,_dema_fi) -_reg("TEMA",_tema_ft,_tema_tl,_tema_pt,_tema_ta,_tema_tu,_tema_fi) -_reg("T3",_t3_ft,_t3_tl,_t3_pt,_t3_ta,_t3_tu,_t3_fi) -_reg("TRIMA",_trima_ft,_trima_tl,_trima_pt,_trima_ta,_trima_tu,_trima_fi) -_reg("KAMA",_kama_ft,_kama_tl,_kama_pt,_kama_ta,_kama_tu,_kama_fi) -_reg("HULL_MA",_hma_ft,_hma_tl,_hma_pt,_hma_ta,_hma_tu,_hma_fi) -_reg("VWMA",_vwma_ft,_vwma_tl,_vwma_pt,_vwma_ta,_vwma_tu,_vwma_fi) -_reg("MIDPOINT",_midpoint_ft,_midpoint_tl,_midpoint_pt,_midpoint_ta,_midpoint_tu,_midpoint_fi) -_reg("MIDPRICE",_midprice_ft,_midprice_tl,_midprice_pt,_midprice_ta,_midprice_tu,_midprice_fi) -_reg("RSI",_rsi_ft,_rsi_tl,_rsi_pt,_rsi_ta,_rsi_tu,_rsi_fi) -_reg("MACD",_macd_ft,_macd_tl,_macd_pt,_macd_ta,_macd_tu,_macd_fi) -_reg("STOCH",_stoch_ft,_stoch_tl,_stoch_pt,_stoch_ta,_stoch_tu,_stoch_fi) -_reg("CCI",_cci_ft,_cci_tl,_cci_pt,_cci_ta,_cci_tu,_cci_fi) -_reg("WILLR",_willr_ft,_willr_tl,_willr_pt,_willr_ta,_willr_tu,_willr_fi) -_reg("AROON",_aroon_ft,_aroon_tl,_aroon_pt,_aroon_ta,_aroon_tu,_aroon_fi) -_reg("AROONOSC",_aroonosc_ft,_aroonosc_tl,_aroonosc_pt,_aroonosc_ta,_aroonosc_tu,_aroonosc_fi) -_reg("ADX",_adx_ft,_adx_tl,_adx_pt,_adx_ta,_adx_tu,_adx_fi) -_reg("MOM",_mom_ft,_mom_tl,_mom_pt,_mom_ta,_mom_tu,_mom_fi) -_reg("ROC",_roc_ft,_roc_tl,_roc_pt,_roc_ta,_roc_tu,_roc_fi) -_reg("CMO",_cmo_ft,_cmo_tl,_cmo_pt,_cmo_ta,_cmo_tu,_cmo_fi) -_reg("PPO",_ppo_ft,_ppo_tl,_ppo_pt,_ppo_ta,_ppo_tu,_ppo_fi) -_reg("TRIX",_trix_ft,_trix_tl,_trix_pt,_trix_ta,_trix_tu,_trix_fi) -_reg("TSF",_tsf_ft,_tsf_tl,_tsf_pt,_tsf_ta,_tsf_tu,_tsf_fi) -_reg("ULTOSC",_ultosc_ft,_ultosc_tl,_ultosc_pt,_ultosc_ta,_ultosc_tu,_ultosc_fi) -_reg("BOP",_bop_ft,_bop_tl,_bop_pt,_bop_ta,_bop_tu,_bop_fi) -_reg("PLUS_DI",_plusdi_ft,_plusdi_tl,_plusdi_pt,_plusdi_ta,_plusdi_tu,_plusdi_fi) -_reg("MINUS_DI",_minusdi_ft,_minusdi_tl,_minusdi_pt,_minusdi_ta,_minusdi_tu,_minusdi_fi) -_reg("BBANDS",_bb_ft,_bb_tl,_bb_pt,_bb_ta,_bb_tu,_bb_fi) -_reg("ATR",_atr_ft,_atr_tl,_atr_pt,_atr_ta,_atr_tu,_atr_fi) -_reg("NATR",_natr_ft,_natr_tl,_natr_pt,_natr_ta,_natr_tu,_natr_fi) -_reg("TRANGE",_trange_ft,_trange_tl,_trange_pt,_trange_ta,_trange_tu,_trange_fi) -_reg("STDDEV",_stddev_ft,_stddev_tl,_stddev_pt,_stddev_ta,_stddev_tu,_stddev_fi) -_reg("VAR",_var_ft,_var_tl,_var_pt,_var_ta,_var_tu,_var_fi) -_reg("SAR",_sar_ft,_sar_tl,_sar_pt,_sar_ta,_sar_tu,_sar_fi) -_reg("KELTNER_CHANNELS",_kc_ft,_kc_tl,_kc_pt,_kc_ta,_kc_tu,_kc_fi) -_reg("DONCHIAN",_donchian_ft,_donchian_tl,_donchian_pt,_donchian_ta,_donchian_tu,_donchian_fi) -_reg("SUPERTREND",_supertrend_ft,_supertrend_tl,_supertrend_pt,_supertrend_ta,_supertrend_tu,_supertrend_fi) -_reg("CHOPPINESS_INDEX",_chop_ft,_chop_tl,_chop_pt,_chop_ta,_chop_tu,_chop_fi) -_reg("OBV",_obv_ft,_obv_tl,_obv_pt,_obv_ta,_obv_tu,_obv_fi) -_reg("AD",_ad_ft,_ad_tl,_ad_pt,_ad_ta,_ad_tu,_ad_fi) -_reg("ADOSC",_adosc_ft,_adosc_tl,_adosc_pt,_adosc_ta,_adosc_tu,_adosc_fi) -_reg("MFI",_mfi_ft,_mfi_tl,_mfi_pt,_mfi_ta,_mfi_tu,_mfi_fi) -_reg("VWAP",_vwap_ft,_vwap_tl,_vwap_pt,_vwap_ta,_vwap_tu,_vwap_fi) -_reg("AVGPRICE",_avgprice_ft,_avgprice_tl,_avgprice_pt,_avgprice_ta,_avgprice_tu,_avgprice_fi) -_reg("MEDPRICE",_medprice_ft,_medprice_tl,_medprice_pt,_medprice_ta,_medprice_tu,_medprice_fi) -_reg("TYPPRICE",_typprice_ft,_typprice_tl,_typprice_pt,_typprice_ta,_typprice_tu,_typprice_fi) -_reg("WCLPRICE",_wclprice_ft,_wclprice_tl,_wclprice_pt,_wclprice_ta,_wclprice_tu,_wclprice_fi) -_reg("SQRT",_sqrt_ft,_sqrt_tl,_sqrt_pt,_sqrt_ta,_sqrt_tu,_sqrt_fi) -_reg("LOG10",_log10_ft,_log10_tl,_log10_pt,_log10_ta,_log10_tu,_log10_fi) -_reg("ADD",_add_ft,_add_tl,_add_pt,_add_ta,_add_tu,_add_fi) -_reg("LINEARREG",_linearreg_ft,_linearreg_tl,_linearreg_pt,_linearreg_ta,_linearreg_tu,_linearreg_fi) -_reg("LINEARREG_SLOPE",_linreg_slope_ft,_linreg_slope_tl,_linreg_slope_pt,_linreg_slope_ta,_linreg_slope_tu,_linreg_slope_fi) -_reg("CORREL",_correl_ft,_correl_tl,_correl_pt,_correl_ta,_correl_tu,_correl_fi) -_reg("BETA",_beta_ft,_beta_tl,_beta_pt,_beta_ta,_beta_tu,_beta_fi) -_reg("HT_DCPERIOD",_ht_dcperiod_ft,_ht_dcperiod_tl,_ht_dcperiod_pt,_ht_dcperiod_ta,_ht_dcperiod_tu,_ht_dcperiod_fi) -_reg("HT_TRENDMODE",_ht_trendmode_ft,_ht_trendmode_tl,_ht_trendmode_pt,_ht_trendmode_ta,_ht_trendmode_tu,_ht_trendmode_fi) -_reg("CDLENGULFING",_cdlengulfing_ft,_cdlengulfing_tl,_cdlengulfing_pt,_cdlengulfing_ta,_cdlengulfing_tu,_cdlengulfing_fi) -_reg("CDLDOJI",_cdldoji_ft,_cdldoji_tl,_cdldoji_pt,_cdldoji_ta,_cdldoji_tu,_cdldoji_fi) -_reg("CDLHAMMER",_cdlhammer_ft,_cdlhammer_tl,_cdlhammer_pt,_cdlhammer_ta,_cdlhammer_tu,_cdlhammer_fi) + +_reg("SMA", _sma_ft, _sma_tl, _sma_pt, _sma_ta, _sma_tu, _sma_fi) +_reg("EMA", _ema_ft, _ema_tl, _ema_pt, _ema_ta, _ema_tu, _ema_fi) +_reg("WMA", _wma_ft, _wma_tl, _wma_pt, _wma_ta, _wma_tu, _wma_fi) +_reg("DEMA", _dema_ft, _dema_tl, _dema_pt, _dema_ta, _dema_tu, _dema_fi) +_reg("TEMA", _tema_ft, _tema_tl, _tema_pt, _tema_ta, _tema_tu, _tema_fi) +_reg("T3", _t3_ft, _t3_tl, _t3_pt, _t3_ta, _t3_tu, _t3_fi) +_reg("TRIMA", _trima_ft, _trima_tl, _trima_pt, _trima_ta, _trima_tu, _trima_fi) +_reg("KAMA", _kama_ft, _kama_tl, _kama_pt, _kama_ta, _kama_tu, _kama_fi) +_reg("HULL_MA", _hma_ft, _hma_tl, _hma_pt, _hma_ta, _hma_tu, _hma_fi) +_reg("VWMA", _vwma_ft, _vwma_tl, _vwma_pt, _vwma_ta, _vwma_tu, _vwma_fi) +_reg( + "MIDPOINT", + _midpoint_ft, + _midpoint_tl, + _midpoint_pt, + _midpoint_ta, + _midpoint_tu, + _midpoint_fi, +) +_reg( + "MIDPRICE", + _midprice_ft, + _midprice_tl, + _midprice_pt, + _midprice_ta, + _midprice_tu, + _midprice_fi, +) +_reg("RSI", _rsi_ft, _rsi_tl, _rsi_pt, _rsi_ta, _rsi_tu, _rsi_fi) +_reg("MACD", _macd_ft, _macd_tl, _macd_pt, _macd_ta, _macd_tu, _macd_fi) +_reg("STOCH", _stoch_ft, _stoch_tl, _stoch_pt, _stoch_ta, _stoch_tu, _stoch_fi) +_reg("CCI", _cci_ft, _cci_tl, _cci_pt, _cci_ta, _cci_tu, _cci_fi) +_reg("WILLR", _willr_ft, _willr_tl, _willr_pt, _willr_ta, _willr_tu, _willr_fi) +_reg("AROON", _aroon_ft, _aroon_tl, _aroon_pt, _aroon_ta, _aroon_tu, _aroon_fi) +_reg( + "AROONOSC", + _aroonosc_ft, + _aroonosc_tl, + _aroonosc_pt, + _aroonosc_ta, + _aroonosc_tu, + _aroonosc_fi, +) +_reg("ADX", _adx_ft, _adx_tl, _adx_pt, _adx_ta, _adx_tu, _adx_fi) +_reg("MOM", _mom_ft, _mom_tl, _mom_pt, _mom_ta, _mom_tu, _mom_fi) +_reg("ROC", _roc_ft, _roc_tl, _roc_pt, _roc_ta, _roc_tu, _roc_fi) +_reg("CMO", _cmo_ft, _cmo_tl, _cmo_pt, _cmo_ta, _cmo_tu, _cmo_fi) +_reg("PPO", _ppo_ft, _ppo_tl, _ppo_pt, _ppo_ta, _ppo_tu, _ppo_fi) +_reg("TRIX", _trix_ft, _trix_tl, _trix_pt, _trix_ta, _trix_tu, _trix_fi) +_reg("TSF", _tsf_ft, _tsf_tl, _tsf_pt, _tsf_ta, _tsf_tu, _tsf_fi) +_reg("ULTOSC", _ultosc_ft, _ultosc_tl, _ultosc_pt, _ultosc_ta, _ultosc_tu, _ultosc_fi) +_reg("BOP", _bop_ft, _bop_tl, _bop_pt, _bop_ta, _bop_tu, _bop_fi) +_reg("PLUS_DI", _plusdi_ft, _plusdi_tl, _plusdi_pt, _plusdi_ta, _plusdi_tu, _plusdi_fi) +_reg( + "MINUS_DI", + _minusdi_ft, + _minusdi_tl, + _minusdi_pt, + _minusdi_ta, + _minusdi_tu, + _minusdi_fi, +) +_reg("BBANDS", _bb_ft, _bb_tl, _bb_pt, _bb_ta, _bb_tu, _bb_fi) +_reg("ATR", _atr_ft, _atr_tl, _atr_pt, _atr_ta, _atr_tu, _atr_fi) +_reg("NATR", _natr_ft, _natr_tl, _natr_pt, _natr_ta, _natr_tu, _natr_fi) +_reg("TRANGE", _trange_ft, _trange_tl, _trange_pt, _trange_ta, _trange_tu, _trange_fi) +_reg("STDDEV", _stddev_ft, _stddev_tl, _stddev_pt, _stddev_ta, _stddev_tu, _stddev_fi) +_reg("VAR", _var_ft, _var_tl, _var_pt, _var_ta, _var_tu, _var_fi) +_reg("SAR", _sar_ft, _sar_tl, _sar_pt, _sar_ta, _sar_tu, _sar_fi) +_reg("KELTNER_CHANNELS", _kc_ft, _kc_tl, _kc_pt, _kc_ta, _kc_tu, _kc_fi) +_reg( + "DONCHIAN", + _donchian_ft, + _donchian_tl, + _donchian_pt, + _donchian_ta, + _donchian_tu, + _donchian_fi, +) +_reg( + "SUPERTREND", + _supertrend_ft, + _supertrend_tl, + _supertrend_pt, + _supertrend_ta, + _supertrend_tu, + _supertrend_fi, +) +_reg("CHOPPINESS_INDEX", _chop_ft, _chop_tl, _chop_pt, _chop_ta, _chop_tu, _chop_fi) +_reg("OBV", _obv_ft, _obv_tl, _obv_pt, _obv_ta, _obv_tu, _obv_fi) +_reg("AD", _ad_ft, _ad_tl, _ad_pt, _ad_ta, _ad_tu, _ad_fi) +_reg("ADOSC", _adosc_ft, _adosc_tl, _adosc_pt, _adosc_ta, _adosc_tu, _adosc_fi) +_reg("MFI", _mfi_ft, _mfi_tl, _mfi_pt, _mfi_ta, _mfi_tu, _mfi_fi) +_reg("VWAP", _vwap_ft, _vwap_tl, _vwap_pt, _vwap_ta, _vwap_tu, _vwap_fi) +_reg( + "AVGPRICE", + _avgprice_ft, + _avgprice_tl, + _avgprice_pt, + _avgprice_ta, + _avgprice_tu, + _avgprice_fi, +) +_reg( + "MEDPRICE", + _medprice_ft, + _medprice_tl, + _medprice_pt, + _medprice_ta, + _medprice_tu, + _medprice_fi, +) +_reg( + "TYPPRICE", + _typprice_ft, + _typprice_tl, + _typprice_pt, + _typprice_ta, + _typprice_tu, + _typprice_fi, +) +_reg( + "WCLPRICE", + _wclprice_ft, + _wclprice_tl, + _wclprice_pt, + _wclprice_ta, + _wclprice_tu, + _wclprice_fi, +) +_reg("SQRT", _sqrt_ft, _sqrt_tl, _sqrt_pt, _sqrt_ta, _sqrt_tu, _sqrt_fi) +_reg("LOG10", _log10_ft, _log10_tl, _log10_pt, _log10_ta, _log10_tu, _log10_fi) +_reg("ADD", _add_ft, _add_tl, _add_pt, _add_ta, _add_tu, _add_fi) +_reg( + "LINEARREG", + _linearreg_ft, + _linearreg_tl, + _linearreg_pt, + _linearreg_ta, + _linearreg_tu, + _linearreg_fi, +) +_reg( + "LINEARREG_SLOPE", + _linreg_slope_ft, + _linreg_slope_tl, + _linreg_slope_pt, + _linreg_slope_ta, + _linreg_slope_tu, + _linreg_slope_fi, +) +_reg("CORREL", _correl_ft, _correl_tl, _correl_pt, _correl_ta, _correl_tu, _correl_fi) +_reg("BETA", _beta_ft, _beta_tl, _beta_pt, _beta_ta, _beta_tu, _beta_fi) +_reg( + "HT_DCPERIOD", + _ht_dcperiod_ft, + _ht_dcperiod_tl, + _ht_dcperiod_pt, + _ht_dcperiod_ta, + _ht_dcperiod_tu, + _ht_dcperiod_fi, +) +_reg( + "HT_TRENDMODE", + _ht_trendmode_ft, + _ht_trendmode_tl, + _ht_trendmode_pt, + _ht_trendmode_ta, + _ht_trendmode_tu, + _ht_trendmode_fi, +) +_reg( + "CDLENGULFING", + _cdlengulfing_ft, + _cdlengulfing_tl, + _cdlengulfing_pt, + _cdlengulfing_ta, + _cdlengulfing_tu, + _cdlengulfing_fi, +) +_reg( + "CDLDOJI", + _cdldoji_ft, + _cdldoji_tl, + _cdldoji_pt, + _cdldoji_ta, + _cdldoji_tu, + _cdldoji_fi, +) +_reg( + "CDLHAMMER", + _cdlhammer_ft, + _cdlhammer_tl, + _cdlhammer_pt, + _cdlhammer_ta, + _cdlhammer_tu, + _cdlhammer_fi, +) # ============================================================ # METADATA # ============================================================ INDICATOR_DEFAULTS: dict[str, dict] = { - "SMA":{"timeperiod":20},"EMA":{"timeperiod":20},"WMA":{"timeperiod":14}, - "DEMA":{"timeperiod":20},"TEMA":{"timeperiod":20},"T3":{"timeperiod":5}, - "TRIMA":{"timeperiod":20},"KAMA":{"timeperiod":10},"HULL_MA":{"timeperiod":16}, - "VWMA":{"timeperiod":20},"MIDPOINT":{"timeperiod":14},"MIDPRICE":{"timeperiod":14}, - "RSI":{"timeperiod":14}, - "MACD":{"fastperiod":12,"slowperiod":26,"signalperiod":9}, - "STOCH":{"fastk_period":14,"slowk_period":3,"slowd_period":3}, - "CCI":{"timeperiod":14},"WILLR":{"timeperiod":14}, - "AROON":{"timeperiod":14},"AROONOSC":{"timeperiod":14}, - "ADX":{"timeperiod":14},"MOM":{"timeperiod":10},"ROC":{"timeperiod":10}, - "CMO":{"timeperiod":14},"PPO":{"fastperiod":12,"slowperiod":26}, - "TRIX":{"timeperiod":18},"TSF":{"timeperiod":14}, - "ULTOSC":{"timeperiod1":7,"timeperiod2":14,"timeperiod3":28}, - "BOP":{},"PLUS_DI":{"timeperiod":14},"MINUS_DI":{"timeperiod":14}, - "BBANDS":{"timeperiod":20,"nbdevup":2.0,"nbdevdn":2.0}, - "ATR":{"timeperiod":14},"NATR":{"timeperiod":14},"TRANGE":{}, - "STDDEV":{"timeperiod":20},"VAR":{"timeperiod":20}, - "SAR":{"acceleration":0.02,"maximum":0.2}, - "KELTNER_CHANNELS":{"timeperiod":20},"DONCHIAN":{"timeperiod":20}, - "SUPERTREND":{"timeperiod":7},"CHOPPINESS_INDEX":{"timeperiod":14}, - "OBV":{},"AD":{},"ADOSC":{"fastperiod":3,"slowperiod":10}, - "MFI":{"timeperiod":14},"VWAP":{}, - "AVGPRICE":{},"MEDPRICE":{},"TYPPRICE":{},"WCLPRICE":{}, - "SQRT":{},"LOG10":{},"ADD":{}, - "LINEARREG":{"timeperiod":14},"LINEARREG_SLOPE":{"timeperiod":14}, - "CORREL":{"timeperiod":30},"BETA":{"timeperiod":5}, - "HT_DCPERIOD":{},"HT_TRENDMODE":{}, - "CDLENGULFING":{},"CDLDOJI":{},"CDLHAMMER":{}, + "SMA": {"timeperiod": 20}, + "EMA": {"timeperiod": 20}, + "WMA": {"timeperiod": 14}, + "DEMA": {"timeperiod": 20}, + "TEMA": {"timeperiod": 20}, + "T3": {"timeperiod": 5}, + "TRIMA": {"timeperiod": 20}, + "KAMA": {"timeperiod": 10}, + "HULL_MA": {"timeperiod": 16}, + "VWMA": {"timeperiod": 20}, + "MIDPOINT": {"timeperiod": 14}, + "MIDPRICE": {"timeperiod": 14}, + "RSI": {"timeperiod": 14}, + "MACD": {"fastperiod": 12, "slowperiod": 26, "signalperiod": 9}, + "STOCH": {"fastk_period": 14, "slowk_period": 3, "slowd_period": 3}, + "CCI": {"timeperiod": 14}, + "WILLR": {"timeperiod": 14}, + "AROON": {"timeperiod": 14}, + "AROONOSC": {"timeperiod": 14}, + "ADX": {"timeperiod": 14}, + "MOM": {"timeperiod": 10}, + "ROC": {"timeperiod": 10}, + "CMO": {"timeperiod": 14}, + "PPO": {"fastperiod": 12, "slowperiod": 26}, + "TRIX": {"timeperiod": 18}, + "TSF": {"timeperiod": 14}, + "ULTOSC": {"timeperiod1": 7, "timeperiod2": 14, "timeperiod3": 28}, + "BOP": {}, + "PLUS_DI": {"timeperiod": 14}, + "MINUS_DI": {"timeperiod": 14}, + "BBANDS": {"timeperiod": 20, "nbdevup": 2.0, "nbdevdn": 2.0}, + "ATR": {"timeperiod": 14}, + "NATR": {"timeperiod": 14}, + "TRANGE": {}, + "STDDEV": {"timeperiod": 20}, + "VAR": {"timeperiod": 20}, + "SAR": {"acceleration": 0.02, "maximum": 0.2}, + "KELTNER_CHANNELS": {"timeperiod": 20}, + "DONCHIAN": {"timeperiod": 20}, + "SUPERTREND": {"timeperiod": 7}, + "CHOPPINESS_INDEX": {"timeperiod": 14}, + "OBV": {}, + "AD": {}, + "ADOSC": {"fastperiod": 3, "slowperiod": 10}, + "MFI": {"timeperiod": 14}, + "VWAP": {}, + "AVGPRICE": {}, + "MEDPRICE": {}, + "TYPPRICE": {}, + "WCLPRICE": {}, + "SQRT": {}, + "LOG10": {}, + "ADD": {}, + "LINEARREG": {"timeperiod": 14}, + "LINEARREG_SLOPE": {"timeperiod": 14}, + "CORREL": {"timeperiod": 30}, + "BETA": {"timeperiod": 5}, + "HT_DCPERIOD": {}, + "HT_TRENDMODE": {}, + "CDLENGULFING": {}, + "CDLDOJI": {}, + "CDLHAMMER": {}, } INDICATOR_NAMES = list(INDICATOR_DEFAULTS.keys()) -LIBRARY_NAMES = ["ferro_ta","talib","pandas_ta","ta","tulipy","finta"] +LIBRARY_NAMES = ["ferro_ta", "talib", "pandas_ta", "ta", "tulipy", "finta"] INDICATOR_CATEGORIES: dict[str, list[str]] = { - "Overlap": ["SMA","EMA","WMA","DEMA","TEMA","T3","TRIMA","KAMA","HULL_MA","VWMA","MIDPOINT","MIDPRICE"], - "Momentum": ["RSI","MACD","STOCH","CCI","WILLR","AROON","AROONOSC","ADX","MOM","ROC","CMO","PPO","TRIX","TSF","ULTOSC","BOP","PLUS_DI","MINUS_DI"], - "Volatility": ["BBANDS","ATR","NATR","TRANGE","STDDEV","VAR","SAR","KELTNER_CHANNELS","DONCHIAN","SUPERTREND","CHOPPINESS_INDEX"], - "Volume": ["OBV","AD","ADOSC","MFI","VWAP"], - "Price Transform": ["AVGPRICE","MEDPRICE","TYPPRICE","WCLPRICE"], - "Math": ["SQRT","LOG10","ADD"], - "Statistics": ["LINEARREG","LINEARREG_SLOPE","CORREL","BETA"], - "Cycle": ["HT_DCPERIOD","HT_TRENDMODE"], - "Pattern": ["CDLENGULFING","CDLDOJI","CDLHAMMER"], + "Overlap": [ + "SMA", + "EMA", + "WMA", + "DEMA", + "TEMA", + "T3", + "TRIMA", + "KAMA", + "HULL_MA", + "VWMA", + "MIDPOINT", + "MIDPRICE", + ], + "Momentum": [ + "RSI", + "MACD", + "STOCH", + "CCI", + "WILLR", + "AROON", + "AROONOSC", + "ADX", + "MOM", + "ROC", + "CMO", + "PPO", + "TRIX", + "TSF", + "ULTOSC", + "BOP", + "PLUS_DI", + "MINUS_DI", + ], + "Volatility": [ + "BBANDS", + "ATR", + "NATR", + "TRANGE", + "STDDEV", + "VAR", + "SAR", + "KELTNER_CHANNELS", + "DONCHIAN", + "SUPERTREND", + "CHOPPINESS_INDEX", + ], + "Volume": ["OBV", "AD", "ADOSC", "MFI", "VWAP"], + "Price Transform": ["AVGPRICE", "MEDPRICE", "TYPPRICE", "WCLPRICE"], + "Math": ["SQRT", "LOG10", "ADD"], + "Statistics": ["LINEARREG", "LINEARREG_SLOPE", "CORREL", "BETA"], + "Cycle": ["HT_DCPERIOD", "HT_TRENDMODE"], + "Pattern": ["CDLENGULFING", "CDLDOJI", "CDLHAMMER"], } # Cumulative: compare first-differences not absolute values -CUMULATIVE_INDICATORS = {"OBV","AD","ADOSC"} +CUMULATIVE_INDICATORS = {"OBV", "AD", "ADOSC"} # Binary output: use agreement rate not allclose -BINARY_INDICATORS = {"CDLENGULFING","CDLDOJI","CDLHAMMER","HT_TRENDMODE"} +BINARY_INDICATORS = {"CDLENGULFING", "CDLDOJI", "CDLHAMMER", "HT_TRENDMODE"} def execute_indicator(library, indicator, data, df=None, **kwargs): @@ -912,6 +2816,7 @@ def execute_indicator(library, indicator, data, df=None, **kwargs): raise KeyError(f"No wrapper for {key!r}") if df is None: from benchmarks.data_generator import get_pandas_ohlcv + df = get_pandas_ohlcv(data) params = {**INDICATOR_DEFAULTS.get(indicator, {}), **kwargs} return REGISTRY[key](data, df, **params) diff --git a/examples/backtesting.ipynb b/examples/backtesting.ipynb index ca77cee..b0eac7f 100644 --- a/examples/backtesting.ipynb +++ b/examples/backtesting.ipynb @@ -21,13 +21,13 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "\n", "import ferro_ta.config as config\n", - "from ferro_ta import BBANDS, EMA, RSI, SMA\n", + "import numpy as np\n", "from ferro_ta.backtest import backtest\n", "from ferro_ta.pipeline import Pipeline\n", "\n", + "from ferro_ta import BBANDS, EMA, RSI, SMA\n", + "\n", "# Synthetic data\n", "np.random.seed(42)\n", "n = 300\n", diff --git a/examples/quickstart.ipynb b/examples/quickstart.ipynb index 406f3d8..25f1c5f 100644 --- a/examples/quickstart.ipynb +++ b/examples/quickstart.ipynb @@ -207,9 +207,10 @@ "metadata": {}, "outputs": [], "source": [ - "from ferro_ta import FerroTAValueError\n", "from ferro_ta.exceptions import check_timeperiod\n", "\n", + "from ferro_ta import FerroTAValueError\n", + "\n", "try:\n", " check_timeperiod(0)\n", "except FerroTAValueError as e:\n", diff --git a/examples/streaming.ipynb b/examples/streaming.ipynb index a51596c..faa1d0d 100644 --- a/examples/streaming.ipynb +++ b/examples/streaming.ipynb @@ -22,7 +22,6 @@ "outputs": [], "source": [ "import numpy as np\n", - "\n", "from ferro_ta.streaming import (\n", " StreamingATR,\n", " StreamingBBands,\n", diff --git a/perf-contract/batch.json b/perf-contract/batch.json index fc110d9..77f8bf0 100644 --- a/perf-contract/batch.json +++ b/perf-contract/batch.json @@ -2,16 +2,38 @@ "metadata": { "suite": "batch", "runtime": { - "generated_at_utc": "2026-03-23T21:08:30.877702+00:00", - "python_version": "3.12.11", - "platform": "macOS-26.3.1-arm64-arm-64bit", + "generated_at_utc": "2026-03-24T09:13:04.010216+00:00", + "python_version": "3.13.5", + "python_implementation": "CPython", + "python_executable": "/Users/pratikbhadane/Work/Projects/ferro-ta/.venv/bin/python3", + "platform": "macOS-26.3.1-arm64-arm-64bit-Mach-O", + "system": "Darwin", + "release": "25.3.0", "machine": "arm64", - "processor": "arm" + "processor": "arm", + "cpu_model": "Apple M3 Max", + "cpu_count_logical": 14, + "total_memory_bytes": 38654705664 }, "git": { - "commit": "2d5000262f0f1439546bd4872235aae0333880a4", + "commit": "53566b9d82898fa4f95c5190156c969a0bd8e8e3", "dirty": true, - "branch": "feat/performace-1.0.2" + "branch": "main" + }, + "build": { + "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: aarch64-apple-darwin\nrelease: 1.93.1\nLLVM version: 21.1.8", + "cargo": "cargo 1.93.1 (083ac5135 2025-12-15)", + "cargo_release_profile": { + "lto": true, + "codegen-units": 1 + }, + "rustflags": null, + "cargo_build_rustflags": null, + "maturin_flags": null + }, + "packages": { + "numpy": "2.2.6", + "ferro-ta": "1.0.4" }, "dataset": { "n_samples": 20000, @@ -23,49 +45,49 @@ "results": [ { "indicator": "SMA", - "parallel_ms": 1.9615, - "sequential_ms": 2.0423, - "loop_ms": 0.8865, - "parallel_speedup_vs_loop": 0.452, - "sequential_speedup_vs_loop": 0.4341 + "parallel_ms": 7.9306, + "sequential_ms": 2.4832, + "loop_ms": 1.0238, + "parallel_speedup_vs_loop": 0.1291, + "sequential_speedup_vs_loop": 0.4123 }, { "indicator": "RSI", - "parallel_ms": 2.1731, - "sequential_ms": 4.3225, - "loop_ms": 3.2043, - "parallel_speedup_vs_loop": 1.4745, - "sequential_speedup_vs_loop": 0.7413 + "parallel_ms": 3.9307, + "sequential_ms": 5.0938, + "loop_ms": 3.6883, + "parallel_speedup_vs_loop": 0.9383, + "sequential_speedup_vs_loop": 0.7241 }, { "indicator": "ATR", - "parallel_ms": 4.2249, - "sequential_ms": 6.6175, - "loop_ms": 4.0382, - "parallel_speedup_vs_loop": 0.9558, - "sequential_speedup_vs_loop": 0.6102 + "parallel_ms": 9.2546, + "sequential_ms": 7.768, + "loop_ms": 5.2669, + "parallel_speedup_vs_loop": 0.5691, + "sequential_speedup_vs_loop": 0.678 }, { "indicator": "ADX", - "parallel_ms": 4.8674, - "sequential_ms": 7.6402, - "loop_ms": 5.1861, - "parallel_speedup_vs_loop": 1.0655, - "sequential_speedup_vs_loop": 0.6788 + "parallel_ms": 9.5489, + "sequential_ms": 9.1403, + "loop_ms": 7.1578, + "parallel_speedup_vs_loop": 0.7496, + "sequential_speedup_vs_loop": 0.7831 } ], "grouped_results": [ { "case": "close_bundle_3", - "grouped_ms": 0.1878, - "separate_ms": 0.1719, - "speedup_vs_separate": 0.9155 + "grouped_ms": 0.466, + "separate_ms": 0.2003, + "speedup_vs_separate": 0.4298 }, { "case": "hlc_bundle_3", - "grouped_ms": 0.2958, - "separate_ms": 0.4633, - "speedup_vs_separate": 1.5666 + "grouped_ms": 1.2538, + "separate_ms": 0.6999, + "speedup_vs_separate": 0.5582 } ] -} \ No newline at end of file +} diff --git a/perf-contract/indicator_latency.json b/perf-contract/indicator_latency.json index 3be2e9f..1d9e3b2 100644 --- a/perf-contract/indicator_latency.json +++ b/perf-contract/indicator_latency.json @@ -2,16 +2,38 @@ "metadata": { "suite": "indicator_latency", "runtime": { - "generated_at_utc": "2026-03-23T21:08:30.515962+00:00", - "python_version": "3.12.11", - "platform": "macOS-26.3.1-arm64-arm-64bit", + "generated_at_utc": "2026-03-24T09:13:02.973728+00:00", + "python_version": "3.13.5", + "python_implementation": "CPython", + "python_executable": "/Users/pratikbhadane/Work/Projects/ferro-ta/.venv/bin/python3", + "platform": "macOS-26.3.1-arm64-arm-64bit-Mach-O", + "system": "Darwin", + "release": "25.3.0", "machine": "arm64", - "processor": "arm" + "processor": "arm", + "cpu_model": "Apple M3 Max", + "cpu_count_logical": 14, + "total_memory_bytes": 38654705664 }, "git": { - "commit": "2d5000262f0f1439546bd4872235aae0333880a4", + "commit": "53566b9d82898fa4f95c5190156c969a0bd8e8e3", "dirty": true, - "branch": "feat/performace-1.0.2" + "branch": "main" + }, + "build": { + "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: aarch64-apple-darwin\nrelease: 1.93.1\nLLVM version: 21.1.8", + "cargo": "cargo 1.93.1 (083ac5135 2025-12-15)", + "cargo_release_profile": { + "lto": true, + "codegen-units": 1 + }, + "rustflags": null, + "cargo_build_rustflags": null, + "maturin_flags": null + }, + "packages": { + "numpy": "2.2.6", + "ferro-ta": "1.0.4" }, "fixtures": [ { @@ -33,7 +55,7 @@ "kwargs": { "timeperiod": 20 }, - "elapsed_ms": 0.0202 + "elapsed_ms": 0.0233 }, { "name": "WILLR_14", @@ -41,13 +63,13 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0183 + "elapsed_ms": 0.0207 }, { "name": "STOCH", "inputs": "hlc", "kwargs": {}, - "elapsed_ms": 0.0181 + "elapsed_ms": 0.0202 }, { "name": "ADX_14", @@ -55,7 +77,7 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0147 + "elapsed_ms": 0.0171 }, { "name": "CCI_14", @@ -63,13 +85,13 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0144 + "elapsed_ms": 0.0164 }, { "name": "MACD", "inputs": "close", "kwargs": {}, - "elapsed_ms": 0.0132 + "elapsed_ms": 0.015 }, { "name": "ATR_14", @@ -77,7 +99,7 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0108 + "elapsed_ms": 0.0116 }, { "name": "RSI_14", @@ -85,7 +107,7 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0102 + "elapsed_ms": 0.011 }, { "name": "STDDEV_20", @@ -93,7 +115,7 @@ "kwargs": { "timeperiod": 20 }, - "elapsed_ms": 0.0086 + "elapsed_ms": 0.0103 }, { "name": "BETA_5", @@ -101,7 +123,7 @@ "kwargs": { "timeperiod": 5 }, - "elapsed_ms": 0.008 + "elapsed_ms": 0.0091 }, { "name": "CORREL_30", @@ -109,15 +131,7 @@ "kwargs": { "timeperiod": 30 }, - "elapsed_ms": 0.0068 - }, - { - "name": "EMA_20", - "inputs": "close", - "kwargs": { - "timeperiod": 20 - }, - "elapsed_ms": 0.0052 + "elapsed_ms": 0.0078 }, { "name": "BBANDS_20", @@ -125,7 +139,7 @@ "kwargs": { "timeperiod": 20 }, - "elapsed_ms": 0.005 + "elapsed_ms": 0.0062 }, { "name": "TSF_14", @@ -133,7 +147,15 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0048 + "elapsed_ms": 0.0056 + }, + { + "name": "EMA_20", + "inputs": "close", + "kwargs": { + "timeperiod": 20 + }, + "elapsed_ms": 0.0055 }, { "name": "LINEARREG_14", @@ -141,7 +163,7 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0046 + "elapsed_ms": 0.0054 }, { "name": "LINEARREG_SLOPE_14", @@ -149,7 +171,7 @@ "kwargs": { "timeperiod": 14 }, - "elapsed_ms": 0.0045 + "elapsed_ms": 0.005 }, { "name": "SMA_20", @@ -157,7 +179,7 @@ "kwargs": { "timeperiod": 20 }, - "elapsed_ms": 0.0027 + "elapsed_ms": 0.0032 } ] -} \ No newline at end of file +} diff --git a/perf-contract/manifest.json b/perf-contract/manifest.json index 39d6e1f..01b5fe6 100644 --- a/perf-contract/manifest.json +++ b/perf-contract/manifest.json @@ -2,16 +2,38 @@ "metadata": { "suite": "perf_contract", "runtime": { - "generated_at_utc": "2026-03-23T21:09:13.077731+00:00", - "python_version": "3.12.11", - "platform": "macOS-26.3.1-arm64-arm-64bit", + "generated_at_utc": "2026-03-24T09:13:08.787230+00:00", + "python_version": "3.13.5", + "python_implementation": "CPython", + "python_executable": "/Users/pratikbhadane/Work/Projects/ferro-ta/.venv/bin/python3", + "platform": "macOS-26.3.1-arm64-arm-64bit-Mach-O", + "system": "Darwin", + "release": "25.3.0", "machine": "arm64", - "processor": "arm" + "processor": "arm", + "cpu_model": "Apple M3 Max", + "cpu_count_logical": 14, + "total_memory_bytes": 38654705664 }, "git": { - "commit": "2d5000262f0f1439546bd4872235aae0333880a4", + "commit": "53566b9d82898fa4f95c5190156c969a0bd8e8e3", "dirty": true, - "branch": "feat/performace-1.0.2" + "branch": "main" + }, + "build": { + "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: aarch64-apple-darwin\nrelease: 1.93.1\nLLVM version: 21.1.8", + "cargo": "cargo 1.93.1 (083ac5135 2025-12-15)", + "cargo_release_profile": { + "lto": true, + "codegen-units": 1 + }, + "rustflags": null, + "cargo_build_rustflags": null, + "maturin_flags": null + }, + "packages": { + "numpy": "2.2.6", + "ferro-ta": "1.0.4" }, "fixtures": [ { @@ -25,28 +47,23 @@ "artifacts": { "indicator_latency": { "path": "perf-contract/indicator_latency.json", - "size_bytes": 3212, - "sha256": "1e7f844fe6eb467f07bbb1e4eb918c56861e8cf819ab4802b96e033c6d2570c4" + "size_bytes": 4041, + "sha256": "564027c7abed7ecd4ae2ac1720217d96e1e31807f8ac7d5c5393c8fd974f13ed" }, "batch": { "path": "perf-contract/batch.json", - "size_bytes": 1679, - "sha256": "202d67b24a88184473f93cec9f866ca34ff60721068e15e7e5d23962f006d169" + "size_bytes": 2507, + "sha256": "c50f242a138a0c358a6fa86c420954b4b11a2200598ca2942bcd0fd2bc456fb2" }, "streaming": { "path": "perf-contract/streaming.json", - "size_bytes": 1939, - "sha256": "04476c3d95a9e7d87e40331b28c5ccad9a7aa5abcfc47fb6ac7e929365f68554" + "size_bytes": 2766, + "sha256": "d271d3219ec098e443e84ef648ab4220cd2a38e6dc998bc6c9fc545d7fc77716" }, "runtime_hotspots": { "path": "perf-contract/runtime_hotspots.json", - "size_bytes": 2365, - "sha256": "d37a58bc88def9f1525b9b0f64f0bd7b9c0df9ed4cae260c833697f6a1689ca8" - }, - "simd": { - "path": "perf-contract/simd.json", - "size_bytes": 7670, - "sha256": "3f9b341a8206698ed172650752d808621c9b5218f42a2373f0f4485496197b4c" + "size_bytes": 3197, + "sha256": "96eafc9a61ac410e47fcdfee6a9db1123cdee0ec05f49a85c631a3975063deed" } } -} \ No newline at end of file +} diff --git a/perf-contract/runtime_hotspots.json b/perf-contract/runtime_hotspots.json index 92c03cb..2e8e2df 100644 --- a/perf-contract/runtime_hotspots.json +++ b/perf-contract/runtime_hotspots.json @@ -2,16 +2,38 @@ "metadata": { "suite": "runtime_hotspots", "runtime": { - "generated_at_utc": "2026-03-23T21:08:34.436165+00:00", - "python_version": "3.12.11", - "platform": "macOS-26.3.1-arm64-arm-64bit", + "generated_at_utc": "2026-03-24T09:13:08.521805+00:00", + "python_version": "3.13.5", + "python_implementation": "CPython", + "python_executable": "/Users/pratikbhadane/Work/Projects/ferro-ta/.venv/bin/python3", + "platform": "macOS-26.3.1-arm64-arm-64bit-Mach-O", + "system": "Darwin", + "release": "25.3.0", "machine": "arm64", - "processor": "arm" + "processor": "arm", + "cpu_model": "Apple M3 Max", + "cpu_count_logical": 14, + "total_memory_bytes": 38654705664 }, "git": { - "commit": "2d5000262f0f1439546bd4872235aae0333880a4", + "commit": "53566b9d82898fa4f95c5190156c969a0bd8e8e3", "dirty": true, - "branch": "feat/performace-1.0.2" + "branch": "main" + }, + "build": { + "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: aarch64-apple-darwin\nrelease: 1.93.1\nLLVM version: 21.1.8", + "cargo": "cargo 1.93.1 (083ac5135 2025-12-15)", + "cargo_release_profile": { + "lto": true, + "codegen-units": 1 + }, + "rustflags": null, + "cargo_build_rustflags": null, + "maturin_flags": null + }, + "packages": { + "numpy": "2.2.6", + "ferro-ta": "1.0.4" }, "dataset": { "price_bars": 20000, @@ -23,74 +45,74 @@ { "category": "python_analysis", "name": "iv_zscore", - "fast_ms": 28.857, - "reference_ms": 897.0178, - "speedup_vs_reference": 31.0849, - "share_of_suite_pct": 70.0 + "fast_ms": 32.5847, + "reference_ms": 990.4233, + "speedup_vs_reference": 30.3954, + "share_of_suite_pct": 69.27 }, { "category": "python_analysis", "name": "iv_rank", - "fast_ms": 10.8496, - "reference_ms": 199.4376, - "speedup_vs_reference": 18.3821, - "share_of_suite_pct": 26.32 + "fast_ms": 12.2974, + "reference_ms": 233.0671, + "speedup_vs_reference": 18.9525, + "share_of_suite_pct": 26.14 }, { "category": "python_analysis", "name": "iv_percentile", - "fast_ms": 0.9124, - "reference_ms": 79.6557, - "speedup_vs_reference": 87.3019, - "share_of_suite_pct": 2.21 + "fast_ms": 0.9511, + "reference_ms": 90.5663, + "speedup_vs_reference": 95.2202, + "share_of_suite_pct": 2.02 }, { "category": "ffi_grouping", "name": "feature_matrix", - "fast_ms": 0.2411, - "reference_ms": 0.2215, - "speedup_vs_reference": 0.9186, - "share_of_suite_pct": 0.58 + "fast_ms": 0.6619, + "reference_ms": 0.6155, + "speedup_vs_reference": 0.9299, + "share_of_suite_pct": 1.41 }, { "category": "ffi_grouping", "name": "compute_many_close", - "fast_ms": 0.1563, - "reference_ms": 0.1498, - "speedup_vs_reference": 0.9587, - "share_of_suite_pct": 0.38 + "fast_ms": 0.3175, + "reference_ms": 0.2437, + "speedup_vs_reference": 0.7675, + "share_of_suite_pct": 0.67 }, { "category": "rust_kernel", "name": "BETA", - "fast_ms": 0.0694, - "reference_ms": 159.2715, - "speedup_vs_reference": 2294.4163, - "share_of_suite_pct": 0.17 + "fast_ms": 0.0742, + "reference_ms": 188.6048, + "speedup_vs_reference": 2541.5695, + "share_of_suite_pct": 0.16 }, { "category": "rust_kernel", "name": "CORREL", - "fast_ms": 0.0555, - "reference_ms": 158.2775, - "speedup_vs_reference": 2851.8468, - "share_of_suite_pct": 0.13 - }, - { - "category": "rust_kernel", - "name": "LINEARREG", - "fast_ms": 0.0414, - "reference_ms": 44.8111, - "speedup_vs_reference": 1081.9491, - "share_of_suite_pct": 0.1 + "fast_ms": 0.0638, + "reference_ms": 215.973, + "speedup_vs_reference": 3387.8115, + "share_of_suite_pct": 0.14 }, { "category": "rust_kernel", "name": "TSF", - "fast_ms": 0.0413, - "reference_ms": 46.3799, - "speedup_vs_reference": 1122.1039, - "share_of_suite_pct": 0.1 + "fast_ms": 0.0441, + "reference_ms": 49.6108, + "speedup_vs_reference": 1125.3954, + "share_of_suite_pct": 0.09 + }, + { + "category": "rust_kernel", + "name": "LINEARREG", + "fast_ms": 0.044, + "reference_ms": 57.9675, + "speedup_vs_reference": 1318.7009, + "share_of_suite_pct": 0.09 } ] -} \ No newline at end of file +} diff --git a/perf-contract/simd.json b/perf-contract/simd.json index 098e5cc..b220833 100644 --- a/perf-contract/simd.json +++ b/perf-contract/simd.json @@ -282,4 +282,4 @@ ] } } -} \ No newline at end of file +} diff --git a/perf-contract/streaming.json b/perf-contract/streaming.json index 92f6471..239adce 100644 --- a/perf-contract/streaming.json +++ b/perf-contract/streaming.json @@ -2,16 +2,38 @@ "metadata": { "suite": "streaming", "runtime": { - "generated_at_utc": "2026-03-23T21:08:30.968886+00:00", - "python_version": "3.12.11", - "platform": "macOS-26.3.1-arm64-arm-64bit", + "generated_at_utc": "2026-03-24T09:13:04.351797+00:00", + "python_version": "3.13.5", + "python_implementation": "CPython", + "python_executable": "/Users/pratikbhadane/Work/Projects/ferro-ta/.venv/bin/python3", + "platform": "macOS-26.3.1-arm64-arm-64bit-Mach-O", + "system": "Darwin", + "release": "25.3.0", "machine": "arm64", - "processor": "arm" + "processor": "arm", + "cpu_model": "Apple M3 Max", + "cpu_count_logical": 14, + "total_memory_bytes": 38654705664 }, "git": { - "commit": "2d5000262f0f1439546bd4872235aae0333880a4", + "commit": "53566b9d82898fa4f95c5190156c969a0bd8e8e3", "dirty": true, - "branch": "feat/performace-1.0.2" + "branch": "main" + }, + "build": { + "rustc": "rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: aarch64-apple-darwin\nrelease: 1.93.1\nLLVM version: 21.1.8", + "cargo": "cargo 1.93.1 (083ac5135 2025-12-15)", + "cargo_release_profile": { + "lto": true, + "codegen-units": 1 + }, + "rustflags": null, + "cargo_build_rustflags": null, + "maturin_flags": null + }, + "packages": { + "numpy": "2.2.6", + "ferro-ta": "1.0.4" }, "dataset": { "n_bars": 20000, @@ -22,52 +44,52 @@ { "indicator": "StreamingSMA", "inputs": "close", - "stream_total_ms": 0.8808, - "batch_total_ms": 0.0153, - "stream_ns_per_update": 44.04, - "batch_ns_per_bar": 0.77, - "updates_per_second": 22705779.59, - "stream_over_batch_ratio": 57.4469 + "stream_total_ms": 0.9927, + "batch_total_ms": 0.0166, + "stream_ns_per_update": 49.63, + "batch_ns_per_bar": 0.83, + "updates_per_second": 20147763.43, + "stream_over_batch_ratio": 59.7092 }, { "indicator": "StreamingEMA", "inputs": "close", - "stream_total_ms": 0.8611, - "batch_total_ms": 0.0408, - "stream_ns_per_update": 43.06, - "batch_ns_per_bar": 2.04, - "updates_per_second": 23225431.81, - "stream_over_batch_ratio": 21.0884 + "stream_total_ms": 0.9459, + "batch_total_ms": 0.0435, + "stream_ns_per_update": 47.3, + "batch_ns_per_bar": 2.18, + "updates_per_second": 21143503.9, + "stream_over_batch_ratio": 21.7247 }, { "indicator": "StreamingRSI", "inputs": "close", - "stream_total_ms": 0.9235, - "batch_total_ms": 0.0932, - "stream_ns_per_update": 46.17, - "batch_ns_per_bar": 4.66, - "updates_per_second": 21656740.75, - "stream_over_batch_ratio": 9.9035 + "stream_total_ms": 1.0059, + "batch_total_ms": 0.0991, + "stream_ns_per_update": 50.3, + "batch_ns_per_bar": 4.95, + "updates_per_second": 19882356.06, + "stream_over_batch_ratio": 10.1523 }, { "indicator": "StreamingATR", "inputs": "hlc", - "stream_total_ms": 2.0074, - "batch_total_ms": 0.0935, - "stream_ns_per_update": 100.37, - "batch_ns_per_bar": 4.68, - "updates_per_second": 9963056.99, - "stream_over_batch_ratio": 21.4603 + "stream_total_ms": 2.1574, + "batch_total_ms": 0.0992, + "stream_ns_per_update": 107.87, + "batch_ns_per_bar": 4.96, + "updates_per_second": 9270525.53, + "stream_over_batch_ratio": 21.746 }, { "indicator": "StreamingVWAP", "inputs": "hlcv", - "stream_total_ms": 2.6068, - "batch_total_ms": 0.0223, - "stream_ns_per_update": 130.34, - "batch_ns_per_bar": 1.11, - "updates_per_second": 7672265.38, - "stream_over_batch_ratio": 116.9385 + "stream_total_ms": 2.6935, + "batch_total_ms": 0.0252, + "stream_ns_per_update": 134.68, + "batch_ns_per_bar": 1.26, + "updates_per_second": 7425170.07, + "stream_over_batch_ratio": 106.8484 } ] -} \ No newline at end of file +} diff --git a/pyproject.toml b/pyproject.toml index 79fece5..0f7abe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ ignore = ["E501", "UP006", "UP045", "UP007"] "tests/*" = ["N801", "N802", "N806", "E402", "E741", "F811"] "tests/unit/*" = ["N801", "N802", "N806", "E402", "E741", "F811"] "tests/integration/*" = ["N801", "N802", "N806", "E402", "E741", "F811"] +"benchmarks/*" = ["E402", "E741"] "python/ferro_ta/*.py" = ["N802"] # Public API: SMA, RSI, ATR, etc. "python/ferro_ta/__init__.py" = ["E402", "N802"] # Re-export surface by design "python/ferro_ta/__init__.pyi" = ["N802", "E402"] diff --git a/python/ferro_ta/analysis/backtest.py b/python/ferro_ta/analysis/backtest.py index a3f59dd..168dc15 100644 --- a/python/ferro_ta/analysis/backtest.py +++ b/python/ferro_ta/analysis/backtest.py @@ -158,7 +158,9 @@ def rsi_strategy( c = np.asarray(close, dtype=np.float64) return np.asarray( - _rust_rsi_threshold_signals(c, int(timeperiod), float(oversold), float(overbought)), + _rust_rsi_threshold_signals( + c, int(timeperiod), float(oversold), float(overbought) + ), dtype=np.float64, ) diff --git a/python/ferro_ta/analysis/derivatives_payoff.py b/python/ferro_ta/analysis/derivatives_payoff.py index 7891bca..c20fc8b 100644 --- a/python/ferro_ta/analysis/derivatives_payoff.py +++ b/python/ferro_ta/analysis/derivatives_payoff.py @@ -173,7 +173,9 @@ def strategy_payoff( return np.zeros_like(grid) try: - return np.asarray(_rust_strategy_payoff_legs(grid, normalized), dtype=np.float64) + return np.asarray( + _rust_strategy_payoff_legs(grid, normalized), dtype=np.float64 + ) except ValueError as err: _normalize_rust_error(err) diff --git a/python/ferro_ta/analysis/features.py b/python/ferro_ta/analysis/features.py index 21629ef..e0bad2a 100644 --- a/python/ferro_ta/analysis/features.py +++ b/python/ferro_ta/analysis/features.py @@ -33,7 +33,9 @@ __all__ = [ def _forward_fill_nan(arr: NDArray[np.float64]) -> NDArray[np.float64]: - return np.asarray(_rust_forward_fill_nan(np.ascontiguousarray(arr, dtype=np.float64))) + return np.asarray( + _rust_forward_fill_nan(np.ascontiguousarray(arr, dtype=np.float64)) + ) # --------------------------------------------------------------------------- diff --git a/python/ferro_ta/mcp/__init__.py b/python/ferro_ta/mcp/__init__.py index 459391e..3aee4b3 100644 --- a/python/ferro_ta/mcp/__init__.py +++ b/python/ferro_ta/mcp/__init__.py @@ -12,7 +12,7 @@ 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 +from typing import Any, cast, get_args, get_origin, get_type_hints import numpy as np @@ -156,38 +156,42 @@ def _object_snapshot(value: Any) -> Any: if isinstance(value, enum.Enum): return value.value - if dataclasses.is_dataclass(value): + if dataclasses.is_dataclass(value) and not isinstance(value, type): return _normalise_json(dataclasses.asdict(value), store_objects=False) - if hasattr(value, "to_dict") and callable(value.to_dict): + dynamic_value = cast(Any, value) + + if hasattr(dynamic_value, "to_dict") and callable(dynamic_value.to_dict): try: - return _normalise_json(value.to_dict(), store_objects=False) + return _normalise_json(dynamic_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"): + if dynamic_value.__class__.__module__.startswith("pandas"): return _normalise_json( - value.to_dict(as_series=False), store_objects=False + dynamic_value.to_dict(orient="list"), store_objects=False + ) + if dynamic_value.__class__.__module__.startswith("polars"): + return _normalise_json( + dynamic_value.to_dict(as_series=False), store_objects=False ) except Exception: return None - if hasattr(value, "__dict__"): + if hasattr(dynamic_value, "__dict__"): fields = { key: val - for key, val in vars(value).items() + for key, val in vars(dynamic_value).items() if not key.startswith("_") and not callable(val) } if fields: return _normalise_json(fields, store_objects=False) - slots = getattr(type(value), "__slots__", ()) + slots = getattr(type(dynamic_value), "__slots__", ()) if slots: fields = {} for slot in slots: - if slot.startswith("_") or not hasattr(value, slot): + if slot.startswith("_") or not hasattr(dynamic_value, slot): continue - slot_value = getattr(value, slot) + slot_value = getattr(dynamic_value, slot) if callable(slot_value): continue fields[slot] = slot_value @@ -215,7 +219,10 @@ def _normalise_json(value: Any, *, store_objects: bool = True) -> Any: 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()] + 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) @@ -408,7 +415,9 @@ def _annotation_includes_custom_class(annotation: Any) -> bool: return False -def _schema_and_py_type(annotation: Any, *, param_name: str) -> tuple[dict[str, Any], Any]: +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: @@ -437,18 +446,25 @@ def _schema_and_py_type(annotation: Any, *, param_name: str) -> tuple[dict[str, if "scalarorarray" in lower: return { "type": _JSON_ANY_TYPE, - "description": f"Parameter `{param_name}`. { _REFERENCE_HELP }", + "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: + 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): + if _annotation_includes_callable(annotation) or _annotation_includes_custom_class( + annotation + ): return { "type": _JSON_ANY_TYPE, "description": f"Parameter `{param_name}`. {_REFERENCE_HELP}", @@ -639,7 +655,10 @@ def _invoke_target( 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()} + { + str(key): _decode_value(item, Any) + for key, item in extra_kwargs.items() + } ) continue @@ -689,8 +708,7 @@ def _describe_instance_payload(identifier: str) -> dict[str, Any]: def _list_instances_payload() -> list[dict[str, Any]]: """Return current stored-object metadata.""" return [ - _describe_instance_payload(identifier) - for identifier in sorted(_INSTANCE_STORE) + _describe_instance_payload(identifier) for identifier in sorted(_INSTANCE_STORE) ] @@ -719,7 +737,9 @@ def _build_public_tool_spec(item: dict[str, str], target: Any) -> _ToolSpec: 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: + def invoke_enum( + 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) @@ -730,7 +750,7 @@ def _build_public_tool_spec(item: dict[str, str], target: Any) -> _ToolSpec: description=description, input_schema=input_schema, wrapper_signature=wrapper_signature, - invoke=invoke, + invoke=invoke_enum, ) signature = inspect.signature(target) @@ -746,7 +766,7 @@ def _build_public_tool_spec(item: dict[str, str], target: Any) -> _ToolSpec: or f"Construct a {item['name']} instance. Returns a stored instance reference." ) - def invoke( + def invoke_target( arguments: dict[str, Any], *, raw_target: Any = target, @@ -770,7 +790,7 @@ def _build_public_tool_spec(item: dict[str, str], target: Any) -> _ToolSpec: description=description, input_schema=input_schema, wrapper_signature=wrapper_signature, - invoke=invoke, + invoke=invoke_target, ) @@ -1136,7 +1156,9 @@ def _instance_management_specs() -> list[_ToolSpec]: 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}") + 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) @@ -1247,7 +1269,7 @@ def _make_fastmcp_wrapper(spec: _ToolSpec) -> Callable[..., Any]: wrapper.__name__ = f"tool_{_slugify(spec.name).replace('-', '_')}" wrapper.__doc__ = spec.description - wrapper.__signature__ = spec.wrapper_signature + setattr(wrapper, "__signature__", spec.wrapper_signature) wrapper.__annotations__ = { parameter.name: ( Any if parameter.annotation is inspect._empty else parameter.annotation @@ -1311,7 +1333,9 @@ def create_server() -> Any: ) for spec in _tool_catalog().values(): - app.add_tool(_make_fastmcp_wrapper(spec), name=spec.name, description=spec.description) + app.add_tool( + _make_fastmcp_wrapper(spec), name=spec.name, description=spec.description + ) return app diff --git a/scripts/build_api_manifest.py b/scripts/build_api_manifest.py index 8bfff33..279ba26 100644 --- a/scripts/build_api_manifest.py +++ b/scripts/build_api_manifest.py @@ -32,7 +32,9 @@ def _load_api_info_module(root: Path, module_path: Path): python_root = str(root / "python") if python_root not in sys.path: sys.path.insert(0, python_root) - spec = importlib.util.spec_from_file_location("ferro_ta_tools_api_info", module_path) + spec = importlib.util.spec_from_file_location( + "ferro_ta_tools_api_info", module_path + ) if spec is None or spec.loader is None: raise RuntimeError(f"Could not load module spec from {module_path}") module = importlib.util.module_from_spec(spec) @@ -217,7 +219,9 @@ def _safe_git_head(root: Path) -> str | None: return value or None -def build_manifest(root: Path, include_runtime_metadata: bool = False) -> dict[str, Any]: +def build_manifest( + root: Path, include_runtime_metadata: bool = False +) -> dict[str, Any]: python_api = _extract_python_api(root) rust_core = _extract_core_exports(root) wasm_exports = _extract_wasm_exports(root) @@ -279,7 +283,9 @@ def main() -> None: output_path = (root / args.output).resolve() output_path.parent.mkdir(parents=True, exist_ok=True) - manifest = build_manifest(root, include_runtime_metadata=args.include_runtime_metadata) + manifest = build_manifest( + root, include_runtime_metadata=args.include_runtime_metadata + ) output_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") print(f"Wrote API manifest to {output_path}") diff --git a/scripts/bump_version.py b/scripts/bump_version.py index b409fbc..ba5011d 100644 --- a/scripts/bump_version.py +++ b/scripts/bump_version.py @@ -15,7 +15,6 @@ import re from dataclasses import dataclass from pathlib import Path - ROOT = Path(__file__).resolve().parent.parent SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$") diff --git a/scripts/pre_push_checks.sh b/scripts/pre_push_checks.sh index 1e3ca6e..86cff24 100755 --- a/scripts/pre_push_checks.sh +++ b/scripts/pre_push_checks.sh @@ -66,8 +66,8 @@ ensure_python_env() { fi need_cmd uv - run_cmd uv sync --extra dev --extra docs - run_cmd uv run --extra dev --extra docs maturin develop --release + run_cmd uv sync --extra dev --extra docs --extra mcp + run_cmd uv run --extra dev --extra docs --extra mcp maturin develop --release python_env_ready=1 } @@ -110,13 +110,13 @@ run_python_lint() { run_python_typecheck() { need_cmd uv - run_cmd uv run --with mypy --with numpy mypy python/ferro_ta --ignore-missing-imports --no-error-summary - run_cmd uv run --with pyright pyright python/ferro_ta + run_cmd uv run --with mypy --with numpy python -m mypy python/ferro_ta --ignore-missing-imports --no-error-summary + run_cmd uv run --with pyright python -m pyright python/ferro_ta } run_python_test() { ensure_python_env - run_cmd uv run --extra dev --with pytest-cov pytest tests/unit/ tests/integration/ -v --cov=ferro_ta --cov-report=term-missing --cov-fail-under=65 + run_cmd uv run --extra dev --extra mcp --with pytest-cov pytest tests/unit/ tests/integration/ -v --cov=ferro_ta --cov-report=term-missing --cov-fail-under=65 } run_docs() { diff --git a/tests/integration/test_cross_surface_manifest.py b/tests/integration/test_cross_surface_manifest.py index 3443673..653593d 100644 --- a/tests/integration/test_cross_surface_manifest.py +++ b/tests/integration/test_cross_surface_manifest.py @@ -1,8 +1,8 @@ from __future__ import annotations import json -from pathlib import Path import sys +from pathlib import Path ROOT = Path(__file__).resolve().parents[2] SCRIPTS = ROOT / "scripts" diff --git a/tests/integration/test_wasm_node_conformance.py b/tests/integration/test_wasm_node_conformance.py index d21f981..8041988 100644 --- a/tests/integration/test_wasm_node_conformance.py +++ b/tests/integration/test_wasm_node_conformance.py @@ -50,7 +50,9 @@ def _run_node_conformance() -> dict[str, list[float | None]]: if shutil.which("node") is None: pytest.skip("node is required for wasm/node conformance test") if not PKG_JS.exists(): - pytest.skip("wasm/pkg not found; run `wasm-pack build --target nodejs --out-dir pkg`") + pytest.skip( + "wasm/pkg not found; run `wasm-pack build --target nodejs --out-dir pkg`" + ) _write_node_conformance_script(SCRIPT) try: @@ -78,7 +80,9 @@ def _assert_close_with_null_nan( ) -> None: assert len(actual) == len(expected) a = np.array([np.nan if v is None else float(v) for v in actual], dtype=np.float64) - e = np.array([np.nan if v is None else float(v) for v in expected], dtype=np.float64) + e = np.array( + [np.nan if v is None else float(v) for v in expected], dtype=np.float64 + ) np.testing.assert_allclose(a, e, atol=atol, rtol=0.0, equal_nan=True) diff --git a/tests/unit/test_tools_and_api.py b/tests/unit/test_tools_and_api.py index 4f04869..d91f228 100644 --- a/tests/unit/test_tools_and_api.py +++ b/tests/unit/test_tools_and_api.py @@ -1403,9 +1403,9 @@ class TestMCPCallTool: instance_id = created["instance_id"] described = json.loads( - handle_call_tool( - "describe_instance", {"instance_id": instance_id} - )["content"][0]["text"] + 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 @@ -1429,9 +1429,9 @@ class TestMCPCallTool: assert "close" in aggregated deleted = json.loads( - handle_call_tool( - "delete_instance", {"instance_id": instance_id} - )["content"][0]["text"] + handle_call_tool("delete_instance", {"instance_id": instance_id})[ + "content" + ][0]["text"] ) assert deleted["deleted"] is True @@ -1441,9 +1441,9 @@ class TestMCPCallTool: from ferro_ta.mcp import handle_call_tool wrapped = json.loads( - handle_call_tool( - "traced", {"func": {"callable": "SMA"}} - )["content"][0]["text"] + handle_call_tool("traced", {"func": {"callable": "SMA"}})["content"][0][ + "text" + ] ) instance_id = wrapped["instance_id"] diff --git a/uv.lock b/uv.lock index 49d09ef..f5bde28 100644 --- a/uv.lock +++ b/uv.lock @@ -2315,11 +2315,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [package.optional-dependencies]