Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repos:
- id: trailing-whitespace
repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.13.0
rev: v0.15.0
hooks:
# Run the linter.
- id: ruff-check
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.13
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ The first beta version of `market_prices` was released May 2022.

Whilst the test suite is pretty comprehensive, there will inevitably be bugs. Please do raise an [issue](https://github.com/maread99/market_prices/issues) with any that you come across. Even better, offer a PR! Contributions welcome.

The package is actively maintained to work with the latest versions of its dependencies, which include `numpy` and `pandas`. **Only limited consideration is given to backwards compatibility with earlier versions of dependencies** - no guarantees are offered in this regard. Of note, whilst the latest release works with pandas v3 and v2, support for pandas v1 was dropped some time ago.

Please use [discussions](https://github.com/maread99/market_prices/discussions) to make any suggestions and offer general feedback.

## Disclaimers
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ classifiers = [
]

dependencies = [
"exchange_calendars",
"exchange-calendars",
"numpy",
"pandas",
"tzdata",
Expand All @@ -64,6 +64,7 @@ dependencies = [
test = [
"attrs",
"hypothesis",
"pytz", # required as stored hdf data includes pytz objects
"pytest",
"pytest-mock",
"tables",
Expand Down
21 changes: 13 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv export --format requirements-txt --no-emit-project --no-dev --no-hashes -o requirements.txt
# uv export --format requirements-txt --no-emit-project --no-hashes --no-dev -o requirements.txt
beautifulsoup4==4.14.3
# via yahooquery
certifi==2026.1.4
Expand All @@ -14,7 +14,7 @@ colorama==0.4.6 ; sys_platform == 'win32'
# via tqdm
curl-cffi==0.14.0
# via yahooquery
exchange-calendars==4.12
exchange-calendars==4.13.1
# via market-prices
idna==3.11
# via requests
Expand All @@ -27,35 +27,40 @@ numpy==2.2.6 ; python_full_version < '3.11'
# exchange-calendars
# market-prices
# pandas
numpy==2.4.1 ; python_full_version >= '3.11'
numpy==2.4.2 ; python_full_version >= '3.11'
# via
# exchange-calendars
# market-prices
# pandas
pandas==2.3.3
pandas==2.3.3 ; python_full_version < '3.11'
# via
# exchange-calendars
# market-prices
# yahooquery
pycparser==2.23 ; implementation_name != 'PyPy'
pandas==3.0.0 ; python_full_version >= '3.11'
# via
# exchange-calendars
# market-prices
# yahooquery
pycparser==3.0 ; implementation_name != 'PyPy'
# via cffi
pyluach==2.3.0
# via exchange-calendars
python-dateutil==2.9.0.post0
# via pandas
pytz==2025.2
pytz==2025.2 ; python_full_version < '3.11'
# via pandas
requests==2.32.5
# via requests-futures
requests-futures==1.0.2
# via yahooquery
six==1.17.0
# via python-dateutil
soupsieve==2.8.1
soupsieve==2.8.3
# via beautifulsoup4
toolz==1.1.0
# via exchange-calendars
tqdm==4.67.1
tqdm==4.67.3
# via yahooquery
typing-extensions==4.15.0
# via beautifulsoup4
Expand Down
10 changes: 9 additions & 1 deletion src/market_prices/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def freq_format_error_msg() -> str:
return (
f'interval/frequency received as "{freq}", although must be defined'
" as one or more consecutive digits followed by one or more"
' consecutive letters. Valid examples: "30min", "3h", "1d", "3m".'
' consecutive letters. Valid examples: "30min", "3h", "1D", "3m".'
)

unit = genutils.remove_digits(freq)
Expand Down Expand Up @@ -375,6 +375,14 @@ def resample(
else:
agg_f = agg_funcs(data)

# convert days to hours as Day is not considered a Tick frequency since pandas 3.0
if isinstance(rule, str):
n, unit = extract_freq_parts(rule)
if unit.upper() == "D":
rule = f"{24 * n}h"
if isinstance(rule, pd.offsets.Day):
rule = f"{24 * rule.n}h"

resampler = resample_me.resample(rule, closed="left", label="left", origin=origin)
resampled = resampler.agg(agg_f)
resampled.columns = columns_
Expand Down
10 changes: 5 additions & 5 deletions src/market_prices/prices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3337,7 +3337,7 @@ def get( # noqa: C901, PLR0912
p.get('10T', weeks=2) last two calendar weeks at ten minute
intervals.
p.get(days=5) last five trading days at inferred interval.
p.get('1d', months=6) daily data for last six months.
p.get('1D', months=6) daily data for last six months.
p.get('1h', months=3, end='2021-03-17 15:00') three months at
hourly intervals to defined datetime (timezone of 'end'
assumed as most common timezone of all symbols).
Expand Down Expand Up @@ -3514,7 +3514,7 @@ def get( # noqa: C901, PLR0912
unit:
"min", "MIN", "T" or "t" for minutes
"h" or "H" for hours
"d" or "D' for days
"D' for days
'm' or "M" for months

Examples:
Expand All @@ -3529,7 +3529,7 @@ def get( # noqa: C901, PLR0912
timedelta(hours=3)

one day:
"1d", "1D"
"1D"
pd.Timedelta(1, "D"), pd.Timedelta(days=1)
timedelta(days=1), timedelta(1)

Expand All @@ -3538,8 +3538,8 @@ def get( # noqa: C901, PLR0912

others:
"90min", "90MIN" - ninety minutes
"5D", "5d" - five days
"40D", "40d" - forty days
"5D" - five days
"40D" - forty days
"1M", "1m" - one month
pd.Timedelta(hours=3, minutes=30) - three and a half
hours
Expand Down
11 changes: 6 additions & 5 deletions src/market_prices/prices/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@

CSV_READ_DFLT_KWARGS: dict[str, Any] = {
"header": 0,
"usecols": lambda x: x.lower()
in ["date", "open", "high", "low", "close", "volume"],
"usecols": lambda x: (
x.lower() in ["date", "open", "high", "low", "close", "volume"]
),
"index_col": "date",
"parse_dates": ["date"],
}
Expand Down Expand Up @@ -118,7 +119,7 @@ def _get_symbol_from_filename(name: str, symbols: list[str]) -> str | None:
# at least two different symbols in filename
return None
symbol = part
return symbol if symbol else None
return symbol or None


def _get_interval_from_filename(name: str) -> TDInterval | None:
Expand Down Expand Up @@ -1095,7 +1096,7 @@ def __init__(
root = path
self.PM_SUBSESSION_ORIGIN = pm_subsession_origin # override class attr
symbols_ = helpers.symbols_to_list(symbols)
delays = {symb: pd.Timedelta(0) for symb in symbols_}
delays = dict.fromkeys(symbols_, 0)

csv_kwargs = _get_csv_read_kwargs(read_csv_kwargs)
paths = self._csv_paths = get_csv_paths(root, symbols_)
Expand Down Expand Up @@ -1167,7 +1168,7 @@ def _compile_tables(
dfs.append(df_symb_)
if not dfs:
continue
df = pd.concat(dfs, axis=1)
df = pd.concat(dfs, axis=1, sort=True)
df = df.sort_index()
df.columns = df.columns.set_names("symbol", level=0)
dfs_by_intrvl[interval] = df
Expand Down
2 changes: 1 addition & 1 deletion src/market_prices/prices/yahoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ def _tidy_yahoo( # noqa: C901, PLR0912
# for, at least, hong kong stocks include a single index after the close.
params = dict(interval=interval, start=start, end=end)
raise errors.PricesUnavailableFromSourceError(params, df)
df = pd.concat(sdfs, axis=1)
df = pd.concat(sdfs, axis=1, sort=True)

if empty_sdfs:
# add symbols for which prices unavailable over requested period
Expand Down
22 changes: 12 additions & 10 deletions src/market_prices/pt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ def _downsample_cbdays(
"\nNB. Downsampling will downsample to a frequency defined in"
" CustomBusinessDay when either `pdfreq` is passed as a"
" CustomBusinessDay (or multiple of) or when the table has a"
' CustomBusinessDay frequency and `pdfreq` is passed with unit "d".'
' CustomBusinessDay frequency and `pdfreq` is passed with unit "D".'
)

if not isinstance(calendar, xcals.ExchangeCalendar):
Expand All @@ -1261,7 +1261,7 @@ def _downsample_cbdays(

# Passing 'end' to the origin argument of DataFrame.resample has no
# effect when passing rule as a CustomBusinessDay.
origin = "start"
origin = "start_day"

# To ensure every indice, INCLUDING the last indice, comprises x
# CustomBusinessDays it's necessary to curtail the start of the dataframe
Expand All @@ -1286,8 +1286,8 @@ def _downsample_cbdays(
resampled.index.right.freq = pdfreq
return resampled

def _downsample_days(self, pdfreq: str | pd.offsets.BaseOffset) -> pd.DataFrame:
"""Downsample to a frequency with unit "d"."""
def _downsample_days(self, pdfreq: str | pd.offsets.Day) -> pd.DataFrame:
"""Downsample to a frequency with unit "D"."""
df = self.prices
pd_offset = pd.tseries.frequencies.to_offset(pdfreq)

Expand Down Expand Up @@ -1329,7 +1329,9 @@ def _downsample_months(
if not pre_table_sessions.empty:
start_ds = pd_offset.rollforward(start_table)
df = df[start_ds:]
resampled = helpers.resample(df, pdfreq, origin="start", nominal_start=start_ds)
resampled = helpers.resample(
df, pdfreq, origin="start_day", nominal_start=start_ds
)
resampled.index = pdutils.get_interval_index(resampled.index, pdfreq)

if drop_incomplete_last_indice:
Expand Down Expand Up @@ -1359,13 +1361,13 @@ def downsample(
pdfreq
Downsample frequency as CustomBusinessDay or any valid input to
pd.tseries.frequencies.to_offset. Examples:
'2d', '3d', '5d', '10d', 'MS', 'QS'
'2D', '3D', '5D', '10D', 'MS', 'QS'
pd.offsets.Day(5), pd.offsets.Day(15)
3 * calendar.day (where calendar is an instance of
xcals.ExchangeCalendar and hence calendar.day is an
instance of CustomBusinessDay).

If `pdfreq` has unit 'd', for example "5d", and table has a
If `pdfreq` has unit 'D', for example "5D", and table has a
frequency in CustomBusinessDay then will assume required
frequency is the table's CustomBusinessDay frequency.

Expand Down Expand Up @@ -1440,7 +1442,7 @@ def downsample(
f"Received `pdfreq` as {pdfreq} although must be either of"
" type pd.offsets.CustomBusinessDay or acceptable input to"
" pd.tseries.frequencies.to_offset that describes a"
' frequency greater than one day. For example "2d", "5d"'
' frequency greater than one day. For example "2D", "5D"'
' "QS" etc.'
)
raise ValueError(msg) from None
Expand All @@ -1449,7 +1451,7 @@ def downsample(
freqstr: str = offset.freqstr

value, unit = helpers.extract_freq_parts(freqstr)
if unit.lower() == "d":
if unit.upper() == "D":
if isinstance(self.freq, pd.offsets.CustomBusinessDay):
pdfreq = self.freq.base * value
return self._downsample_cbdays(pdfreq, calendar)
Expand All @@ -1459,7 +1461,7 @@ def downsample(
ext = ["t", "T", "H", "S"] # for pandas pre 2.2 compatibility
if unit in invalid_units + ext:
raise ValueError(
"Cannot downsample to a `pdfreq` with a unit more precise than 'd'."
"Cannot downsample to a `pdfreq` with a unit more precise than 'D'."
)

return self._downsample_months(freqstr, calendar, drop_incomplete_last_indice)
Expand Down
Loading