Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
[Unreleased]¶
eqlib Core — Correctness (P0)¶
Fixed¶
- Annualization basis unified to 244 trading days —
TRADING_DAYS_PER_YEAR(244, the A-share correct value) is now imported fromeqlib.constantseverywhere it was previously hardcoded as 252. Affected:eqlib/scientific/statistics.py,eqlib/scientific/risk.py,eqlib/scientific/overfitting.py,eqlib/utils/stats.py(rolling_sharpe,downside_deviation),eqlib/attribution.py(_calc_alpha_betaandanalyze_returns). The same backtest now produces matching Sharpe ratios across all modules — previously the same daily returns could yield different annualized values depending on which code path computed them. - OrderCost defaults updated to 2024 fee rates —
eqlib/objects.py:OrderCostnow defaults toclose_tax=0.0005,open_commission=close_commission=0.00025, aligning withexamples/_defaults.py:DEFAULT_ORDER_COSTand the 2023-08-28 stamp-duty cut. Cascaded toeqlib/engine.py,eqlib/strategies/*.py,eqlib/examples/multi_factor_momentum.py,web_strategy_studio/backend/studio_api/routers/strategies.py,web_strategy_studio/backend/studio_api/data/templates.json,web_strategy_studio/backend/studio_api/data/eqlib_symbols.json,agent/strategy_template.py. The historical 0.001 stamp duty is still applied automatically for dates before 2023-08-28 via the existing date-branch logic. - MLSelector honest naming —
target='forward_return_5d'now raisesNotImplementedErrorinstead of silently computing past returns. The default target remainspast_return_5d(backward-compatible) but its docstring explicitly warns that it uses historical returns as labels — not a true forward-looking model. For true forward-return prediction, users must supplylabel_dataas a panel DataFrame. MLSelectorconstructor signature completed —label_dataandcustom_featuresparameters are now exposed in the public API (previously only accessible via internals), enabling panel-label training and user-supplied feature functions.walk_forward_analysisis now a true rolling window — Replaced the previous single-backtest-and-slice proxy with a real walk-forward loop: the[start_date, end_date]range is divided into multiple train/test windows that slide forward bystepdays. Each window runs a fresh backtest on the train segment (in-sample) and the test segment (out-of-sample), and per-window Sharpe / return / max-drawdown are aggregated intooos_is_ratioandis_sharpe_decay. The dict-input path (slicing a pre-computed equity curve) is retained for backward compatibility.- Cornish-Fisher VaR —
eqlib/utils/stats.py:value_at_riskandeqlib/scientific/risk.py:value_at_risknow acceptmethod='cornish_fisher', adjusting the parametric (normal) VaR for sample skew and excess kurtosis. Better suited for A-share daily returns, which are typically negatively skewed and heavy-tailed — plainparametrictends to underestimate tail risk. - Statistical ddof consistency —
eqlib/scientific/risk.pynow usesddof=1(sample standard deviation) everywhere, matchingeqlib/attribution.pyandeqlib/scientific/statistics.py. Previously a few code paths usedddof=0, producing slightly different Sharpe/Sortino values for the same input series. examples/18_grid_trading.pyfixed — Original grid strategy generated 0 trades over the test period (MDD 97.4%). Rewrotemarket_openwith level-crossing trigger logic (buy when price crosses DOWN to a lower grid level, sell when it crosses UP to a higher level), reduced grid levels from 10 to 8, shortened range lookback from 60 to 30 days, and added a 5% rebuild threshold to refresh the grid when the price range drifts. New result: 146 trades, MDD -19%, win rate 71.9%.- Examples 21-24 normalized — ML examples now use
examples._defaultsfor trading costs and dates (instead of hardcoded values), removedsys.path.insert(0, '.')(provided bypip install -e .), and added module-level docstrings with number / title / teaching objectives / run command. Example 21 changedtarget='forward_return_5d'→past_return_5dto align with the honest naming above.
Web Strategy Studio¶
Security¶
- Real JWT authentication —
studio_api/auth.py:get_current_usernow validates JWT bearer tokens (or short-lived SSE query tokens), looks up the user in the database, checksis_active, and verifies session revocation viaUserSession. Previously the function was a stub that always returned a demo user, defeating the protected routes. ensure_admin_usersetsrole=ROLE_ADMIN— Admin user created on startup now actually has admin privileges. Also refuses to start withoutEQ_ADMIN_PASSWORDunlessEQ_STUDIO_TESTING=1.
Documentation¶
Added¶
- Tutorial 11 (ML Selection) — both
docs/tutorials/11-ml-selection.mdand.en.mdnow use the 2024 fee rates. docs/reference/api-ml.md—MLSelectorconstructor signature now showslabel_dataandcustom_features; new sections forcheck_feature_driftandauto_tune_selector.docs/reference/api-scientific.md—walk_forward_analysisdocumented with the new true rolling-window behavior, including return values (oos_is_ratio,is_sharpe_decay, per-windowtrain_start/end/test_start/end).docs/reference/api-risk.md— Cornish-Fisher VaR method documented.docs/tutorials/index.mdand.en.md— fixed stale "Example N" numbering that did not match the actual file names.
CI/CD¶
Fixed¶
doc-sync.ymlnow installs docs extras viapip install -e ".[docs]"instead of a hardcoded package list —mkdocs-git-revision-date-localized-pluginwas missing, causingmkdocs build --strictto fail.- Studio Tests Backend (Python 3.10/3.11), Studio Tests Frontend, and Doc Sync Check workflows all pass.
[1.0.3] — 2026-05-23¶
Web Strategy Studio¶
Added¶
- Auto-logout on authentication failure (401 response) with seamless redirect to login page
- Preset users configuration via
users.yamlfile (recommended) orEQ_PRESET_USERSenvironment variable - Smart polling for history panel — only polls when there are active (running/queued) runs
Changed¶
- Removed registration page — users must be pre-configured by server
- Fixed sidebar navigation buttons not responding to clicks
- Added loading indicators for login button, stock search, and backtest runs
- Enhanced button hover effects with visual feedback
- Translated key UI labels to Chinese (e.g., "初始资金", "使用本地数据")
- Added
aria-live="polite"for toast notifications, improved keyboard navigation
Fixed¶
- Race condition in
run_queue.py— semaphore acquisition order to prevent queue corruption - Stream hub buffer operations — added async lock for concurrent buffer access
- Stock picker search cancellation — AbortController cancels previous requests
- Unmount state handling — added
mountedRefto prevent setState on unmounted components - JWT leak in URLs — fixed JWT token appearing in URLs when opening reports in new tabs
Security¶
- Hardened JWT handling and token validation
- Filtered sensitive environment variables (
EQ_JWT_SECRET,EQ_ADMIN_PASSWORD) from subprocess - Removed unauthenticated static reports mount — reports now served through authenticated API only
eqlib Core¶
Fixed¶
- NaN handling in Sharpe, Sortino, max_drawdown, and beta calculations — returns stable values for edge cases
Documentation¶
Added¶
- Web Studio section in README, user guide, tutorials, and FAQ
- 6 new Web Studio FAQ entries (login, deployment, troubleshooting)
Changed¶
- Improved Quick Start sections with PyPI vs source install guidance
- Updated web_studio.md with auto-logout mechanism and preset users configuration
GitHub Pages¶
Added¶
- "Web 工作室" hero card with icon (🌐) on homepage
- Emoji icons for all hero cards (🚀, 📖, 🎓, 🌐, 🔧)
- Search keyboard shortcut hint ("⌘K 快捷搜索")
- Focus-visible states for keyboard navigation accessibility
Changed¶
- Enhanced hero card hover effects with elevation animation and shadow
- Added back-to-top fade-in animation
- Added navigation active indicator (blue accent bar)
- Optimized responsive layout for mobile devices
- Added print-friendly styles
CI/CD¶
Fixed¶
- Studio Tests workflow: ruff lint errors,
@types/nodefor frontend TypeScript - Backend pytest: asyncio loop handling and queue isolation tests
- bcrypt 4.x compatibility: replaced passlib with bcrypt directly
[1.0.2] — 2026-05-15¶
Security¶
- CORS
allow_originsconfigurable viaEQ_STUDIO_CORS_ALLOWED_ORIGINS - Subprocess environment filtered to prevent host secrets leaking
StreamHubcleans up empty entries to prevent unbounded memory growth
Web Strategy Studio¶
- Dockerfile adds non-root
studiouser andHEALTHCHECK cancel_runawaits subprocess exit before committing "cancelled" to DB- Backtest report modal uses native Lightweight Charts rendering
- Metrics comparison table uses real LTVC mini area charts
- Python 3.9 compatibility with legacy typing syntax
Backtest Correctness (P0)¶
- Fixed look-ahead bias in stock selection filters
- Fixed alpha/beta
fillna(0)contaminating benchmark returns - Fixed monthly rebalance skipping when month-start is a holiday
- Added optional price-limit enforcement (
set_option('check_price_limit', True)) - Fixed MaxSharpe arithmetic-mean annualization (now uses geometric formula)
HTML Report¶
- Added RSI(14), MACD(12,26,9), Bollinger Bands(20,2) indicators
- Added indicator toggle panel on K-line chart
- Added crosshair-linked legend for real-time values
⚠️ Backtest Correctness Fixes (P0) (continued from Unreleased)¶
Users of previous versions should re-run their backtests. Several bugs caused strategies to appear significantly more profitable than they would be in practice.
C1 — Look-ahead bias in stock selection filters (⚠️ previous results inaccurate)¶
- Root cause:
filter_low_price_stocks,filter_high_pe_stocks, andfetch_factor_datacalled the live-data API (get_current_data) even during historical backtests, filtering stocks with today's PE/price. - Fix: In backtest mode (preloaded OHLCV panel), OHLCV fields now use the
preloaded bar at
context.current_dt; PE/PB return NaN with a warning instead of silently using the live snapshot.
C2 — Alpha/beta fillna(0) contaminates benchmark returns (⚠️ previous results inaccurate)¶
- Root cause:
_calc_alpha_betaandfama_french_analysistreated missing benchmark days as "0% return" viareindex(...).fillna(0), biasing beta downward and alpha upward. - Fix: Use raw intersection /
dropnaseries.
C3 — Monthly rebalance skips when month-start is a holiday¶
- Root cause:
_should_run_schedulecheckedday.day == N; A-share January 1 is always a holiday somonthly:1never fired. - Fix: New
_is_first_trading_day_ge(day, n)fires on the first trading day ≥ N using the preloaded trading calendar.
C4 — No price-limit enforcement¶
- Fix: Added
_get_price_limit_ratio()(10% main board, 20% ChiNext/STAR) and an optional check viaset_option('check_price_limit', True)(default False — existing backtests unaffected).
C5 — MaxSharpe arithmetic-mean annualization (⚠️ previous Sharpe values too high)¶
- Root cause:
_annual_statsusedmean * 252, which diverges significantly from geometric return at high volatility. - Fix: Geometric formula:
(1 + port_ret).prod() ** (252/n) - 1.
C6 — Paper-trade fills orders before any strategy orders exist¶
- Fix: Added
_warmup_doneflag; skips_fill_pending_orderson the first calendar-day transition.
Added¶
simple_factor_analysis()— renamed fromfama_french_analysis()to accurately reflect that it does not implement the Fama-French 3-factor model. The old name is retained as a deprecated alias with aDeprecationWarning.walk_forward()andWFAResultineqlib.wfa— walk-forward / rolling out-of-sample validation framework.StrategyConfig.use_localfield — promotesuse_localfrom a hardcodedTrueto a user-configurable parameter inrun_portfolio_backtest.- Glossary page (
docs/glossary.md) added to documentation site. tools/profiling/directory for performance benchmarking scripts (moved fromexamples/21_combined_strategy/).
HTML Report — TradingView-Style Indicators¶
- RSI(14) — Welles Wilder EMA-based RSI oscillator with 70/30/50 reference lines and 30/70 overbought/oversold zones.
- MACD(12,26,9) — MACD line, signal line, and histogram with zero-line reference.
- Bollinger Bands(20,2) — Upper/middle/lower bands overlay on the K-line chart.
- Indicator Toggle Panel — Floating buttons on the K-line chart to show/hide MA, BB, Volume, and Support/Resistance independently.
- Crosshair-Linked Legend — Real-time overlay showing OHLC, MA values, RSI, MACD/Signal/Hist, BB bands, and volume at the current cursor position.
Changed¶
attribute_history(..., fq='post')now raisesValueErrorin backtest mode (the preloaded panel only storesqfq/ forward-adjusted data); usefq='pre'._get_trading_days()now prefersak.tool_trade_date_hist_sina()over the 601390 stock history (more complete, no listing-date gaps).data._cacheanddata._spot_cacheare now protected bythreading.Lockfor safe use in multi-threaded back-testing scenarios.- Sortino ratio now uses MAR = 0 (industry standard) and
ddof=0, consistent with the Sharpe ratio implementation. analyze_returns()excess Sharpe is now the correct Information Ratio (excess.mean() / excess.std() * sqrt(252)); the prior double-subtraction of the risk-free rate has been removed.
Fixed¶
- Monthly schedule:
monthly:1now correctly fires on the first A-share trading day of each month (previously missed January and months where day 1 falls on a weekend/holiday). fama_french_analysis/_calc_alpha_betano longer distort beta/alpha by treating missing benchmark days as 0% returns.
Web Strategy Studio¶
- S1: CORS
allow_originsis now configurable viaEQ_STUDIO_CORS_ALLOWED_ORIGINS; defaults tolocalhost:5173andlocalhost:8080only (removes the["*"]+allow_credentials=Trueviolation). - S2:
security_scanner.pynow documents its limitations as a "friendly lint" tool, not a security sandbox. - S3: Subprocess environment is now filtered to an allowlist (
PATH,HOME,EQ_*, etc.) to prevent host secrets from leaking into user strategy processes. - S4:
StreamHubnow cleans up emptyrun_identries after every unsubscribe and on terminal events (done/error), preventing unbounded memory growth on long-running instances. - S5:
backtest_executor.pynow parses"Backtest progress N/M"lines from the engine's stdout for true fractional progress instead of the crudelog_lines // 2heuristic. - S6: Idempotency map entries now expire after 24 h (configurable via
EQ_STUDIO_IDEMPOTENCY_TTL_SEC); a background task purges stale entries. - S7: Dockerfile adds a non-root
studiouser and aHEALTHCHECKfor container orchestrators. - S8:
cancel_runnow awaits subprocess exit (up to 5 s) before committing "cancelled" to the DB, eliminating duplicate SSEdoneevents. - S9: Backtest report modal now uses native Lightweight Charts
rendering (replacing the iframe approach). The new
ReportViewercomponent renders all chart types — K-line, cumulative returns, drawdown, daily P&L, RSI, MACD — with synchronized time scales and responsive resizing. - S10: Metrics comparison table now uses real LTVC mini area charts for equity curve sparklines instead of SVG approximations, with green/red coloring based on cumulative direction.
- S11: New backend endpoint
GET /api/v1/runs/{run_id}/report/dataserves the full report.json with all chart data arrays for native rendering. - S12: Python 3.9 compatibility — all
str | None/list[T]/dict[K, V]type annotations converted toOptional[T]/List[T]/Dict[K, V]across the entire backend codebase, with ruff rules adjusted to accept the legacy typing syntax.
Documentation¶
- M10: mkdocs nav now points to the split-by-chapter user guide
(
doc/user_guide/index.md); the old single-page version is still accessible but no longer in the top nav. - M11: CHANGELOG, CONTRIBUTING, and SECURITY added to the nav under "项目动态 / Project Updates".
- E6:
doc/utils_reference.mdadded to the API reference section of the nav. - E9: Glossary page added (
docs/glossary.md).
Risk Disclosures Added¶
- Strategy examples 14, 15, 16, 17 now include
⚠️ RISK DISCLOSUREheaders warning that examples are for teaching purposes only and should not be deployed to live trading without thorough out-of-sample validation.
[0.1.1] — 2025-07 (P0 patch)¶
See [Unreleased] section for the full list. This will be tagged once all P0 backtest correctness fixes are merged.
[0.1.0] — 2025-05¶
Initial release.
Added¶
- Event-driven backtest engine (
eqlib/engine.py) - Fluent query API (
eqlib/stock_query.py) - Data layer with akshare integration and local CSV caching
- Strategy templates: MACD+Volume, Bollinger, Grid Trading, Multi-Factor
- Matplotlib chart generation and Markdown report export
- PTrade/QMT adapter for live deployment
- Comprehensive test suite
- Dual index charts (CSI300 + SSE) in HTML backtest reports
- Support/Resistance strategy (
examples/20_sr_strategy/) - Stock selection strategy (
examples/22_stock_selection_strategy.py) - Local data backtest (
examples/19_local_data_backtest.py) with CSV cache - Portfolio backtesting with equal-weight rebalancing (
examples/12_portfolio_backtest.py) - Brinson attribution and factor analysis
- HTML report with interactive charts via Lightweight Charts
- MkDocs documentation site with CI/CD via GitHub Pages