Time translation & holiday tracking for backtesting pipelines
Project description
quando
Navigate trading calendars, business days, and market periods — with zero fuss.
quando is a lightweight library for business day arithmetic and market calendar utilities. It auto-detects any date input, resolves holidays via exchange-calendars, and provides 52 methods covering everything from date conversion to settlement dates and rebalancing triggers.
Features
- 52 methods — conversion, timezone, validation, day checks, navigation, ranges, period boundaries, settlement, and expiry
- Auto-detecting inputs — string (ISO, compact, US, slash),
datetime,date,int/float(Unix epoch or Excel West serial) - Exchange calendars — NYSE, EUREX, LSE, TSX, ASX, HKEX, JPX, CME via
exchange-calendars; any exchange code works - Custom holidays — add, remove, and persist overrides to JSON; merged transparently at query time
- Period boundaries — month, quarter, year, and ISO week; always snaps to the nearest business day
- Rebalancing triggers —
is_month_end(),is_quarter_end(),is_year_end()for signal and rebalance logic - Settlement & expiry —
T+Nconventions, COB roll-back, and monthly/quarterly/weekly expiry dates - Pluggable calendars — load a custom JSON calendar file and set it as the active default with one call
Installation
pip install quando
Or with uv:
uv add quando
For development:
git clone https://github.com/aexsalomao/quando
cd quando
uv sync --extra dev
Quick Start
import quando as q
q.use("NYSE") # set global calendar (default if omitted)
# Parse anything
q.to_datetime("20240115") # datetime(2024, 1, 15, tzinfo=UTC)
q.to_datetime(45306) # Excel West serial → 2024-01-15
q.to_datetime(1705276800.0) # Unix epoch
# Day checks
q.is_business_day("2024-01-15") # False — MLK Day
q.is_holiday("2024-01-15") # True
q.is_weekend("2024-01-13") # True
# Navigation
q.next_business_day("2024-12-24") # Dec 26 (Christmas skip)
q.add_business_days("2024-01-10", 5) # Jan 17
q.snap("2024-01-13", "forward") # Jan 15... wait, holiday → Jan 16
# Period boundaries
q.start_of_month("2024-09-15") # Sep 3 (Sep 1 is Sunday, Sep 2 is Labor Day)
q.end_of_quarter("2024-01-15") # Mar 28 (Mar 31 is Sunday, Mar 29 Good Friday)
q.end_of_year("2024-06-01") # Dec 31
# Rebalancing triggers
if q.is_month_end("2024-01-31"):
print("rebalance!") # prints — Jan 31 is last NYSE trading day
# Date ranges & counting
q.date_range("2024-01-01", "2024-01-31") # list of 21 trading days
q.trading_days_in_year(2024) # 252
q.business_days_between("2024-01-01", "2024-01-31") # 21
# Settlement & expiry
q.to_settlement_date("2024-06-10", "T+2") # Jun 12
q.next_expiry("2024-01-01", "monthly") # Jan 19 (third Friday)
q.days_to_expiry("2024-01-10", "2024-01-19") # 6 business days
API
Setup
| Call | Returns | Notes |
|---|---|---|
q.use(cal) |
None |
Set global calendar: "NYSE", "LSE", "EUREX", … |
q.verbose(flag) |
None |
Toggle internal debug logging |
q.as_of(value) |
None |
Set reference date for expiry/settlement calculations |
Conversions
| Call | Returns |
|---|---|
q.to_timestamp(value) |
float — Unix epoch |
q.to_datetime(value) |
datetime — UTC-aware |
q.to_west(value) |
int — Excel serial (days since 1899-12-30) |
q.to_iso(value) |
str — ISO 8601 (YYYY-MM-DDTHH:MM:SS+00:00) |
q.convert(value, to_fmt) |
any — to_fmt ∈ "timestamp", "datetime", "west", "iso" |
q.to_timestamps(values) |
list[float] |
q.to_datetimes(values) |
list[datetime] |
Timezone
| Call | Returns |
|---|---|
q.shift_tz(value, tz) |
datetime — clock converted to target timezone |
q.localize(value, tz) |
datetime — timezone attached, clock unchanged |
q.strip_tz(value) |
datetime — naive, expressed in UTC |
Validation & Comparison
| Call | Returns |
|---|---|
q.is_valid(value) |
bool — True if quando can parse the input |
q.is_same_day(a, b) |
bool |
q.is_before(a, b) |
bool — True if a < b |
q.is_after(a, b) |
bool — True if a > b |
Day Checks
| Call | Returns |
|---|---|
q.is_weekend(value) |
bool — no calendar needed |
q.is_holiday(value, cal=None) |
bool — weekday that is a market holiday |
q.is_business_day(value, cal=None) |
bool — not weekend and not holiday |
q.is_expiry_day(value) |
bool — third Friday of the month |
q.day_of_week(value) |
str — e.g. "Monday" |
Holiday Management
| Call | Returns |
|---|---|
q.list_holidays(year, cal=None) |
list[tuple[date, str]] — sorted (date, name) pairs |
q.add_holiday(value, name, cal=None) |
None — add a custom holiday override |
q.remove_holiday(value, cal=None) |
None — remove a holiday from the active calendar |
Date Navigation
| Call | Returns |
|---|---|
q.next_business_day(value, cal=None) |
datetime |
q.prev_business_day(value, cal=None) |
datetime |
q.add_business_days(value, n, cal=None) |
datetime — negative n goes backward |
q.snap(value, direction, cal=None) |
datetime — direction ∈ "forward", "backward", "nearest" |
Date Ranges & Counting
| Call | Returns |
|---|---|
q.business_days_between(start, end, cal=None) |
int — exclusive of both endpoints |
q.date_range(start, end, cal=None) |
list[datetime] — all business days, inclusive |
q.trading_days_in_month(value, cal=None) |
int |
q.trading_days_in_year(year, cal=None) |
int — annualization factor (typically 252) |
q.filter_business_days(values, cal=None) |
list — only the business days from input |
Period Boundaries
All period functions return a UTC-aware datetime that lands on a business day.
| Call | Returns |
|---|---|
q.start_of_month(value, cal=None) |
datetime — first business day of month |
q.end_of_month(value, cal=None) |
datetime — last business day of month |
q.start_of_quarter(value, cal=None) |
datetime — first business day of quarter |
q.end_of_quarter(value, cal=None) |
datetime — last business day of quarter |
q.start_of_year(value, cal=None) |
datetime — first business day of year |
q.end_of_year(value, cal=None) |
datetime — last business day of year |
q.start_of_week(value, cal=None) |
datetime — first business day of ISO week |
q.end_of_week(value, cal=None) |
datetime — last business day of ISO week |
Period Boundary Checks
Useful as rebalancing triggers in a backtest loop.
| Call | Returns |
|---|---|
q.is_month_end(value, cal=None) |
bool — True if value is the last business day of its month |
q.is_quarter_end(value, cal=None) |
bool — True if value is the last business day of its quarter |
q.is_year_end(value, cal=None) |
bool — True if value is the last business day of its year |
Settlement & Expiry
| Call | Returns |
|---|---|
q.to_settlement_date(value, convention, cal=None) |
datetime — e.g. convention="T+2" adds 2 business days |
q.to_cob(value, cal=None) |
datetime — close-of-business; rolls back if non-business day |
q.next_expiry(value, contract_type) |
datetime — contract_type ∈ "monthly", "quarterly", "weekly" |
q.days_to_expiry(value, expiry, cal=None) |
int — business days to expiry |
Reference & I/O
| Call | Returns |
|---|---|
q.today(cal=None) |
datetime — today, tz-aware in the exchange's local timezone |
q.load_calendar(path) |
None — load JSON calendar file and set as active |
q.save_calendar(path, cal=None) |
None — export custom holiday additions to JSON |
Supported Calendars
| Alias | Exchange | Timezone |
|---|---|---|
NYSE |
New York Stock Exchange | America/New_York |
EUREX |
Eurex | Europe/Berlin |
LSE |
London Stock Exchange | Europe/London |
TSX |
Toronto Stock Exchange | America/Toronto |
ASX |
Australian Securities Exchange | Australia/Sydney |
HKEX |
Hong Kong Exchanges | Asia/Hong_Kong |
JPX |
Japan Exchange Group | Asia/Tokyo |
CME |
Chicago Mercantile Exchange | America/Chicago |
Any exchange-calendars exchange code (e.g. "XLON", "XEUR") also works directly.
Custom Calendars
import quando as q
# Add a one-off closure
q.use("NYSE")
q.add_holiday("2024-07-05", "Independence Day (observed)")
# Persist to JSON
q.save_calendar("my_calendar.json")
# Load it back in another session
q.load_calendar("my_calendar.json") # also calls q.use() with the saved calendar name
JSON schema:
{
"name": "NYSE",
"holidays": [
{"date": "2024-07-05", "name": "Independence Day (observed)"}
]
}
Running Tests
pytest
pytest --cov=quando # with coverage
License
MIT © Antônio Salomão
Project details
Release history Release notifications | RSS feed
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 quando-0.2.1.tar.gz.
File metadata
- Download URL: quando-0.2.1.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e816fa2b83ec9acc59b06f44f0050e1821c2a84ced64d38d70bc0751a7d77efb
|
|
| MD5 |
a054599bac7e42f49f3a6e5528a9a8cd
|
|
| BLAKE2b-256 |
8cf25ef1e792378c1fa3e75efe3bb45098ada80384f2c0ab06fb41da24070c1f
|
Provenance
The following attestation bundles were made for quando-0.2.1.tar.gz:
Publisher:
publish.yml on aexsalomao/quando
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quando-0.2.1.tar.gz -
Subject digest:
e816fa2b83ec9acc59b06f44f0050e1821c2a84ced64d38d70bc0751a7d77efb - Sigstore transparency entry: 1328049421
- Sigstore integration time:
-
Permalink:
aexsalomao/quando@f2884df3d15a65bd3353b6101c07aae74d53f996 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/aexsalomao
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f2884df3d15a65bd3353b6101c07aae74d53f996 -
Trigger Event:
release
-
Statement type:
File details
Details for the file quando-0.2.1-py3-none-any.whl.
File metadata
- Download URL: quando-0.2.1-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c5fb741c73123e6a0d626aed7c67c801e98f0b485852eb365967b175ba0b1b0
|
|
| MD5 |
aedfb3f0e3cb98ed1fde2a9d81bc560c
|
|
| BLAKE2b-256 |
6d17ab2f8bb304e94a450087dd21b68eb75473fa59e7f5a2f00e8b937e2962d4
|
Provenance
The following attestation bundles were made for quando-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on aexsalomao/quando
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quando-0.2.1-py3-none-any.whl -
Subject digest:
8c5fb741c73123e6a0d626aed7c67c801e98f0b485852eb365967b175ba0b1b0 - Sigstore transparency entry: 1328049425
- Sigstore integration time:
-
Permalink:
aexsalomao/quando@f2884df3d15a65bd3353b6101c07aae74d53f996 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/aexsalomao
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f2884df3d15a65bd3353b6101c07aae74d53f996 -
Trigger Event:
release
-
Statement type: