Files
Pratik Bhadane 288b1546b2 feat: broaden CPU and platform coverage across PyPI, nodes, and crate… (#26)
* feat: broaden CPU and platform coverage across PyPI, nodes, and crates.io

Replace static SIMD with runtime CPU-feature dispatch and expand the release
wheel matrix so one set of artifacts runs on any target CPU and platform
without illegal-instruction crashes.

Rust core:
- Add multiversion runtime dispatch (crates/ferro_ta_core/src/simd.rs); drop
  compile-time `wide`. `simd` feature is now default-on and forwarded through
  the pyo3 crate, and stays compatible with #![forbid(unsafe_code)].

Packaging:
- abi3-py310: one cp310-abi3 wheel per platform (covers CPython 3.10+).
- CI matrix adds Linux aarch64 + musllinux (x86_64/aarch64) and Windows arm64.

Node/Docker + docs:
- api/Dockerfile: document baseline+dispatch (no target-cpu pin) and add a
  fail-fast import check; aarch64 containers now install cleanly.
- Rewrite docs/guides/simd.md; fix stale `wide` mention in ADR 0003.
- Add ADR 0006 (CPU coverage strategy).

Also bundles in-flight release prep already staged in the tree (DTW exception
types, SBOM/provenance security, supporting docs).

* fix(ci): clear cargo-deny and pip-audit failures; apply dependency bumps

cargo-deny (advisories):
- Ignore pyo3 RUSTSEC-2026-0176 / RUSTSEC-2026-0177 in deny.toml with a
  documented rationale: ferro-ta uses neither affected code path
  (PyList/PyTuple nth iterators; PyCFunction::new_closure). Upstream fix
  needs pyo3 >=0.29 (large API migration), tracked as a follow-up.

pip-audit:
- Bump dev lockfile idna 3.18, pytest 9.1.1, urllib3 2.7.0 to clear
  PYSEC-2026-215, CVE-2025-71176, PYSEC-2026-141/142.

Dependency bumps (supersede open dependabot PRs; they auto-close on merge):
- cargo: log 0.4.32, serde_json 1.0.150, rayon 1.12.0
- api/requirements.txt: uvicorn>=0.49.0, pydantic>=2.13.4, ferro-ta>=1.1.4
- CI actions: deploy-pages v5, upload-pages-artifact v5, action-gh-release v3

The open `wide` 1.5.0 bump (PR #24) is obsolete — the crate is removed in
this branch.

* chore: address CodeRabbit review; remove docs/adr section

CodeRabbit findings:
- CI sbom job: add `attestations: write` so attest-build-provenance can run
  (it had only contents:write + id-token:write).
- simd.rs: vectorize `wma_seed` with lane-local accumulators — it was scalar
  behind the multiversion wrapper, adding dispatch overhead for no SIMD gain.
- CHANGELOG: consolidate the duplicate `### Changed` heading.
- python/ferro_ta/__init__.py: also re-export the `FerroTaError` alias.
- docs/guides/dtw.md: soften "byte-for-byte" parity to within-tolerance.

Remove docs/adr/ at maintainer request and clean up the ADR links in the
SIMD and DTW guides. The ADR files remain in commit 9506a30 if ever needed.
2026-06-29 18:21:22 +05:30

675 lines
19 KiB
Python

"""
ferro_ta — A fast Technical Analysis library powered by Rust and PyO3.
Drop-in alternative to TA-Lib with pre-compiled wheels for all platforms.
Indicators are organized into sub-modules matching TA-Lib's category structure,
and are also importable directly from this top-level package for convenience.
Sub-packages
------------
* :mod:`ferro_ta.indicators` — All indicator functions (overlap, momentum, volume, volatility, statistic, cycle, pattern, price_transform, math_ops, extended)
* :mod:`ferro_ta.core` — Core utilities (exceptions, config, logging, registry, raw)
* :mod:`ferro_ta.data` — Data utilities (streaming, batch, chunked, resampling, aggregation, adapters)
* :mod:`ferro_ta.analysis` — Analysis tools (portfolio, backtest, regime, cross_asset, attribution, signals, features, crypto, options, futures, derivatives payoff)
* :mod:`ferro_ta.tools` — Developer tools (tools, viz, dashboard, alerts, dsl, pipeline, workflow, api_info, gpu)
Sub-modules (also accessible via sub-packages above)
-----------------------------------------------------
* :mod:`ferro_ta.indicators.overlap` — Overlap Studies (SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, T3, MACD, BBANDS, SAR, MA, MAVP, MAMA, SAREXT, MACDEXT, …)
* :mod:`ferro_ta.indicators.momentum` — Momentum Indicators (RSI, STOCH, ADX, CCI, WILLR, AROON, MFI, …)
* :mod:`ferro_ta.indicators.volume` — Volume Indicators (AD, ADOSC, OBV)
* :mod:`ferro_ta.indicators.volatility` — Volatility Indicators (ATR, NATR, TRANGE)
* :mod:`ferro_ta.indicators.statistic` — Statistic Functions (STDDEV, VAR, LINEARREG, BETA, CORREL, …)
* :mod:`ferro_ta.indicators.price_transform` — Price Transformations (AVGPRICE, MEDPRICE, TYPPRICE, WCLPRICE)
* :mod:`ferro_ta.indicators.pattern` — Pattern Recognition (CDLDOJI, CDLENGULFING, CDLHAMMER, …)
* :mod:`ferro_ta.indicators.cycle` — Cycle Indicators (HT_TRENDLINE, HT_DCPERIOD, HT_DCPHASE, HT_PHASOR, HT_SINE, HT_TRENDMODE)
* :mod:`ferro_ta.indicators.math_ops` — Math Operators/Transforms (ADD, SUB, MULT, DIV, SUM, MAX, MIN, ACOS, SIN, …)
* :mod:`ferro_ta.indicators.extended` — Extended Indicators (VWAP, SUPERTREND, ICHIMOKU, DONCHIAN, PIVOT_POINTS, KELTNER_CHANNELS, HULL_MA, CHANDELIER_EXIT, VWMA, CHOPPINESS_INDEX)
* :mod:`ferro_ta.data.streaming` — Streaming / Incremental API (bar-by-bar stateful classes for live trading)
* :mod:`ferro_ta.data.batch` — Batch Execution API (run SMA/EMA/RSI on 2-D arrays of multiple series)
* :mod:`ferro_ta.data.resampling` — OHLCV resampling and multi-timeframe API
* :mod:`ferro_ta.data.aggregation` — Tick/trade aggregation pipeline
* :mod:`ferro_ta.tools.dsl` — Strategy expression DSL
* :mod:`ferro_ta.analysis.signals` — Signal composition and screening
* :mod:`ferro_ta.analysis.portfolio` — Portfolio and multi-asset analytics
* :mod:`ferro_ta.analysis.cross_asset` — Cross-asset and relative strength
* :mod:`ferro_ta.analysis.features` — Feature matrix and ML readiness
* :mod:`ferro_ta.analysis.options` — Options pricing, Greeks, IV, smile, and chain analytics
* :mod:`ferro_ta.analysis.futures` — Futures basis, carry, roll, and curve analytics
* :mod:`ferro_ta.tools.viz` — Charting and visualisation API
* :mod:`ferro_ta.data.adapters` — Market data adapters
Usage
-----
>>> import numpy as np
>>> from ferro_ta import SMA, EMA, RSI, MACD, BBANDS
>>> close = np.array([10.0, 11.0, 12.0, 13.0, 14.0, 13.5, 12.5])
>>> SMA(close, timeperiod=3)
array([ nan, nan, 11. , 12. , 13. , 13.5, 13.33...])
>>> # Or import from sub-packages:
>>> from ferro_ta.indicators.overlap import SMA, BBANDS
>>> from ferro_ta.indicators.momentum import RSI, ADX
>>> from ferro_ta.indicators.volatility import ATR
>>> from ferro_ta.indicators.cycle import HT_TRENDLINE, HT_DCPERIOD
>>> # Backward-compat flat imports still work:
>>> from ferro_ta.overlap import SMA # noqa: F401 (stub)
"""
from __future__ import annotations
import re as _re
import sys as _sys
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
from importlib.metadata import version as _dist_version
from pathlib import Path as _Path
try:
import tomllib as _tomllib
except ImportError: # pragma: no cover
try:
import tomli as _tomllib # type: ignore[no-redef]
except ImportError: # pragma: no cover
_tomllib = None # type: ignore[assignment]
def _detect_version() -> str:
try:
return _dist_version("ferro-ta")
except _PackageNotFoundError:
pass
if _tomllib is not None:
pyproject_toml = _Path(__file__).resolve().parents[2] / "pyproject.toml"
if pyproject_toml.is_file():
try:
with pyproject_toml.open("rb") as handle:
data = _tomllib.load(handle)
return data.get("project", {}).get("version", "0+unknown")
except Exception:
pass
pyproject_toml = _Path(__file__).resolve().parents[2] / "pyproject.toml"
if pyproject_toml.is_file():
try:
text = pyproject_toml.read_text(encoding="utf-8")
match = _re.search(r'^version\s*=\s*"([^"]+)"', text, _re.MULTILINE)
if match:
return match.group(1)
except Exception:
pass
return "0+unknown"
__version__ = _detect_version()
# ---------------------------------------------------------------------------
# Exceptions — exported at the top level for convenient catching
# ---------------------------------------------------------------------------
from ferro_ta.core.exceptions import ( # noqa: F401
FerroTAError,
FerroTaError,
FerroTAInputError,
FerroTAValueError,
InsufficientDataError,
InvalidInputError,
InvalidPeriodError,
LengthMismatchError,
NumericConvergenceError,
)
# ---------------------------------------------------------------------------
# Cycle Indicators
# ---------------------------------------------------------------------------
from ferro_ta.indicators.cycle import ( # noqa: F401
HT_DCPERIOD,
HT_DCPHASE,
HT_PHASOR,
HT_SINE,
HT_TRENDLINE,
HT_TRENDMODE,
)
# ---------------------------------------------------------------------------
# Math Operators & Math Transforms
# ---------------------------------------------------------------------------
from ferro_ta.indicators.math_ops import ( # noqa: F401
ACOS,
ADD,
ASIN,
ATAN,
CEIL,
COS,
COSH,
DIV,
EXP,
FLOOR,
LN,
LOG10,
MAX,
MAXINDEX,
MIN,
MININDEX,
MULT,
SIN,
SINH,
SQRT,
SUB,
SUM,
TAN,
TANH,
)
# ---------------------------------------------------------------------------
# Momentum Indicators
# ---------------------------------------------------------------------------
from ferro_ta.indicators.momentum import ( # noqa: F401
ADX,
ADXR,
APO,
AROON,
AROONOSC,
BOP,
CCI,
CMO,
DX,
MFI,
MINUS_DI,
MINUS_DM,
MOM,
PLUS_DI,
PLUS_DM,
PPO,
ROC,
ROCP,
ROCR,
ROCR100,
RSI,
STOCH,
STOCHF,
STOCHRSI,
TRANGE,
TRIX,
ULTOSC,
WILLR,
)
# ---------------------------------------------------------------------------
# Overlap Studies
# ---------------------------------------------------------------------------
from ferro_ta.indicators.overlap import ( # noqa: F401
BBANDS,
DEMA,
EMA,
KAMA,
MA,
MACD,
MACDEXT,
MACDFIX,
MAMA,
MAVP,
MIDPOINT,
MIDPRICE,
SAR,
SAREXT,
SMA,
T3,
TEMA,
TRIMA,
WMA,
)
# ---------------------------------------------------------------------------
# Pattern Recognition
# ---------------------------------------------------------------------------
from ferro_ta.indicators.pattern import ( # noqa: F401
CDL2CROWS,
CDL3BLACKCROWS,
CDL3INSIDE,
CDL3LINESTRIKE,
CDL3OUTSIDE,
CDL3STARSINSOUTH,
CDL3WHITESOLDIERS,
CDLABANDONEDBABY,
CDLADVANCEBLOCK,
CDLBELTHOLD,
CDLBREAKAWAY,
CDLCLOSINGMARUBOZU,
CDLCONCEALBABYSWALL,
CDLCOUNTERATTACK,
CDLDARKCLOUDCOVER,
CDLDOJI,
CDLDOJISTAR,
CDLDRAGONFLYDOJI,
CDLENGULFING,
CDLEVENINGDOJISTAR,
CDLEVENINGSTAR,
CDLGAPSIDESIDEWHITE,
CDLGRAVESTONEDOJI,
CDLHAMMER,
CDLHANGINGMAN,
CDLHARAMI,
CDLHARAMICROSS,
CDLHIGHWAVE,
CDLHIKKAKE,
CDLHIKKAKEMOD,
CDLHOMINGPIGEON,
CDLIDENTICAL3CROWS,
CDLINNECK,
CDLINVERTEDHAMMER,
CDLKICKING,
CDLKICKINGBYLENGTH,
CDLLADDERBOTTOM,
CDLLONGLEGGEDDOJI,
CDLLONGLINE,
CDLMARUBOZU,
CDLMATCHINGLOW,
CDLMATHOLD,
CDLMORNINGDOJISTAR,
CDLMORNINGSTAR,
CDLONNECK,
CDLPIERCING,
CDLRICKSHAWMAN,
CDLRISEFALL3METHODS,
CDLSEPARATINGLINES,
CDLSHOOTINGSTAR,
CDLSHORTLINE,
CDLSPINNINGTOP,
CDLSTALLEDPATTERN,
CDLSTICKSANDWICH,
CDLTAKURI,
CDLTASUKIGAP,
CDLTHRUSTING,
CDLTRISTAR,
CDLUNIQUE3RIVER,
CDLUPSIDEGAP2CROWS,
CDLXSIDEGAP3METHODS,
)
# ---------------------------------------------------------------------------
# Price Transformations
# ---------------------------------------------------------------------------
from ferro_ta.indicators.price_transform import ( # noqa: F401
AVGPRICE,
MEDPRICE,
TYPPRICE,
WCLPRICE,
)
# ---------------------------------------------------------------------------
# Statistic Functions
# ---------------------------------------------------------------------------
from ferro_ta.indicators.statistic import ( # noqa: F401
BETA,
CORREL,
LINEARREG,
LINEARREG_ANGLE,
LINEARREG_INTERCEPT,
LINEARREG_SLOPE,
STDDEV,
TSF,
VAR,
)
# ---------------------------------------------------------------------------
# Volatility Indicators
# ---------------------------------------------------------------------------
from ferro_ta.indicators.volatility import ( # noqa: F401
ATR,
NATR,
)
# ---------------------------------------------------------------------------
# Volume Indicators
# ---------------------------------------------------------------------------
from ferro_ta.indicators.volume import ( # noqa: F401
AD,
ADOSC,
OBV,
)
__all__ = [
"__version__",
# Overlap Studies
"SMA",
"EMA",
"WMA",
"DEMA",
"TEMA",
"TRIMA",
"KAMA",
"T3",
"BBANDS",
"MACD",
"MACDFIX",
"MACDEXT",
"SAR",
"SAREXT",
"MA",
"MAVP",
"MAMA",
"MIDPOINT",
"MIDPRICE",
# Momentum
"RSI",
"MOM",
"ROC",
"ROCP",
"ROCR",
"ROCR100",
"WILLR",
"AROON",
"AROONOSC",
"CCI",
"MFI",
"BOP",
"STOCHF",
"STOCH",
"STOCHRSI",
"APO",
"PPO",
"CMO",
"PLUS_DM",
"MINUS_DM",
"PLUS_DI",
"MINUS_DI",
"DX",
"ADX",
"ADXR",
"TRIX",
"ULTOSC",
"TRANGE",
# Volume
"AD",
"ADOSC",
"OBV",
# Volatility
"ATR",
"NATR",
# Statistics
"STDDEV",
"VAR",
"LINEARREG",
"LINEARREG_SLOPE",
"LINEARREG_INTERCEPT",
"LINEARREG_ANGLE",
"TSF",
"BETA",
"CORREL",
# Price transforms
"AVGPRICE",
"MEDPRICE",
"TYPPRICE",
"WCLPRICE",
# Patterns
"CDL2CROWS",
"CDL3BLACKCROWS",
"CDL3INSIDE",
"CDL3LINESTRIKE",
"CDL3OUTSIDE",
"CDL3STARSINSOUTH",
"CDL3WHITESOLDIERS",
"CDLABANDONEDBABY",
"CDLADVANCEBLOCK",
"CDLBELTHOLD",
"CDLBREAKAWAY",
"CDLCLOSINGMARUBOZU",
"CDLCONCEALBABYSWALL",
"CDLCOUNTERATTACK",
"CDLDARKCLOUDCOVER",
"CDLDOJI",
"CDLDOJISTAR",
"CDLDRAGONFLYDOJI",
"CDLENGULFING",
"CDLEVENINGDOJISTAR",
"CDLEVENINGSTAR",
"CDLGAPSIDESIDEWHITE",
"CDLGRAVESTONEDOJI",
"CDLHAMMER",
"CDLHANGINGMAN",
"CDLHARAMI",
"CDLHARAMICROSS",
"CDLHIGHWAVE",
"CDLHIKKAKE",
"CDLHIKKAKEMOD",
"CDLHOMINGPIGEON",
"CDLIDENTICAL3CROWS",
"CDLINNECK",
"CDLINVERTEDHAMMER",
"CDLKICKING",
"CDLKICKINGBYLENGTH",
"CDLLADDERBOTTOM",
"CDLLONGLEGGEDDOJI",
"CDLLONGLINE",
"CDLMARUBOZU",
"CDLMATCHINGLOW",
"CDLMATHOLD",
"CDLMORNINGDOJISTAR",
"CDLMORNINGSTAR",
"CDLONNECK",
"CDLPIERCING",
"CDLRICKSHAWMAN",
"CDLRISEFALL3METHODS",
"CDLSEPARATINGLINES",
"CDLSHOOTINGSTAR",
"CDLSHORTLINE",
"CDLSPINNINGTOP",
"CDLSTALLEDPATTERN",
"CDLSTICKSANDWICH",
"CDLTAKURI",
"CDLTASUKIGAP",
"CDLTHRUSTING",
"CDLTRISTAR",
"CDLUNIQUE3RIVER",
"CDLUPSIDEGAP2CROWS",
"CDLXSIDEGAP3METHODS",
# Cycle
"HT_TRENDLINE",
"HT_DCPERIOD",
"HT_DCPHASE",
"HT_PHASOR",
"HT_SINE",
"HT_TRENDMODE",
# Math Operators
"ADD",
"SUB",
"MULT",
"DIV",
"SUM",
"MAX",
"MIN",
"MAXINDEX",
"MININDEX",
# Math Transforms
"ACOS",
"ASIN",
"ATAN",
"CEIL",
"COS",
"COSH",
"EXP",
"FLOOR",
"LN",
"LOG10",
"SIN",
"SINH",
"SQRT",
"TAN",
"TANH",
# Extended Indicators
"VWAP",
"SUPERTREND",
"ICHIMOKU",
"DONCHIAN",
"PIVOT_POINTS",
"KELTNER_CHANNELS",
"HULL_MA",
"CHANDELIER_EXIT",
"VWMA",
"CHOPPINESS_INDEX",
# API discovery
"about",
"indicators",
"methods",
"info",
# Logging utilities
"enable_debug",
"disable_debug",
"debug_mode",
"get_logger",
"log_call",
"benchmark",
"traced",
]
# ---------------------------------------------------------------------------
# Extended Indicators
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Pandas API — apply transparent pandas.Series / DataFrame support to every
# public indicator function exported from this module.
# ---------------------------------------------------------------------------
from ferro_ta._utils import pandas_wrap as _pandas_wrap # noqa: E402
from ferro_ta._utils import polars_wrap as _polars_wrap # noqa: E402
from ferro_ta.analysis.attribution import ( # noqa: F401, E402
TradeStats,
attribution_by_month,
attribution_by_signal,
from_backtest,
trade_stats,
)
from ferro_ta.analysis.crypto import ( # noqa: F401, E402
continuous_bar_labels,
funding_pnl,
resample_continuous,
session_boundaries,
)
from ferro_ta.analysis.regime import ( # noqa: F401, E402
detect_breaks_cusum,
regime,
regime_adx,
regime_combined,
rolling_variance_break,
structural_breaks,
)
from ferro_ta.core import exceptions as exceptions # noqa: F401, E402
# ---------------------------------------------------------------------------
# Logging utilities — ferro_ta.enable_debug() / ferro_ta.benchmark()
# ---------------------------------------------------------------------------
from ferro_ta.core.logging_utils import ( # noqa: F401, E402
benchmark,
debug_mode,
disable_debug,
enable_debug,
get_logger,
log_call,
traced,
)
from ferro_ta.data import batch as batch # noqa: F401, E402
from ferro_ta.data import streaming as streaming # noqa: F401, E402
# ---------------------------------------------------------------------------
# Batch API (not in __all__ — use directly from ferro_ta.batch)
# Import: from ferro_ta.batch import batch_sma, batch_ema, batch_rsi
# ---------------------------------------------------------------------------
from ferro_ta.data.batch import ( # noqa: F401, E402
batch_apply,
batch_ema,
batch_rsi,
batch_sma,
compute_many,
)
from ferro_ta.data.chunked import ( # noqa: F401, E402
chunk_apply,
make_chunk_ranges,
stitch_chunks,
trim_overlap,
)
# ---------------------------------------------------------------------------
# Streaming / Incremental API (not in __all__ — these are classes, not funcs)
# Import directly: from ferro_ta.streaming import StreamingSMA, ...
# ---------------------------------------------------------------------------
from ferro_ta.data.streaming import ( # noqa: F401, E402 # type: ignore[assignment]
StreamingATR, # type: ignore[attr-defined]
StreamingBBands, # type: ignore[attr-defined]
StreamingEMA, # type: ignore[attr-defined]
StreamingMACD, # type: ignore[attr-defined]
StreamingRSI, # type: ignore[attr-defined]
StreamingSMA, # type: ignore[attr-defined]
StreamingStoch, # type: ignore[attr-defined]
StreamingSupertrend, # type: ignore[attr-defined]
StreamingVWAP, # type: ignore[attr-defined]
)
from ferro_ta.indicators import cycle as cycle # noqa: F401, E402
from ferro_ta.indicators import extended as extended # noqa: F401, E402
from ferro_ta.indicators import math_ops as math_ops # noqa: F401, E402
from ferro_ta.indicators import momentum as momentum # noqa: F401, E402
from ferro_ta.indicators import overlap as overlap # noqa: F401, E402
from ferro_ta.indicators import pattern as pattern # noqa: F401, E402
from ferro_ta.indicators import price_transform as price_transform # noqa: F401, E402
from ferro_ta.indicators import statistic as statistic # noqa: F401, E402
from ferro_ta.indicators import volatility as volatility # noqa: F401, E402
from ferro_ta.indicators import volume as volume # noqa: F401, E402
from ferro_ta.indicators.extended import ( # noqa: F401, E402
CHANDELIER_EXIT,
CHOPPINESS_INDEX,
DONCHIAN,
HULL_MA,
ICHIMOKU,
KELTNER_CHANNELS,
PIVOT_POINTS,
SUPERTREND,
VWAP,
VWMA,
)
# ---------------------------------------------------------------------------
# Additional modules (not in __all__ — access via submodule)
# ---------------------------------------------------------------------------
from ferro_ta.tools.alerts import ( # noqa: F401, E402
AlertManager,
check_cross,
check_threshold,
collect_alert_bars,
)
# ---------------------------------------------------------------------------
# API discovery helpers — ferro_ta.about(), ferro_ta.methods(),
# ferro_ta.indicators(), and ferro_ta.info()
# ---------------------------------------------------------------------------
from ferro_ta.tools.api_info import about, indicators, info, methods # noqa: F401, E402
_ALIASED_SUBMODULES = {
"batch": batch,
"cycle": cycle,
"exceptions": exceptions,
"extended": extended,
"math_ops": math_ops,
"momentum": momentum,
"overlap": overlap,
"pattern": pattern,
"price_transform": price_transform,
"statistic": statistic,
"streaming": streaming,
"volatility": volatility,
"volume": volume,
}
for _module_name, _module in _ALIASED_SUBMODULES.items():
setattr(_sys.modules[__name__], _module_name, _module)
_sys.modules[f"{__name__}.{_module_name}"] = _module
_g = globals()
for _name in __all__:
_fn = _g.get(_name)
if callable(_fn) and not getattr(_fn, "_pandas_wrapped", False):
_g[_name] = _pandas_wrap(_fn)
_fn = _g.get(_name)
if callable(_fn) and not getattr(_fn, "_polars_wrapped", False):
_g[_name] = _polars_wrap(_fn)
del _ALIASED_SUBMODULES, _g, _module, _module_name, _name, _fn, _sys