Replaces the build-time VITE_BACKTEST_ENABLED gate with a fully runtime
flow: a global Cosmos-backed default (already shipped in the existing
dynamicConfig system) plus a new per-user override layer. An admin can
now enable backtest for specific users without flipping the global
switch — useful for staged rollout and beta testers.
Resolution order: per-user override > global config > env fallback.
Both /api/feature-flags (FE display) and /api/backtest/run (server
guard) consult the same merge logic.
Backend (backend/src/...):
~ services/profileRepository.ts
+ TradingUserFeatureFlags interface
+ featureFlags?: TradingUserFeatureFlags on TradingUserProfile
+ setUserFeatureFlags(userId, { backtestEnabled, ... })
~ saveCurrentUserProfile() — strip role + featureFlags from input
so non-admins can't elevate via PATCH /api/me/profile
~ mergeTradingUserProfiles() — preserves explicit flag values only
~ services/apiServer.ts
~ /api/feature-flags merges per-user override into the response
+ /api/admin/users/:userId/feature-flags (GET — overrides + effective)
+ /api/admin/users/:userId/feature-flags (PATCH — admin-only writer)
~ /api/backtest/run resolves effective flags before guarding
~ backtest/index.ts
+ RunBacktestOptions.skipGlobalFeatureFlagCheck
~ runBacktest() honors the override (route already gated stricter)
Frontend (web/src/...):
~ backtest/flags.ts — isBacktestBuildEnabled() now returns true.
Kept as a no-op function so existing callers don't break.
+ lib/userFeatureFlagsApi.ts — typed admin client
+ components/admin/UserFeatureFlagsPanel.tsx
Tri-state picker per flag (Default / On / Off), Look up by user id,
Save/Reset, shows the merged "effective" value.
~ tabs/ConfigTab.tsx — mounts <UserFeatureFlagsPanel /> below the
existing global Backtest Access Control section.
~ layout-fixes.css §27 — styles for the per-user panel.
Tests:
+ testBacktestEngine: skipGlobalFeatureFlagCheck enables per-user
override semantics. 12/12 regression checks pass.
Security note: featureFlags + role are explicitly stripped from
saveCurrentUserProfile input. Only the admin-only PATCH endpoint can
set per-user overrides.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds BacktestAlpacaSource so saved trade plans for US equities can be
backtested without manual CSV upload. Mirrors the existing Kraken
loader pattern.
Backend:
+ backend/src/backtest/data/alpacaLoader.ts
loadDatasetFromAlpaca({ symbols, fromTs, toTs, feed, adjustment })
- Uses the existing @alpacahq/alpaca-trade-api SDK
- Fetches 15Min bars; normalize.ts aggregates 1h/4h
- 50-day warm-up lookback so ProEngine has enough EMA/RSI history
- Throws cleanly with config guidance if ALPACA_API_KEY missing
- In-memory cache keyed by (symbol, window, feed, adjustment)
~ backend/src/backtest/types.ts
+ BacktestAlpacaSource interface
+ 'alpaca' added to BacktestDataSource and BacktestDataSourceType
~ backend/src/backtest/data/loadHistoricalData.ts
Wires 'alpaca' source into the dispatcher
Frontend:
~ web/src/backtest/types.ts — adds 'alpaca' to BacktestDataSourceType
~ web/src/backtest/components/BacktestConfigurator.tsx
+ 'alpaca' as a SourceType option
+ AUTO_FETCH_SOURCES list — kraken AND alpaca skip the upload-required
validation
+ 'Alpaca (US equities)' option in the source-picker dropdown
+ Source-picker change handler seeds default IEX/raw Alpaca payload
Tests:
+ testBacktestEngine.ts: new "alpaca data source dispatcher" assertion
Verifies the type discriminator + error message without hitting
the network. 11/11 regression checks pass.
Caveats (documented in alpacaLoader inline + ENGINE_READINESS.md §3.4):
- Free IEX feed has limited symbol coverage (~2016+)
- SIP feed (paid) needed for full pre-2017 + full-market historical
- The loader graceful-fails when credentials aren't configured
- Existing Alpaca live-trading connector unchanged — backtest uses
its own SDK instance with a different fetch path
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds testBacktestEngine.ts — the minimum viable test set called out in
docs/backtest/ENGINE_READINESS.md §3.1. Codifies behaviors verified
during the readiness audit so they don't silently regress.
Coverage (10 assertions, all passing):
Unit (testable building blocks):
- aggregateCandles 15m→1h: OHLC preserved, volume summed
- aggregateCandles 15m→4h: OHLC preserved, volume summed
- computeSummary: hand-computed PnL/winRate/drawdown match
- computeSummary: empty inputs → all zeros (no NaN/Infinity)
- computeSharpe: single-point timeline → 0 (no divide-by-zero)
- withLogLevel: level restored after success AND after throw
Integration (full runBacktest):
- Determinism: same input → byte-identical JSON output
- Flat-price tape → 0 trades, 0 PnL, 0 drawdown
- Result shape contract: all documented top-level keys present
- Empty candle dataset throws explicitly (no silent 0-trade result)
Conforms to the existing testXxx.ts convention used by the other
check:backtest-* scripts. Wired into package.json:
- "check:backtest-engine": "node --import tsx testBacktestEngine.ts"
- chained into the top-level "test" script
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Backtest runs were emitting ~25k log lines per 5k-candle backtest at
default 'info' level (~5 logs per candle from rule evaluations). At
year-scale that's 80k+ lines per run — operationally disruptive and
likely the reason the production gate was set conservatively.
Changes:
- utils/logger.ts: respect LOG_LEVEL env override (no behavior change
when unset). Add `withLogLevel(level, fn)` helper that swaps the
logger level for the duration of a function and always restores it
via finally — safe across throws.
- backtest/index.ts: wrap runBacktestReplay() in withLogLevel('warn').
New `logLevel?: string` option on RunBacktestOptions lets callers
override (e.g. 'info' or 'debug' for engine diagnosis).
Verified:
- 2,000-candle run: 25,000 → 3 log lines at default
- 500-candle run with logLevel='info': 3,202 lines (verbose still works)
- Logger.level correctly restored after both successful runs and
failed runs that throw (assertBacktestMode rejection test)
- No regression: logger initial level honors LOG_LEVEL env or 'info'
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds web/src/layout-fixes.css imported once from main.tsx — surgical
overrides applied with !important so they win against the existing
3000+ line index.css without rewriting it.
Issues fixed:
1. Modals/popovers/dropdowns clipped by .dashboard-main { overflow: hidden }
→ now overflow: visible. Stacking context guard for [role=dialog].
2. Right panel (308px fixed) covers main content on laptop/tablet
→ 260px below 1280px, hidden below 1024px.
3. Tables extending off-screen
→ .dashboard-content table wrapped with display:block + overflow-x:auto
so they scroll inside their column. Also exposes .scroll-x utility.
4. Header search/indices push each other off-screen
→ flex-wrap on .trading-header, search shrinks to 240–360px range,
indices wrap with smaller column gap on narrow.
5. Long unbreakable strings (commit SHAs, URLs) escaping containers
→ overflow-wrap: anywhere + word-break: break-word + pre-wrap on <pre>.
6. Sidebar 76px doesn't collapse on mobile
→ 56px below 768px with reduced content padding.
Bumps @bytelyst/devops to ^0.1.3 (responsive panel) in backend + web.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Backend:
- /api/devops/info now requires admin role (was: any authenticated user).
Exposes env keys, dep checks, and socket counts — admin-only by design.
- New /api/devops/version (public, no auth) returns build SHA/branch/image
for ops/CI rollback verification.
- Dep checks: live ping for Cosmos (trading_users) and platform-service.
- Service version read dynamically via readServiceVersion(import.meta.url)
— no more hardcoded '0.1.0'.
- extra: socketIoConnections + tradingApiUrl for runtime debugging.
- saveCurrentUserProfile no longer accepts client-supplied role —
prevents drift with platform JWT (which is authoritative).
Web:
- DevOps tab is now admin-only (gated behind isAdmin like Bot Config and
Admin Panel). Both the section list and content render are guarded.
- Service version baked into bundle via Vite `define` (__WEB_SERVICE_VERSION__)
read from web/package.json — no more hardcoded VERSION constant.
- Bumps @bytelyst/devops dep to ^0.1.2.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Backend:
- Fix role drift: /api/me/profile now returns JWT role authoritatively (was reading
drifting role from trading_users). PATCH strips client-supplied role.
- Add /api/devops/info endpoint backed by @bytelyst/devops/server.
- Dockerfile: bake BYTELYST_COMMIT_SHA / BYTELYST_BUILT_AT / etc. as build args.
Web:
- Migrate from vendor/ + .pnpmfile.cjs to Gitea npm registry (consistency with backend).
- Replace file: refs in web/package.json with semver ranges resolved from Gitea.
- Drop vendor/bytelyst/* tree and .pnpmfile.cjs.
- Add DevOpsTab in Settings using @bytelyst/devops/ui (tabbed: Build/Runtime/Config/Deps/Raw).
- Vite alias: restrict @bytelyst/* catch-all to single-segment names so subpath
imports (@bytelyst/devops/ui) resolve via package exports map.
- Bake BYTELYST_* metadata into the bundle as VITE_BYTELYST_* env.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The base image approach was reverted for notes and clock due to workspace
complexity. The trading repo was still using the base image approach
which is causing build failures. Reverting to the vendor approach
which works reliably for the monorepo structure.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Update Dockerfiles to use bytelyst-common-base-backend and bytelyst-common-base-web
images instead of installing @bytelyst/* packages via vendor directory.
Benefits:
- Smaller final images (~50MB vs ~250MB)
- Faster builds (base image cached)
- Consistent package versions across products
- No need for vendor/ directory maintenance
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Remove the silent shared demo-key fallback for FMP-backed research and screener routes, document the required key, and make backend/.env.example trackable so setup guidance has one source of truth.
Refs: docs/AUDIT_REDESIGN.md item C6.
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Normalize and limit /api/news symbols before proxying to Alpaca so only bounded, expected symbol characters reach the upstream news endpoint.
Refs: docs/AUDIT_REDESIGN.md item C4.
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Reject unsupported /api/screener sector values before building the FMP query so only known sector labels reach the upstream stock screener.
Refs: docs/AUDIT_REDESIGN.md item C3.
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Reject inline JavaScript strategy payloads before backtest execution, both at the API boundary and inside runBacktest, so saved profiles and direct internal calls cannot route unsandboxed code into replay handling.
Refs: docs/AUDIT_REDESIGN.md item C1.
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Add static contract coverage for the dashboard market data and research proxy routes so auth, upstream URL construction, response normalization, and FMP cache usage stay guarded by the backend test gate.
Refs: docs/AUDIT_REDESIGN.md item F6.
Co-Authored-By: GPT-5 Codex <noreply@openai.com>