Skip to content

Commit

Permalink
Fix pandas 2.2 deprecation warnings
Browse files Browse the repository at this point in the history
Fixes to support pandas pre 2.2.0 default frequency strings, "T" (now
"min"), "H" (now "h"), "S" (now "s"), "M" (now "ME") and "Y" (now
"YE").
  • Loading branch information
maread99 committed Feb 2, 2024
1 parent b8256a3 commit e56d6dd
Show file tree
Hide file tree
Showing 14 changed files with 83 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ignore =
E203,
# W503 (not PEP 8 compliant) ignored to not conflict with black
W503,
# 'E704 multiple statements on one line' ignored to not conflict with black when function content defined as ...
E704,
# D105 Missing docstring in magic method. I have no issue with this.
# D Let pylint pick up all the doc errors
D
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ jobs:
with:
user: __token__
password: ${{ secrets.PYPI_TEST_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
repository-url: https://test.pypi.org/legacy/

- name: Install from testpypi and import
shell: bash
run: |
sleep 5
i=0
while [ $i -lt 12 ] && [ "${{ github.ref_name }}" != $(pip index versions -i https://test.pypi.org/simple --pre market-analy | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\
do echo "waiting for package to appear in test index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
do echo "waiting for package to appear in test index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; let i++; echo "next i is $i"; done
pip install --index-url https://test.pypi.org/simple market-analy==${{ github.ref_name }} --no-deps
pip install -r etc/requirements.txt
python -c 'import market_analy;print(market_analy.__version__)'
Expand All @@ -58,8 +59,9 @@ jobs:
- name: Install and import
shell: bash
run: |
sleep 5
i=0
while [ $i - lt 12 ] && [ "${{ github.ref_name }}" != $(pip index versions -i https://pypi.org/simple --pre market-analy | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\
do echo "waiting for package to appear in index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
do echo "waiting for package to appear in index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; let i++; echo "next i is $i"; done
pip install --index-url https://pypi.org/simple market-analy==${{ github.ref_name }}
python -c 'import market_analy;print(market_analy.__version__)'
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ repos:
hooks:
- id: check-yaml
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 24.1.1
hooks:
- id: black
# It is recommended to specify the latest version of Python
Expand All @@ -13,7 +13,7 @@ repos:
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.9
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-docstrings]
2 changes: 1 addition & 1 deletion etc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ korean-lunar-calendar==0.3.1
# via exchange-calendars
lxml==4.9.4
# via yahooquery
market-prices==0.12
market-prices==0.12.1
# via market_analy (pyproject.toml)
markupsafe==2.1.4
# via jinja2
Expand Down
2 changes: 1 addition & 1 deletion etc/requirements_dependabot/requirements_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ korean-lunar-calendar==0.3.1
# via exchange-calendars
lxml==4.9.4
# via yahooquery
market-prices==0.12
market-prices==0.12.1
# via market_analy (pyproject.toml)
markupsafe==2.1.4
# via jinja2
Expand Down
2 changes: 1 addition & 1 deletion etc/requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ korean-lunar-calendar==0.3.1
# via exchange-calendars
lxml==4.9.4
# via yahooquery
market-prices==0.12
market-prices==0.12.1
# via market_analy (pyproject.toml)
markupsafe==2.1.4
# via
Expand Down
14 changes: 11 additions & 3 deletions src/market_analy/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@
range_string,
request_daily_prices,
)
from market_analy.utils.pandas_utils import rebase_to_row
from market_analy.utils.pandas_utils import (
rebase_to_row,
interval_index_new_tz,
index_dates_to_str,
)

if TYPE_CHECKING:
from .trends.movements import MovementsSupportChartAnaly
Expand Down Expand Up @@ -649,6 +653,10 @@ def chg_every_interval(
)

elif style:
if isinstance(chgs.index, pd.DatetimeIndex):
chgs.index = index_dates_to_str(chgs.index)
else:
chgs.index = interval_index_new_tz(chgs.index, None)
symbol_cols = chgs.columns
styler = style_df(chgs.reset_index(), chg_cols=symbol_cols, caption=caption)
styler.format({c: formatter_percent for c in symbol_cols})
Expand Down Expand Up @@ -678,7 +686,7 @@ def price_on(
session = cal.minute_to_session(minute, "previous")
df = self.prices.session_prices(session)
date = df.index[0]
df = df.stack(0).droplevel(0) # one row for each symbol
df = df.pt.stacked.droplevel(0) # one row for each symbol
chg_df = self.chg(end=date, days=1, style=False)
chg_df.pop("close") # so as not to replicate column
df = pd.concat([df, chg_df], axis=1)
Expand Down Expand Up @@ -1129,7 +1137,7 @@ def max_chg_compare(
dur_cols = [c for c in DURATION_COLUMNS if c in df]
for col in dur_cols:
if col in df:
df[col].fillna(0, inplace=True)
df.fillna({col: 0}, inplace=True)
if col == "days":
df[col] = df[col].astype("int64")

Expand Down
27 changes: 9 additions & 18 deletions src/market_analy/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,23 @@ class ChartSupportsCasesGui(typing.Protocol):
"""

@property
def current_case(self) -> CaseSupportsChartAnaly | None:
...
def current_case(self) -> CaseSupportsChartAnaly | None: ...

def hide_cases(self):
...
def hide_cases(self): ...

def show_cases(self):
...
def show_cases(self): ...

def reset_marks(self):
...
def reset_marks(self): ...

def deselect_current_case(self):
...
def deselect_current_case(self): ...

def select_next_case(self):
...
def select_next_case(self): ...

def select_previous_case(self):
...
def select_previous_case(self): ...

def update_trend_mark(self):
...
def update_trend_mark(self): ...

def reset_x_ticks(self):
...
def reset_x_ticks(self): ...


class CaseBase(ABC):
Expand Down
3 changes: 1 addition & 2 deletions src/market_analy/trends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ def __init__(
interval: mp.intervals.RowInterval,
*args: typing.Any,
**kwargs: typing.Any,
):
...
): ...

def get_movements(self) -> MovementsSupportChartAnaly:
"""Evaluate all movements over `data`."""
Expand Down
12 changes: 6 additions & 6 deletions src/market_analy/trends/analy.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ class Trends:
interval : pd.Timedelta | str
Interval that each row of `data` represents. Examples:
pd.Timedelta(15, "T") (or "15T"): each row of data represents
15 minutes
pd.Timedelta(15, "min") (or "15min"): each row of data
represents 15 minutes
pd.Timedelta(1, "D") (or "1D"): for daily data.
Expand All @@ -107,7 +107,7 @@ class Trends:
Example, if each row of `data` (i.e. each bar) represents one
session (i.e. if `interval` is "1D") then passing `prd` as 10 will
set the period as 10 sessions, whilst if each bar represents an
interval of 30 minutes (i.e. `interval` is "30T") then passing 10
interval of 30 minutes (i.e. `interval` is "30min") then passing 10
will set the period as 5 trading hours.
ext_break
Expand Down Expand Up @@ -696,8 +696,8 @@ class TrendsAlt:
interval : pd.Timedelta | str
Interval that each row of `data` represents. Examples:
pd.Timedelta(15, "T") (or "15T"): each row of data represents
15 minutes
pd.Timedelta(15, "min") (or "15min"): each row of data
represents 15 minutes
pd.Timedelta(1, "D") (or "1D"): for daily data.
Expand All @@ -708,7 +708,7 @@ class TrendsAlt:
bars on a OHLC chart). For example, if each row of `data`
represents one session (i.e. if `interval` is "1D") then passing 10
will set the period as 10 sessions, whilst if each row represents
an interval of 30 minutes (i.e. `interval` is "30T") then passing
an interval of 30 minutes (i.e. `interval` is "30min") then passing
10 will set the period as 5 hours.
ext : float
Expand Down
40 changes: 35 additions & 5 deletions src/market_analy/utils/pandas_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,21 @@ def interval_of_intervals(
Examples
--------
>>> left = pd.date_range('2021-05-01 12:00', periods=5, freq='1H')
>>> right = left + pd.Timedelta(30, 'T')
>>> # ignore first part, for testing purposes only...
>>> import pytest, pandas
>>> v = pandas.__version__
>>> if (
... (v.count(".") == 1 and float(v) < 2.2)
... or (
... v.count(".") > 1
... and float(v[:v.index(".", v.index(".") + 1)]) < 2.2
... )
... ):
... pytest.skip("printed return only valid from pandas 2.2")
>>> #
>>> # example from here...
>>> left = pd.date_range('2021-05-01 12:00', periods=5, freq='h')
>>> right = left + pd.Timedelta(30, 'min')
>>> index = pd.IntervalIndex.from_arrays(left, right)
>>> index.to_series(index=range(5))
0 (2021-05-01 12:00:00, 2021-05-01 12:30:00]
Expand All @@ -151,8 +164,9 @@ def interval_of_intervals(
4 (2021-05-01 16:00:00, 2021-05-01 16:30:00]
dtype: interval
>>> interval_of_intervals(index)
Interval('2021-05-01 12:00:00', '2021-05-01 16:30:00', closed='right')
Interval(2021-05-01 12:00:00, 2021-05-01 16:30:00, closed='right')
"""
# NOTE Can lose doctest skip when pandas min support is >= 2.2
if not intervals.is_monotonic_increasing:
raise ValueError(f"`intervals` must be monotonic. Received as '{intervals}'.")
return pd.Interval(intervals[0].left, intervals[-1].right, closed=closed)
Expand Down Expand Up @@ -239,9 +253,9 @@ def interval_index_new_tz(
--------
>>> tz = ZoneInfo("US/Central")
>>> left = pd.date_range(
... '2021-05-01 12:00', periods=5, freq='1H', tz=tz
... '2021-05-01 12:00', periods=5, freq='h', tz=tz
... )
>>> right = left + pd.Timedelta(30, 'T')
>>> right = left + pd.Timedelta(30, 'min')
>>> index = pd.IntervalIndex.from_arrays(left, right)
>>> index.right.tz
zoneinfo.ZoneInfo(key='US/Central')
Expand All @@ -260,3 +274,19 @@ def interval_index_new_tz(
except TypeError:
indices.append(indx.tz_convert(tz))
return pd.IntervalIndex.from_arrays(indices[0], indices[1], closed=index.closed)


def index_dates_to_str(index: pd.DatetimeIndex) -> pd.Index:
"""Convert index representing dates to an index of dtype 'string'.
Formats dates as %Y-%m-%d.
Examples
--------
>>> import pandas as pd
>>> index = pd.date_range("2020-01-09", "2020-01-10", freq="D")
>>> str_index = index_dates_to_str(index)
>>> str_index
Index(['2020-01-09', '2020-01-10'], dtype='string')
"""
return index.strftime("%Y-%m-%d").astype("string")
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _pickle_new_prices(symbols: str, path: pathlib.Path):
"""
prices = mp.PricesYahoo(symbols)
# time adjusted to ensure prices instance doesn't request prices
now = pd.Timestamp.now().floor("T")
now = pd.Timestamp.now().floor("min")
margin = pd.Timedelta(hours=1, minutes=1)
now_required = now - margin

Expand Down
12 changes: 6 additions & 6 deletions tests/test_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def test_max_advance_and_max_decline():
assert rtrn.loc[("max_adv", "days")] == 3

# ...with minute index
index = pd.date_range("2023-01-01 12:43", freq="T", periods=len(opens))
index = pd.date_range("2023-01-01 12:43", freq="min", periods=len(opens))
df = pd.DataFrame(dict(open=opens, high=highs, low=lows, close=closes), index=index)
rtrn = analysis.max_advance(df, label="max_adv")
assert len(rtrn) == 1
Expand Down Expand Up @@ -206,7 +206,7 @@ def test_max_advance_and_max_decline():
assert rtrn.loc[("max_dec", "days")] == 4

# ...with minute index
index = pd.date_range("2023-01-01 12:43", freq="T", periods=len(opens))
index = pd.date_range("2023-01-01 12:43", freq="min", periods=len(opens))
df = pd.DataFrame(dict(open=opens, high=highs, low=lows, close=closes), index=index)
rtrn = analysis.max_decline(df, label="max_dec")
assert len(rtrn) == 1
Expand Down Expand Up @@ -888,7 +888,7 @@ def test_plot_ohlc(self, analy, intraday_pp):
assert gui.chart.plottable_interval == expected_plottable
expected = (
intraday_pp["start"].tz_convert(None),
intraday_pp["end"].tz_convert(None) - pd.Timedelta(15, "T"),
intraday_pp["end"].tz_convert(None) - pd.Timedelta(15, "min"),
)
assert gui.date_slider.slider.value == expected

Expand Down Expand Up @@ -1053,7 +1053,7 @@ def test_plot_ohlc(self, analy, intraday_pp):
assert gui._dialog.value
assert (
gui._dialog.text
== "Prices for interval '30T' are only available from '2022-12-05' although the earliest date that can be plotted on the chart implies that require data from '2021-12-30'."
== "Prices for interval '30min' are only available from '2022-12-05' although the earliest date that can be plotted on the chart implies that require data from '2021-12-30'."
)
gui._dialog.close_dialog()
assert not gui._dialog.value
Expand All @@ -1066,7 +1066,7 @@ def test_plot_ohlc(self, analy, intraday_pp):
assert gui._dialog.value
assert (
gui._dialog.text
== "Prices for interval '1T' are only available from '2023-01-03' although the earliest date that can be plotted on the chart implies that require data from '2021-12-30'."
== "Prices for interval '1min' are only available from '2023-01-03' although the earliest date that can be plotted on the chart implies that require data from '2021-12-30'."
)
gui._dialog.close_dialog()
assert not gui._dialog.value
Expand Down Expand Up @@ -2515,7 +2515,7 @@ def test_plot_mult(self, analy, intraday_pp, tz):
assert gui._dialog.value
assert (
gui._dialog.text
== "Prices for interval '15T' are not available over the current plottable dates as no price is available over this peroid for the following symbols: ['9988.HK']."
== "Prices for interval '15min' are not available over the current plottable dates as no price is available over this peroid for the following symbols: ['9988.HK']."
)
gui._dialog.close_dialog()
assert not gui._dialog.value
Expand Down
4 changes: 2 additions & 2 deletions tests/test_trends.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def data_dji_15T(path_res, xnys) -> abc.Iterator[pd.DataFrame]:
Data from call to:
prices = PricesYahoo("^DJI")
data = prices.get(
interval="15T",
interval="15min",
start="2023-05-01",
end="2023-05-30",
lose_single_symbol=True
Expand Down Expand Up @@ -134,7 +134,7 @@ def test_dji_15T_prd15_minbars10(path_res, data_dji_15T):
"""
moves = analy_trends.Trends(
data=data_dji_15T,
interval="15T",
interval="15min",
prd=15,
ext_break=0.002,
ext_limit=0.001,
Expand Down

0 comments on commit e56d6dd

Please sign in to comment.