Skip to content

Speed up multi-symbol PricesYahoo instantiation - #568

Closed
maread99 wants to merge 1 commit into
masterfrom
claude/epic-lovelace-nzdsf
Closed

Speed up multi-symbol PricesYahoo instantiation#568
maread99 wants to merge 1 commit into
masterfrom
claude/epic-lovelace-nzdsf

Conversation

@maread99

@maread99 maread99 commented May 27, 2026

Copy link
Copy Markdown
Owner

Instantiation of mp.PricesYahoo(tickers) becomes very slow with a high number of tickers, with the performance impairment possibly growing disproportionately with ticker count, and possibly some symbols having a greater affect that others.

Investigation

Profiling PricesYahoo.__init__ with the yahooquery network calls mocked out isolated the cost to calendar handling rather than networking:

  • PricesYahoo._set_daily_bi_limit sets the daily base limit to the global earliest first-trade date across all symbols. A single old symbol (e.g. KO, first trade 1962) drags this limit — and therefore every calendar built in PricesBase._set_calendars — back ~60 years.
  • The dominant hotspot (≈3s for a 1962→today span, called once during instantiation) was composite_schedule in utils/calendar_utils.py. It reduced open/close times across calendars by constructing pd.DataFrame([sch["open"] for sch in schedules]).min(), which builds a frame with one column per session and reduces over those (tens of thousands of) columns. Measured in isolation: 3.22s vs 0.006s for the equivalent per-row reduction — a ~500× difference, identical result.
  • CompositeCalendar.first_session / last_session read self.sessions[0]/[-1], forcing construction of the entire composite schedule just to obtain the bounds (which __init__ queries).
  • Separately, mixing an old symbol with a symbol on a younger-bounded calendar (e.g. XTKS, bound_min 1997) made instantiation raise, because the global daily limit was passed as start to a calendar that cannot be evaluated that far back.

Changes

  1. composite_schedule — reduce open/close across calendars with pd.concat(..., axis=1).min(axis=1) / .max(axis=1) instead of the transposed per-session-column reduction. Verified to produce identical schedules (full-DataFrame comparison across several calendar combinations, including XTKS/XKRX).
  2. CompositeCalendar.first_session / last_session — evaluate directly as max/min of the underlying calendars' bounds, avoiding a full composite-schedule build when only the bounds are needed.
  3. PricesBase._set_calendars — when the requested start (driven by the daily limit) predates a calendar's bound_min, fall back to the calendar's earliest supported start instead of raising. The pre-existing CalendarTooShortWarning already flags any shortfall against the limit.

Measured impact

Instantiation time (yahooquery mocked, fresh process, sample mixing old US/EU symbols with a Tokyo symbol):

symbols before after
6 2.80s 0.71s
8 4.50s 1.41s
10 crash (ValueError on XTKS) 2.03s

All changes are behaviour-preserving for the schedule/bounds; the third change only affects cases that previously raised.

Test plan

  • pytest tests/test_calendar_utils.py (full suite needs network for answer-CSV fixtures; run in CI)
  • pytest tests/test_yahoo.py tests/test_base.py
  • Offline equivalence check: new composite_schedule and CompositeCalendar bounds match the original implementation across multiple calendar combinations
  • ruff check / ruff format --check pass on changed files

https://claude.ai/code/session_018CDCHDRV5ikwiB6SSHYEcP


Generated by Claude Code

`composite_schedule` reduced open/close across calendars by building a
DataFrame with one column per session and reducing over those columns.
That is O(sessions) columns and dominates instantiation when calendars
span a long period (e.g. when a symbol has an early first-trade date,
which drags the daily limit, and hence the calendars, far back). Reduce
across calendars (axis=1) instead, which is identical in result and
orders of magnitude faster.

Also evaluate `CompositeCalendar.first_session` / `last_session`
directly from the underlying calendars so that querying the composite
bounds (as happens during instantiation) no longer forces construction
of the full composite schedule.

Finally, fall back to a calendar's earliest supported start in
`_set_calendars` when the daily limit predates the calendar's
`bound_min`, so that combining an old symbol with a symbol on a
younger-bounded calendar (e.g. XTKS) no longer raises on instantiation.

https://claude.ai/code/session_018CDCHDRV5ikwiB6SSHYEcP
@maread99

Copy link
Copy Markdown
Owner Author

Claude completely missed the real problem (addressed in #569). What was offered here seems something of a nonsense.

@maread99 maread99 closed this May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants