Skip to content

feat(leaderboard): add 14 external strategies as baseline classes - #395

Open
TrentonNewWorld wants to merge 6 commits into
Open-Finance-Lab:mainfrom
TrentonNewWorld:feat/external-leaderboard-strategies
Open

feat(leaderboard): add 14 external strategies as baseline classes#395
TrentonNewWorld wants to merge 6 commits into
Open-Finance-Lab:mainfrom
TrentonNewWorld:feat/external-leaderboard-strategies

Conversation

@TrentonNewWorld

Copy link
Copy Markdown

Summary

  • Adds 14 deterministic (no LLM call) baseline strategies to the leaderboard registry, translated from three external sources backtested in a "Strategy Lab" report: TradingAgents' technical-composite proxy, 6 of QuantConnect's public Investment Strategy Library entries, and 8 of freqtrade-strategies' most prominent strategies (crypto-specific filters identified and stripped rather than blindly ported; GodStra excluded as not portable).
  • Adds _signal_engine.py, which bridges these daily-scale signals (RSI-14, SMA-200, monthly rebalances, etc.) onto the hourly equity-curve contract BaselineStrategy.run() must honor — resamples hourly bars to daily OHLCV, evaluates each strategy's weight function once per trading day using only history strictly before that day (no look-ahead), and marks equity every hour in between.
  • Every strategy's lookback adaptively caps at whatever history is actually available rather than requiring a fixed window, since the real leaderboard's contest window (~1 month) is much shorter than the full year these strategies were originally validated against.
  • Registers all 14 in registry.py and adds matching entries to leaderboard.json's strategies array (a registered class alone is inert — it only becomes visible/live once it also has a config entry, since that's the only call site for the registry).
  • Also includes two earlier, unrelated fixes made in the same working session: the Windows venv-path + repo-root-relative DATABASE_PATH bugs (fix(backtests)), and a risk-gated Alpaca live-trading broker + a new Mission Control wallet/holdings page (feat(trading)).
  • Along the way, fixed two pre-existing gaps caught while verifying this: mission_control.py was leaking raw exception text via "error": str(e) (violates test_error_detail_sanitization.py's CodeQL-motivated guard), and test_app_composition.py's route contract didn't yet include the two Mission Control routes.

Test plan

  • pytest dashboard/backend/tests/domain/leaderboard/test_external_strategies.py -v — 58 new tests (registry identity, key resolution, required_symbols, run() smoke tests against synthetic hourly bars, including a short-history graceful-degradation case).
  • Full backend suite (pytest dashboard/backend/tests/) — only the same 8 pre-existing, unrelated failures remain (iFinD, vnpy, optional-SDK, test_engine_move, test_canonical_consumers, test_router_move::test_no_circular_imports) plus one pre-existing CSRF test error; confirmed via a clean git stash baseline that none are caused by this branch.
  • Verified end-to-end against real Alpaca market data on a local dev server: all 26 leaderboard entries (12 original + 14 new) return sane, finite equity curves via GET /api/v1/leaderboard?period=contest, and all 14 new strategies appear by name in the frontend's chart legend/picker on the Competition tab.

🤖 Generated with Claude Code

TrentonNewWorld and others added 3 commits August 21, 2026 18:46
…BASE_PATH

Two bugs made every dashboard backtest fail on Windows: the venv-python
resolution assumed the Unix Scripts layout (bin/python3) instead of checking
Windows' Scripts/python.exe first, and a relative DATABASE_PATH resolved
against the backtest subprocess's cwd rather than the repo root, silently
writing to a second, wrong database nested under dashboard/dashboard/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…page

Custom live-trading broker mirroring the existing Robinhood risk-gate
pattern: separate live key pair from paper keys (Alpaca rejects one on the
other's endpoint), off by default via both a per-call flag and an
ALPACA_LIVE_EXECUTE env var kill switch, per-order notional cap, no
shorting, full JSONL audit logging.

Mission Control is a new dedicated page showing real-money and paper
wallet balances/holdings side by side, backed by a single read-only
overview endpoint. Placing orders stays out of scope for that endpoint --
see alpaca_live_service.py for the actual risk-gated execution path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds 14 deterministic (no LLM call) baseline strategies to the leaderboard
registry, translated from three external sources backtested in the
"Strategy Lab" report: TradingAgents' technical-composite proxy, 6 of
QuantConnect's public Investment Strategy Library entries, and 8 of
freqtrade-strategies' most prominent strategies (crypto-specific filters
identified and stripped rather than blindly ported; GodStra excluded as
not portable, its thresholds being hyperopt-fit to a specific crypto pair).

The key architectural piece is _signal_engine.py: every BaselineStrategy
must return an hourly equity curve (confirmed via base.py and
service.py's fetch_hourly_bars using TimeFrame.Hour), but all 14
strategies' signals are daily-scale (RSI-14, SMA-200, monthly rebalances,
etc). The engine resamples hourly bars to daily OHLCV, evaluates each
strategy's weight function once per trading day using only history
strictly before that day (no look-ahead), and marks equity every hour in
between -- preserving the validated daily-cadence logic while honoring the
hourly-bar contract the rest of the leaderboard engine expects.

Every strategy's lookback adaptively caps at whatever history is actually
available rather than requiring a fixed window: the real leaderboard's
contest window is only about a month, versus the full year these
strategies were originally validated against, so e.g. a "252-day
momentum" strategy degrades to a much shorter-window version of itself on
the real board rather than crashing or NaN-ing out. Documented per
strategy, not hidden.

Registering a class alone is inert -- a strategy only becomes visible/live
once it also has an entry in leaderboard.json's strategies array (the only
call site for the registry), so 14 matching entries were added there too.

Also fixes two pre-existing gaps in dashboard/config/leaderboard.json's
consumers caught while verifying this: mission_control.py was leaking raw
exception text via "error": str(e) (test_error_detail_sanitization.py's
CodeQL-motivated guard), and test_app_composition.py's route contract
didn't yet include the two Mission Control routes.

58 new tests cover registry identity, key resolution, required_symbols,
and run() smoke tests against synthetic hourly bars, including a
short-history graceful-degradation case matching the real contest
window's constraint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

@TrentonNewWorld is attempting to deploy a commit to the allan-feng's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Allan-Feng

Copy link
Copy Markdown
Collaborator

Thanks for putting this together. We really appreciate the work that went into the strategies.

A suggestion: rather than adding all of them to the leaderboard right away, it would be great to pick 2–3 of them and run them through the ATL backtest workflow first, and compare their results with the existing market indices/ buy-and-hold baselines.

After testing the strategies, Agent Supermarket would be a great place for them. That’s where we’d love community agents to land, so people can try them, clone them, and iterate.

Happy to talk through details anytime. Feel free to ping me (allanfeng) on our Discord: https://discord.gg/9HnQ6XDG98

FlyM1ss and others added 3 commits August 30, 2026 12:41
Behaviour:
- turn_of_month: the last sampled date is no longer flagged a month-end
  (bought SPY on the final day of every contest); a zero open no longer
  starts a phantom hold.
- capm_alpha_ranking: a lone qualifier gets weight 1.0 (was a hardcoded
  0.5 leaving half the book in cash); OLS variance/centring computed on
  the symbol's own valid days; pct_change(fill_method=None).
- volatility_effect: pct_change(fill_method=None) so a gap is not a 0% day.
- bandtastic: slow-EMA span capped at available history -- the fixed
  50-day floor could never trade on the ~44-day leaderboard window.
- trendrider: SMA-200 window sized off the symbol's own rows, not the
  union frame (one gap day made it permanently NaN).
- _indicators: rsi -> 100 (not NaN) with no down moves; adx -> 0 (not
  NaN) on a range-bound stretch; zscore_row all-NaN -> zeros.
- build_price_cache: a symbol missing only the first timestamp is priced
  from its own first bar instead of being dropped for the whole run;
  duplicated bar stamps resolve to the last bar (also deduped in the two
  SPY-only strategies before scalar .loc reads).

Structure / performance:
- hlhb, trendrider, supertrend_triple precompute indicators once per
  symbol over the full series (causal, so bit-identical; pinned by
  test_indicators.py) instead of recomputing per symbol per day.
- daily_history computes the ET date key once per symbol, not 5x.
- Dead lot_size parameter removed; duplicate price comprehension folded.
- base.required_symbols / num_trades gain the shared default; 12 + 14
  identical overrides deleted; subset_bars used everywhere.

Tests: the multi-symbol smoke tests were vacuous (fixture keyed SYM0..29
vs the DJIA-30 default, so run() returned [] before touching anything);
they now pass the synthetic symbols and assert the curve length. Adds a
reference-window test for the engine and regression tests for each fix.
Drops the unused `datetime as dt` import (CodeQL py/unused-import).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKw312eMfu2bWaAsR4M8xb
Mission Control:
- GET /api/v1/mission-control/overview is admin-gated (it returns the
  real account's cash/equity/positions and whether execution is armed).
- Live snapshot failures are caught and reported instead of 500ing the
  page; successful snapshots cached 30s via paper_trading_cache.
- Page sends credentials, escapes every interpolated field, and shows a
  sign-in message on 401/403. Router added to the blocking-IO guard list.

Live services:
- alpaca_live_service validates the decision through the LLM validator
  before turning it into orders; rejected actions are reported in the
  result; ALPACA_LIVE_EXECUTE read once per run.
- robinhood_live_service accepts "on" like the other truthy env parsers.
- alpaca_live resolves the data feed outside the try block (a config
  error is fatal, not swallowed), raises on an unknown side, and keeps
  fill fields in `raw`.
- Shared helpers moved to execution/_live_common.py.
- run_alpaca_live_agent loads dashboard/.env like the app does.
- credentials/alpaca_live.json.example documented and un-ignored;
  conftest strips ALPACA_LIVE_* so a developer's shell can never arm a
  test run; CLAUDE.md documents the three frontend surfaces and the
  ALPACA_LIVE_* env contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKw312eMfu2bWaAsR4M8xb
…un index

- paths.py gains resolve_python_exe(venv_dir) and resolve_env_path(); the
  four copies of the Windows-vs-POSIX interpreter lookup and the relative
  DATABASE_PATH handling (backtests router, algo_service, database.py,
  ai_hedge_fund adapter) now go through them; the "venv vs system
  Python" startup log line still reports which one was actually chosen.
- leaderboard service: ensure_leaderboard_runs and get_leaderboard build
  _cached_run_index once instead of calling _find_cached_run per strategy
  (5 -> 19 entries made the per-request cost visible), and a single-flight
  lock keyed on (session, start, end) stops concurrent public GETs from
  recomputing the same window.
- leaderboard.js: a preset-less baseline entry styles as kind 'strategy'
  (dashed, thin) rather than 'team' -- dormant until the 14 new labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKw312eMfu2bWaAsR4M8xb
@FlyM1ss

FlyM1ss commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Review pass done; fixes pushed as three commits on this branch (b154da8d, 32e2c14e, 998ef916). Full backend suite green locally (3421 passed), CodeQL alert #1282 (py/unused-import) closed.

Strategies (b154da8d) — behaviour: turn_of_month bought SPY on the last day of every contest (last date flagged as month-end) and a zero open started a phantom hold; capm_alpha_ranking hardcoded 0.5 weight (half the book in cash with one qualifier) and its OLS variance was over the full window while the covariance used the symbol's valid days; pct_change() padded gaps into 0% days (capm, volatility_effect); bandtastic could never trade (50-day floor vs ~44-day window); trendrider's SMA-200 went permanently NaN after one gap day; rsi NaN with no down moves, adx NaN on a flat stretch; build_price_cache dropped any symbol missing the first timestamp for the whole run. Structure: hlhb/trendrider/supertrend_triple precompute indicators once per symbol (causal → bit-identical, pinned by test_indicators.py); daily_history maps dates once not 5×; dead lot_size removed; the 12 required_symbols + 14 num_trades copies moved into base.py. Tests: the multi-symbol smoke tests were vacuous (fixture SYM0..29 vs DJIA-30 default → run() returned [] before touching anything) — they now pass symbols and assert curve length; added a reference-window engine test.

Live broker / Mission Control (32e2c14e)/api/v1/mission-control/overview is now admin-gated (it exposes the real account and whether execution is armed); live snapshot errors no longer 500 the page; 30 s cache; page sends credentials + escapes output. alpaca_live_service runs the decision through the LLM validator before building orders; feed resolution errors are fatal instead of swallowed; ALPACA_LIVE_* stripped in conftest; alpaca_live.json.example + CLAUDE.md docs.

Backend (998ef916) — shared resolve_python_exe/resolve_env_path replace four copies of the Windows-venv / relative-DATABASE_PATH logic; leaderboard service builds _cached_run_index once per request (was _find_cached_run × 19 entries × 3 call sites) with a single-flight lock per window; leaderboard.js styles preset-less baseline entries as strategy, not team.

Not changed, flagged for follow-up

  • Empty weights {} = liquidate (sanctioned by the _signal_engine docstring); entry/exit strategies keep held instead — worth a deliberate decision.
  • trendrider's fixed ema(close, 50) is all-NaN below 50 rows, so the golden-cross entry / bearish-cross exit never fire on the leaderboard window (the other four signals still do). Same cap as bandtastic would fix it but breaks the precompute.
  • TEAM_COLOR_PALETTE has 10 colours for 19+ entries → collisions; presets for the new labels would need to avoid MODEL_COLOR_PALETTE.
  • Truthy-env parsing is duplicated across the two live services and the leaderboard flags — a shared helper.
  • User-facing docs now stale (not edited here): docs/source/lab/live_trading.rst intro, docs/source/lab/getting_started.rst (~L130–154), README "Go live" bullet.

@FlyM1ss

FlyM1ss commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Overall looks good. I also recommend to test 2 to 3 strategies first.

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.

4 participants