upticks
Price action, honestly. A causality-first price-action research library for Python. Plain pandas in, plain pandas out.
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.
Status: alpha — the foundation, not the whole library
v0.1.5 is the bar engine. It loads and validates data, infers sessions, and resamples them correctly. That is genuinely useful on its own, and it is deliberately all that is here.
| shipping now | not here yet |
|---|---|
load / scan — ingest, aliasing, dtype coercion, timezone discipline |
indicators (moving averages, momentum, volatility, …) |
16 hygiene checks as a tidy report; repair() as a separate explicit call |
candlestick and chart patterns |
| gap-based session inference — no exchange calendar, anywhere | pivots and market structure |
the session-anchored resampler, 1min → YE |
the event-study and backtest engines |
| session parts: opening range, initial balance, closing range | plotting |
| corporate actions and the three adjustment modes |
If you install this expecting RSI, you will be disappointed. Indicators arrive in the next stage. What is here is the layer all of that has to be a pure function of, and it is the part that is usually wrong.
Why session anchoring
Resample a 09:15-opening equity session to hourly bars with pandas and you get this:
df.resample("1h").agg(AGG) # [45, 60, 60, 60, 60, 60, 30] ← a 45-minute first bar
The grid is anchored to midnight, so the session's first bucket is a stub and every
subsequent boundary is offset from the open. upticks anchors each bucket to the session's
own first bar:
up.resample(bars, "1h") # [60, 60, 60, 60, 60, 60, 15] ← anchored to 09:15
The difference compounds. A 75min or 125min frame under a global origin drifts to a
different time of day on every subsequent session; anchored per session it lands on the same
five (or three) boundaries every trading day. A special evening session — NSE's Muhurat
trading, 18:00–18:59 — becomes exactly one hourly bar instead of being shredded or dropped.
Both behaviours remain reachable: anchor="midnight" is the correct choice for a 24-hour
instrument, and it is the only way to obtain the naive grid. You cannot get it by accident.
Causality, concretely
Every resampled bar carries an 18-column bar contract beside the frame, and the column
that matters is avail_ts — the instant the bar became knowable:
bars.contract[["bar_open_ts", "bar_close_ts", "avail_ts", "is_complete", "is_forming"]]
bar_close_tsis the actual last constituent plus one interval, never the nominal period end. On a truncated or special session those differ, and the nominal answer is wrong.avail_tsis the only legal join key. Joining on a bar's label is what leaks, so labels are not offered as join keys anywhere.is_formingmarks a bar that can still change. By default the still-open final bucket is withheld entirely (forming="drop"), so nothing knowable-early reaches a backtest. A daily bar for a session the feed has not finished publishing is not handed to you as settled.- Resampling to a finer frequency is refused with a typed error. There is no public forward-fill-to-finer verb, because that is the leak.
- Resample first, compute second.
RSI(14)on hourly bars is a different quantity fromRSI(14)computed on minutes and aggregated; no public path produces the latter.
Tested as named properties, not asserted as design intent: volume conservation, extreme
preservation, 1min→1h→1D bitwise identical to 1min→1D, idempotence, no bucket spanning
two sessions, and avail_ts > bar_open_ts on every emitted bar.
On look-ahead itself the claim is bounded, deliberately: no detectable look-ahead under these tests — never provably none. This release ships the structural layers — schema assertions at construction, the availability contract, and the refusals above — and the causality harness that makes "these tests" an enumerable, named list arrives in the next stage. Until it does, read the claim as exactly what it says: these tests did not detect a leak, which is not the same as there being none.
Sessions without a calendar
There is no exchange-calendar dependency anywhere in this library, and there never will be. Sessions are inferred from the multiplicative structure of the data's own gaps; the holiday table is the complement of the observed trading days.
This handles, on real data:
- special sessions — an evening or afternoon session that matches no modal shape is kept as a first-class session, never merged into a neighbour
- half-days and truncated feeds — flagged
is_short, andis_provisionalwhen they sit at the data tail and the vendor may still revise them - lunch-break markets — a mid-session gap is absorbed as a break, not a boundary, so Tokyo's 11:30–12:30 does not split the day in two
- midnight-crossing sessions — a CME Globex 23:30 bar belongs to the next calendar day's trade date, and the session is one session
- trading halts — a 90-minute hole is a shape deviation, not a session split
- DST transitions — boundaries are computed in integer nanoseconds from the session open, so a spring-forward week does not shift them
A declared SessionShape bypasses inference entirely where the heuristic is unsafe.
Quick start
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 | 2024-08-07 → 2026-08-12
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
orb = up.opening_range(bars, 15) # one bar per session, first 15 minutes
Nothing is repaired behind your back. load() reports; repair() is a separate call that
takes an explicit policy and records it in Meta.
Design commitments
Three runtime dependencies: pandas, numpy, scipy. Nothing else, ever. pyarrow,
matplotlib and numba are optional extras, imported lazily inside the one function that
needs them, and their absence raises an error naming the extra.
bars.df is a plain DataFrame and never a subclass, so anything that consumes pandas can
consume it. Metadata that pandas drops — timezone, tick grid, session table, fingerprint,
adjustment lineage — lives on the handle instead.
Refusals name the missing data. Every error carries a remedy that says what to pass.
An ambiguous frequency alias is refused rather than guessed: 60m means sixty minutes in
MetaTrader and sixty month-ends in pandas, so upticks refuses it and names both.
Defaults are documented, not folklore. up.defaults_provenance() returns every numeric
default with its origin and citation.
Supported versions
Python 3.11–3.13. pandas 2.2 through 3.x, and the test suite is run under both majors — pandas 3 changed the default datetime resolution from nanoseconds to microseconds, which silently breaks naive integer-time arithmetic, so this is verified rather than assumed.
Honest limitations
- Alpha. The public surface of this stage is stable and tested, but later stages will add to it. Pin the version.
- Session inference is a heuristic. It is validated against every session of a
two-year 1-minute reference file and against synthetic fixtures for four other market
shapes, but a genuinely novel session structure may need a declared
SessionShape. - Corporate-action detection is candidate-only. Splits and bonuses are matched against a small set of rational overnight ratios. 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: rescaling pre-ex-date bars uses information from the ex-date. 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; supply one explicitly if you need it.
License
Apache-2.0. Copyright (c) Nashit Babber.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file upticks-0.1.5.tar.gz.
File metadata
- Download URL: upticks-0.1.5.tar.gz
- Upload date:
- Size: 531.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51661a27aaeb163f7cf824e01aa68384b55908782e6df9597e6ae58b56d40fe4
|
|
| MD5 |
a0c61d004000ca1e362d15b84f014107
|
|
| BLAKE2b-256 |
1a5f60b18f76b491f995338def9c4ac16323324446b13214f48b392e7ff87941
|
File details
Details for the file upticks-0.1.5-py3-none-any.whl.
File metadata
- Download URL: upticks-0.1.5-py3-none-any.whl
- Upload date:
- Size: 223.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5560a20769d646ccf93931a3387cad48ed8829d94769764b6f615d96aa6b423
|
|
| MD5 |
95efb90323f770048cd5c4797f4ff5e1
|
|
| BLAKE2b-256 |
0815dd1ee6865e5a0a4ca1e3855612425cfbf36acc40ef08bcc7d43824dbdf85
|