perf(factors): tiered operator optimization — bottleneck + numpy stride + parallel bench#342
Conversation
…de + parallel bench Tier 1: Replace 4 slow Python-callback factor operators with C-compiled and vectorized equivalents (~45-354x per operator): - ts_rank: numpy sliding_window_view (~45x, bn.move_rank uses incompatible Spearman normalization) - ts_argmax/ts_argmin: bottleneck move_argmax/argmin with (n-1)-result correction (~233-280x) - decay_linear: numpy stride + einsum (~42x, scipy convolve1d has lookahead bias) Tier 2: Add ProcessPoolExecutor to bench_runner.py for parallel factor computation via fork COW on macOS/Linux (VIBE_TRADING_BENCH_WORKERS). Tier 3: Vectorize _calc_equity() in BaseEngine with numpy array ops, auto-detecting subclass overrides (FuturesBaseEngine, CompositeEngine) to preserve market-rule correctness. Graceful fallback: VIBE_TRADING_DISABLE_BOTTLENECK=1 forces pandas path. All 77 new tests pass in both modes. Full suite: 4473 passed. Co-Authored-By: OpenCode <noreply@opencode.ai> AI-Model: qwen3.7-max Co-Authored-By: opencode <noreply@ai-tool.com> Co-Authored-By: Qoder <noreply@qoder.com> Co-Authored-By: Claude Code <noreply@anthropic.com> AI-Contributed/Feature: 435/435 AI-Contributed/UT: 517/517
Review Work — Final ReportOverall Verdict: PASSED ✅
Test Results SummaryNew Tests (46/46 passed ✅)Full Test Suite (4623/4627 passed ✅)
Fallback Mode (37/37 passed ✅)Performance Benchmarks (Real tushare key)Operator Benchmarks (5000 rows × 100 cols, window=20)
Real-World Scale (2000 rows × 300 cols, window=20)
Equity Vectorization (50 positions, 5000 iterations)
Real Alpha Bench (tushare, CSI300, gtja191 subset, 2 workers)
Goal Breakdown
Constraint Compliance
Security Review
Severity: NONE — This is a pure performance optimization with no security surface changes. Key FindingsPositive
Non-Blocking Observations
RecommendationsNon-blocking (consider for follow-up)
Review performed with real tushare key backtest validation. All 5 background review agents timed out (30m each); review completed locally with full test execution and manual code review. |
Closes #339 Refs #342 Original performance implementation from #342 by @shadowinlife. Maintainer integration keeps the contribution and tightens the parallel execution and dependency edges before merge. Validation: - PYTHONPATH=/tmp/vibe-pr342-bn-only:agent pytest agent/tests/test_factor_operators.py agent/tests/test_bench_parallel.py agent/tests/test_equity_regression.py -q - VIBE_TRADING_DISABLE_BOTTLENECK=1 PYTHONPATH=/tmp/vibe-pr342-bn-only:agent pytest agent/tests/test_factor_operators.py -q - python -m compileall -q agent/backtest/engines/base.py agent/scripts/bench_performance.py agent/src/factors/_backend.py agent/src/factors/base.py agent/src/factors/bench_runner.py - git diff --check - GitHub Actions CI test passed
|
Thanks @shadowinlife. We integrated the performance work via maintainer PR #376 with a small maintainer polish pass around worker initialization, warning handling, and dependency cleanup. Closing this PR as superseded by #376; #339 is now closed by the merged integration. |
Summary
ts_rank,ts_argmax,ts_argmin,decay_linear) with C-compiled bottleneck and vectorized numpy equivalents — 45-354x faster per operatorProcessPoolExecutortobench_runner.pyfor parallel factor computation — 3-4x end-to-end speedup on multi-core machines_calc_equity()inBaseEnginewith numpy array ops, auto-detecting subclass overrides for market-rule correctnessWhy
Factor bench runs (e.g.
alpha bench --zoo gtja191 --universe csi300) spend 90%+ of wall time in 4 operators that usepandas.rolling().apply()with Python callbacks — the slowest possible path for numpy-backed data. The backtest equity loop also calls_calc_equity()per bar with a Python dict iteration over positions.This PR replaces those hot paths with C-compiled (bottleneck) and vectorized (numpy stride tricks / einsum) equivalents, with graceful fallback via
VIBE_TRADING_DISABLE_BOTTLENECK=1.Changes
close #339
Tier 1: Operator Replacement (4 operators)
ts_rankrolling().apply(_last_rank)sliding_window_viewts_argmaxrolling().apply(_argmax_last)bn.move_argmax+(n-1)-resultcorrectionts_argminrolling().apply(_argmin_last)bn.move_argmin+(n-1)-resultcorrectiondecay_linearrolling().apply(_dot_weights)einsumDesign notes:
bn.move_rankwas evaluated but uses Spearman rank correlation (incompatible with our percentile-rank normalization) —ts_rankuses numpysliding_window_viewinsteadscipy.ndimage.convolve1dwas evaluated fordecay_linearbut introduces lookahead bias — numpy stride is causally correctbn.move_argmax/argminreturns distance from window end, not 0-based index — corrected via(n-1) - resultsrc/factors/_backend.pyhandles graceful bottleneck import withVIBE_TRADING_DISABLE_BOTTLENECK=1env overrideTier 2: Factor-Level Parallelism
bench_runner.pynow usesProcessPoolExecutorwith fork COW on macOS/LinuxVIBE_TRADING_BENCH_WORKERSenv var (default:os.cpu_count())workers=1or only 1 alpha_compute_single_alphais picklable; Registry is not pickled — each worker callsget_default_registry()internallyTier 3: Equity Vectorization
_calc_equity()extracts position fields into numpy arrays, vectorizes margin + PnL as array arithmeticFuturesBaseEngine._calc_pnl,CompositeEngine._calc_margin) — falls back to loop when overriddentotal_unrealizedloop also vectorized with the same patternDependencies
bottleneck>=1.3.7to main dependencies (pre-built wheels, no C compiler needed)hypothesis>=6.0to dev dependenciesTest Plan
pytest --ignore=agent/tests/e2e_backtest --tb=short -q→ 4473 passed)test_factor_operators.py— 37 equivalence tests (bottleneck vs pandas fallback, NaN handling, warmup, lookahead, constant windows, fallback mode)test_bench_parallel.py— 5 tests (parallel vs sequential equivalence, progress callback,only=filter, error isolation)test_equity_regression.py— 4 tests (empty positions, single/multiple positions, mixed leverage)agent/scripts/bench_performance.pyconfirms speedups on 5000×100 panelVIBE_TRADING_DISABLE_BOTTLENECK=1 pytest→ all 77 new tests passChecklist
src/agent/,src/session/,src/providers/) without prior discussion