Skip to content

Project the trendline with fixed weights, not a polyfit per row (0.14.4) - #49

Merged
mspinola merged 1 commit into
mainfrom
claude/faster-trading-signals
Sep 25, 2026
Merged

mspinola merged 1 commit into
mainfrom
claude/faster-trading-signals

Conversation

@mspinola

Copy link
Copy Markdown
Owner

Why

The first render of cot-analyzer's newest weekly report is dominated by the first get_symbols_data build per market, and most of that is append_trading_signals (see mspinola/cot-analyzer#137). Profiling 84 real calls (42 markets x raw and OI-normalized) found one hot spot: is_down_trend_line_break ran np.polyfit in rolling.apply 93,546 times, about 36% of the function.

Change

New conditions.rolling_trendline_projection(series, window). For a fixed window, the least-squares line through (0..n-1, y) projected to x=n is a fixed weighted sum of the y values, w_i = 1/n + (x_i - xbar)(n - xbar)/Sxx. The rolling fit therefore becomes one sliding-window dot product. It keeps the same NaN rule as rolling(window).apply. project_trendline is kept as the reference, and tests pin the new form against it. Version bumps to 0.14.4.

Equivalence

  • The projection matches polyfit to 7.4e-13 relative on all 46,773 real windows. It is not bit for bit, because lstsq sums in a different order.
  • The break compares a close against the projection, so exact ties are decided by rounding in both forms. The real store has 9 near-ties, 4 of them inside a downtrend.
  • All 84 append_trading_signals outputs are identical to main in every column, dtype and column order, including those 4.

Speed (local, real stores)

main this
append_trading_signals, 84 calls 2.73s 1.67s
cold get_matrix_data(None, "Custom") (3 runs) 1.66-1.80s 1.08-1.12s

What's left is about 450 small pandas operations per call on ~440-row frames, roughly 30us each, with no single hot spot. The next biggest pieces are the ~66 column inserts per call into a 170-column frame (~2.4ms) and 11 range indexes (~1.8ms). Going further would mean rewriting the signal conditions in numpy, which is a much larger and riskier change and is not attempted here.

Tests

pytest gives 498 passed, 2 skipped (the EFA/EEM proxy tests, since the dummy store has no equity bars). ruff check src tests is clean (0.15.22). cot-analyzer's suite passes against this tree too (886).

🤖 Generated with Claude Code

is_down_trend_line_break fitted a least-squares line to every trailing
5-bar window with np.polyfit through rolling.apply: 93,546 fits for the
42-market universe on both bases, the largest single cost in
append_trading_signals (~36% of it under the profiler). For a fixed window
the line through (0..n-1, y) projected to x=n is a fixed linear
combination of the y values, w_i = 1/n + (x_i - xbar)(n - xbar)/Sxx, so
the rolling fit is one sliding-window dot product. Same NaN rule as
rolling(window).apply.

Values agree with polyfit to 7.4e-13 relative (lstsq sums in a different
order), so not bit for bit. The break compares a close against the
projection, so exact ties are decided by rounding in both forms: 9
near-ties (4 inside a downtrend) exist in the real store, and all 84
append_trading_signals outputs (42 markets x raw and OI-normalized) are
identical to main, every column, dtype and order. New tests pin the
vectorised form against the rolling polyfit, NaN for NaN, windows 2-10.

append_trading_signals, 84 real calls: 2.73s -> 1.67s. Cold Signal
Matrix build (cot-analyzer's first weekly-page render): 1.7s -> 1.1s
locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mspinola
mspinola merged commit 94edb96 into main Sep 25, 2026
4 checks passed
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.

1 participant