Metadata-Version: 2.4
Name: upticks
Version: 1.0.3
Summary: Price action, honestly — a causality-first price-action research library.
Author-email: Nashit Babber <nashit.babber@gmail.com>
License-Expression: Apache-2.0
Project-URL: Documentation, https://github.com/nashit8421/upticks-docs
Project-URL: Changelog, https://github.com/nashit8421/upticks-docs/blob/main/CHANGELOG.md
Project-URL: Causality, https://github.com/nashit8421/upticks-docs/blob/main/docs/CAUSALITY.md
Project-URL: Limitations, https://github.com/nashit8421/upticks-docs/blob/main/docs/LIMITATIONS.md
Keywords: price action,ohlcv,market structure,pandas,backtesting
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Typing :: Typed
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas<4,>=2.2
Requires-Dist: numpy<3,>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: fast
Requires-Dist: numba; extra == "fast"
Provides-Extra: plot
Requires-Dist: matplotlib; extra == "plot"
Provides-Extra: cache
Requires-Dist: pyarrow; extra == "cache"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: hypothesis; extra == "dev"
Requires-Dist: pytest-xdist; extra == "dev"
Dynamic: license-file

# upticks

**Price action, honestly.** A causality-first price-action research library for Python.
Plain pandas in, plain pandas out.

```bash
pip install upticks
```

Most technical-analysis libraries will happily hand you a number that could not have been known
at the time it is stamped. `upticks` is built so that the leak is structurally unavailable: every
bar carries the instant it became knowable, and that is the only key a join is allowed to use.

---

## The problem it exists to remove

Every bar carries two timestamps. The **index** is the label — where the bar sits on the clock.
**`avail_ts`** is the instant the bar became knowable. For an intraday bar those are close
together; for a daily bar built from a session they are hours apart, and that gap is where
look-ahead lives.

Joining a daily bar onto intraday data *by calendar date* hands the 09:15 bar a close that will
not exist until 15:30. On this library's own reference file that is **2,975 of 3,480 rows —
85.5 %**, the median gap being **6h15m**.

`up.align` joins backward on `avail_ts`, and there is no other join on the public surface. No
`'nearest'`, no `'forward'`, no label-keyed join anywhere.

---

## Quick start

```python
import upticks as up

bars = up.load("NIFTY_1min.csv", tz="Asia/Kolkata", exchange="NSE", preset="nse_intraday")

print(bars.report())
# 500 sessions | 8 short | 2 off-hours (Muhurat) | tick 0.05 | 186747 bars

bars.quality          # the 16 hygiene checks, one row each
bars.sessions.table   # one row per session, with its flags

hourly = up.resample(bars, "1h")
daily  = up.resample(bars, "1D")      # one bar per SESSION, never a midnight resample
weekly = up.resample(bars, "W-FRI")   # restamped to the last actual session of the week
```

Nothing is repaired behind your back. `load()` reports; `repair()` is a separate call that takes
an explicit policy and records it in `Meta`.

### Indicators are a registry, not a grab-bag

```python
ind = up.indicators(daily, ["ema_20", "rsi_14", "macd", "bbands"])
ind.columns
# ['ema', 'rsi', 'macd', 'macd_signal', 'macd_hist', 'bb_lower', 'bb_mid', 'bb_upper']

ind["rsi"].isna().sum() == up.lookback("rsi", length=14) == 14   # asserted in CI, per entry
up.unstable_period("rsi", length=14)   # 216 — bars until the recursion has converged

up.catalog(family="oscillator")        # what each is, what it needs, how it is verified
up.explain(daily, "rsi")               # measured lag, warm-up, repainting verdict, provenance
```

Column names are the registry key and carry no parameters, so a downstream join does not break
when a period changes. Two calls to the same indicator at different parameters are disambiguated
by a hash of those parameters, never by silent overwrite.

Every entry declares where its defaults came from. `up.defaults_provenance()` returns **2,142**
rows and will tell you that RSI's 14 is `literature` with Wilder 1978 chapter 6 behind it — and
that its `source="close"` is an `author_choice`, because an editorial threshold that claims a
source it does not name is worse than one that admits it is editorial.

### The causality half

```python
joined = up.align(bars, daily, columns=["close"])   # backward on avail_ts; no other key exists

report = up.check_causality(my_detector, bars)      # cut the history, recompute, compare
report.is_causal, report.confirmation_lag, report.n_repainting_bars

up.lint_report("my_package")                        # the AST lint, before anything ever runs

split = up.holdout(bars, frac=0.2)                  # the tail is locked, not merely separate
                                                    # re-splitting or widening raises HoldoutLocked
```

---

## How it is kept true

Verification is layered, because each layer catches a class the others miss.

| layer | what it catches |
|---|---|
| AST lint | banned constructs in source, before anything runs — syntactic, a cheap filter |
| `check_causality` | cuts history at many points, recomputes, compares — a measurement |
| planted-bug corpus | deliberately broken implementations that *must* be caught |
| reference twins | a loop-based reference implementation beside every vectorised kernel |
| golden numbers | measured values pinned per version, per interpreter, per pandas major |
| byte-gated docs | generated pages fail CI if they differ from what the code renders today |

## Tested on

Every cell is **run**, not declared — and each is cross-paired against the other pandas major, so
a corpus written under one is read back under the other.

| | pandas 2.2.3 | pandas 2.3.3 | pandas 3.0.5 |
|---|---|---|---|
| **Python 3.11** | 9,488 pass | 9,484 pass | 9,485 pass |
| **Python 3.12** | 9,488 pass | 9,484 pass | 9,485 pass |
| **Python 3.13** | 9,488 pass | 9,484 pass | 9,485 pass |

A declared range is not compatibility; running is. Both axes carry a guard: a Python or pandas
version inside the declared range that no cell has executed is a **test failure**, not a silence.
That guard exists because each axis has already shipped a version nobody ran — on one of them,
every interval in the library was 1000× too small.

## The surface

| | |
|---|---|
| exported names, frozen | **138** |
| registered algorithms | **442** |
| hygiene checks per load | **16** |
| defaults with provenance | **2,142** |
| runtime dependencies | **3** |

Three runtime dependencies and no more: `pandas>=2.2,<4`, `numpy>=1.24,<3`, `scipy>=1.10`.
`pyarrow`, `matplotlib` and `numba` are optional extras, imported inside the one function that
needs them; their absence raises `DependencyMissing` naming the extra rather than an
`ImportError` from four frames down.

```bash
pip install "upticks[plot]"      # matplotlib
pip install "upticks[parquet]"   # pyarrow
```

Every one of the 40 public verbs that takes a bars handle carries a `Causality:` paragraph, and
all 80 public functions carry a primary-source citation. Both are gated by tests, which is what
makes them a contract rather than a convention.

---

## Honest limitations

This section is not an afterthought. It is the part hardest to write and most worth reading.

- **The verification tiers are not what the plan projected.** The census is **0** entries at tier
  A, **437** at B, **5** at C. Tier A means a numeric table from the primary text committed as a
  fixture; none exists yet, so the layer that catches a wrong *reading* of a formula has not run.
- **Session inference is a heuristic.** Validated against every session of a two-year 1-minute
  file and synthetic fixtures for four other market shapes — a genuinely novel session structure
  may need a declared `SessionShape`.
- **Corporate-action detection is candidate-only.** An ex-dividend drop is observationally
  identical to an ordinary news gap without a dividend feed, and is reported as a candidate,
  never a fact.
- **Back-adjustment is non-causal by construction** and says so. It is available, registered as
  non-causal, and refused by default where causality matters.
- **No exchange calendar means no forward-looking holidays.** The data is the calendar, so a
  holiday after the last bar is unknowable.
- **`check_causality` is a measurement, not a proof.** It cuts history at a finite set of points.
  A leak that only fires at a cut point it did not choose is a leak it will not report.
  `is_causal` is allowed to be *undetermined*, with the reason named, rather than forced to a
  boolean it cannot support.
- **The AST lint is syntactic.** A banned operation reached through `getattr` or a third-party
  helper is invisible to it.
- **A BCa interval is bias-corrected, not calibrated.** Over 1,000 replications at nominal 0.90,
  on the variance of a lognormal sample at n=40 it covers **0.607** against the percentile
  interval's 0.551.
- **Value-equality leak scanning is not offered, deliberately.** On the reference file it produces
  **20,685** false positives on the 1D→1min join — 11.10 % of it. Leak tests here compare
  provenance, not values.
- **Every measured number comes from one instrument**, one exchange and one liquidity regime.
  A second reference file is the honest fix, and it is not done.

---

## Versioning

Semantic versioning, with one addition this library treats as load-bearing: every release that
moves a *number* — a default, a threshold, a published measurement — records the old value, the
new value and what moved on the reference file. **"0 rows moved" is still an entry**, because a
change with no measured effect is information, and its absence is what lets a real one hide.

A major bump is required for: a removed name; a changed column; **a changed default**; a changed
signature; a refusal becoming a value or a value becoming a refusal; a causality contract
weakening; and a narrowed support range.

Supported: Python 3.11–3.13, pandas 2.2 through 3.x, numpy 1.24–2.x, scipy 1.10 and later.

---

## Documentation

Full documentation is public at
**<https://github.com/nashit8421/upticks-docs>** — the verification layers, the complete
limitations page, the API and defaults references, the provenance table for all 442 algorithms,
and three executable walkthroughs.

- [Causality](https://github.com/nashit8421/upticks-docs/blob/main/docs/CAUSALITY.md) — the layers, each named with something it caught
- [Limitations](https://github.com/nashit8421/upticks-docs/blob/main/docs/LIMITATIONS.md) — what the verification does not cover
- [Versioning](https://github.com/nashit8421/upticks-docs/blob/main/docs/VERSIONING.md) — the freeze, and what counts as breaking
- [Changelog](https://github.com/nashit8421/upticks-docs/blob/main/CHANGELOG.md) — with a Numeric changes section per release

---

## License

Apache-2.0 · Nashit Babber
