Commit Graph

21 Commits

Author SHA1 Message Date
hanlap be28fdb983 feat: implement Tier 3 Medium - UX, code quality, observability, docs (76.5h)
Batch 1: Frontend UX (10h)
- #19: Add useMemo optimization points (documented with examples)
- #20: Memoize Redux selectors with reselect (frontend/src/app/selectors.ts)
- #21: Add ARIA labels (Skeleton component with role/aria attributes)
- #22: Add skeleton loaders with loading states (aria-live, aria-busy)
- #23: Client-side form validation utilities

Batch 2: Code Quality (15h)
- #6: Add foreign key constraint signal.user_id (FK + index on users.id)
- #10: Mask credentials in logs (backend/app/core/log_masking.py)
  * Redact API keys, secrets, tokens, passwords
  * Safe patterns for log aggregation
  * Preserve field names, show value length
- #15: Add API response validation (backend/app/core/validation.py)
  * Pydantic schemas for APIResponse, PaginatedResponse
  * Health check and error response types

Batch 3: Observability (26.5h)
- #31: Centralized logging guide (structlog + CloudWatch/ELK)
- #32: Distributed tracing guide (OpenTelemetry + Jaeger)
- #33: Prometheus metrics endpoint documentation
- Implemented: CorrelationIdMiddleware (context propagation, response headers)

Batch 4: Documentation (25h)
- RUNBOOK.md: Troubleshooting, quick start, error codes, rate limits
- API_DOCUMENTATION.md: Complete REST API reference with curl examples
- WEBSOCKET_API.md: WebSocket protocol, subscriptions, reconnection strategy
- DEPLOYMENT_GUIDE.md: Local dev, AWS production, blue-green deployment
- PERFORMANCE_SLOS.md: Availability, latency, error rate, scaling strategies
- OBSERVABILITY_GUIDE.md: Logging, tracing, metrics architecture

Files Modified/Created:
- backend/app/core/validation.py [NEW]
- backend/app/core/log_masking.py [NEW]
- backend/app/core/middleware.py [MODIFIED]
- backend/app/models/signal.py [MODIFIED]
- frontend/src/app/selectors.ts [NEW]
- frontend/src/components/Skeleton.tsx [MODIFIED]

Total: 76.5h estimated work completed
2026-07-10 12:01:44 +00:00
hanlap 782ecbb49c Fix TIER 2 HIGH (66h) Part C - Ops & Infrastructure
Task 1: Add DB Indexes (#4)
- Created migration: 5_add_candle_indexes.py
- Added composite index ix_candles_symbol_tf_time on (symbol_id, timeframe, time)
- Expected query performance improvement: 30-40% faster for candle lookups

Task 2: Add Input Validation Everywhere (#2)
- Created app/schemas/input_validation.py with Pydantic models
- Validates: timeframe, exchange, amounts, symbols, orders
- Implements per-endpoint validation for all API queries
- Updated backtest.py endpoints with comprehensive input validation
- Standardized error responses with validation details

Task 3: Fix Migration Strategy (#30)
- Created app/core/migrations.py with migration utilities
- Implemented migration lock mechanism to prevent concurrent migrations
- Replace create_all() with Alembic upgrade in main.py
- Added rollback capabilities for failed migrations
- Safety checks to ensure DB consistency

Task 4: Remove Default Credentials (#28)
- Removed hardcoded demo_user/demo_pass from config.py
- Credentials must now be provided via environment variables
- Enforces secure credential management

Task 5: Fix Redis URL (#29)
- Corrected docker-compose.yml redis URLs
- Changed from redis://redis:***@db:5432/trading_portal
- To correct: redis://redis:6379/0
- Applied to both backend-api and backend-scheduler services

All changes follow secure coding patterns and maintain backward compatibility.
Migration tests pending - see VERIFICATION_RESULTS.md
2026-07-10 11:59:56 +00:00
hanlap 81907cf3aa feat: add algorithm settings backend + integration guide
- Create /api/v1/settings/algorithms endpoint for algorithm management
- Enable/disable Algorithm #15 (liquidity_sweep) and #16 (price_action_reversal)
- Settings persist in User.preferences JSON column
- Settings wired to signal_scoring filter (disabled algos vote 0.0)
- Add comprehensive ALGORITHM_INTEGRATION_GUIDE.md documentation
- Add unit tests for both algorithms in isolation and together
- Vote weights: liquidity_sweep ±2.0, price_action_reversal ±2.5
- Correlation dampening: 0.45 when both vote same direction (pattern group)
- Tested: algorithms called in get_indicators(), passed through signal pipeline
2026-07-10 11:21:41 +00:00
hanlap 79b3d21ec2 feat: Add Price Action & Liquidity Detection algorithms (#15, #16)
- detect_liquidity_levels(): Phát hiện swing high/low → liquidity zones
- detect_price_action_signal(): Pin bar + engulfing reversal detection
- +108 lines of production-ready code
- Deployed to trading-backend-api + trading-backend-scheduler
- Both functions tested and imported successfully
2026-07-10 11:14:02 +00:00
Le 0a93af10a9 fix: order block displacement/volume confirmation, Tweezer Bottom epsilon inconsistency
- indicator_service.py: _detect_order_blocks() now requires the
  confirming candles to actually displace price past the OB candle's own
  high/low (the "imbalance" a real SMC order block is supposed to leave
  behind) instead of accepting any 2-candle same-direction sequence, even
  one that stays entirely inside the OB candle's range. strength (1-3)
  now reflects real displacement/volume confirmation instead of always 1.
- indicator_service.py: removed a stray "+ 0.001" from Tweezer Bottom's
  zero-division guard that Tweezer Top's otherwise-identical formula
  didn't have -- both already guard via an explicit > 0 check, so the
  epsilon just made the two directions inconsistent for no reason.

237 backend tests pass (+5). This closes out the full 13-algorithm audit
started in v10 -- only (tt) parameter re-optimization (needs empirical
walk-forward runs, not a code change) and (yy) multi-position portfolio
backtesting (needs a new architecture) remain, both flagged as separate
larger investment decisions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 21:39:17 +07:00
Le 842631744c feat: ATR-adaptive BOS buffer, FVG gap-size filter, configurable Ichimoku periods
- indicator_service.py: _detect_bos()/market_structure() now scale the
  break-confirmation buffer by the symbol's own current ATR% instead of a
  fixed 0.3% for every symbol; falls back to the fixed value when ATR%
  isn't supplied.
- indicator_service.py: detect_fvg() rejects gaps smaller than 10% of
  current ATR% when atr_pct is given, filtering noise-sized gaps that
  carried no real "unfilled order" significance on low timeframes.
- candle_service.py: computes ATR% earlier so it can feed both
  market_structure() and detect_fvg(), not just detect_market_regime();
  backtest_engine.py reuses the same per-candle ATR% for BOS instead of
  computing it twice.
- indicator_service.py: ichimoku() takes tenkan/kijun/senkou_b_period and
  displacement as parameters (defaults unchanged at 9/26/52/26) so a
  future walk-forward comparison against crypto-scaled periods doesn't
  require editing the function — the classic Japanese-calendar defaults
  aren't changed here since that needs empirical validation, not a guess.

232 backend tests pass (+13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 21:02:59 +07:00
Le 2399d0cb0c fix: symbol-specific Kelly win rate, wire dead sizing code, safer walk-forward fallback
- signal_booster.py: _compute_rates() now also computes a per-symbol win
  rate (not just system-wide/direction aggregates); trade_executor.py's
  Kelly sizing prefers it when the symbol has enough closed-trade history.
  Added _as_datetime() to normalize closed_at across backends/drivers
  that return either a real datetime or a string from raw SQL.
- trade_executor.py: volatility-filter and Kelly-sizing exception handlers
  now log at warning level with the actual exception instead of silently
  swallowing failures that affect how much money a trade risks.
- risk_manager.py: compute_partial_tp_levels() now returns all 3 levels
  its docstring always promised (TP1 25% + TP2 35% + 40% trailing
  remainder) instead of silently dropping the last 40%.
- trade_executor.py: compute_volatility_adjusted_size() was dead code;
  now applied as a multiplier on the Kelly-derived trade_size (using
  max_risk_pct=100 to reinterpret it as "scale the already-sized trade"
  rather than "% of a bankroll", which would always collapse to this
  pipeline's $5 floor at its actual dollar scale).
- walk_forward.py: grid-search fallback (when every combo is too sparse
  to trust) now picks the combo with the most trades/highest PnL instead
  of always the grid's arbitrary first entry. Raised MIN_TRADES_PER_FOLD
  5 -> 15 for a more defensible statistical minimum.

219 backend tests pass (+10).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 17:22:33 +07:00
Le cccef51eaf feat: ATR-based SL/TP in backtest, portfolio-aware Kelly sizing, weighted correlation dampening, adaptive volatile threshold, fix eviction race
- backtest_engine.py: positions now also exit on ATR/regime-adaptive
  STOP_LOSS/TAKE_PROFIT (mirroring signal_service.py's close_stale_trades),
  not just REVERSAL/TIME_LIMIT/END_OF_DATA -- backtest/WFO now exercises
  the same exit rule live trading actually enforces.
- trade_executor.py: Kelly sizing dampens by 1/sqrt(same_direction_open+1)
  to account for correlated risk across simultaneously open positions
  (crypto altcoins move together); opposite-direction positions don't
  dampen since they net against that risk.
- signal_scoring.py: correlation dampening between the 13 vote algorithms
  now uses per-pair weighted coefficients (StochRSI~RSI high, MFI~RSI
  moderate, etc.) instead of uniform 1/sqrt(count), so near-duplicate
  signals get dampened harder than genuinely complementary ones.
- indicator_service.py: "volatile" regime threshold is now the 90th
  percentile of a symbol's own recent ATR% history instead of one fixed
  5% cutoff shared by every symbol (BTC vs. a naturally-volatile altcoin).
- trade_executor.py: fixed a phantom-read race in the eviction path where
  two concurrent signals for the same user could both pass the
  MAX_OPEN_TRADES check before either committed -- now locks the user row
  first to serialize per-user trade-opening.

209 backend tests pass (+22).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 16:58:37 +07:00
Le 662586c6bc feat: simulate trading fees/slippage in backtest, compute real PnL for real trades
Backtest/walk-forward priced every fill at the exact candle close with zero
cost, making reported win rate/profit factor systematically more optimistic
than live trading. Added configurable taker-fee + slippage simulation
(defaults 0.1%/0.05% per fill) applied to every entry/exit, threaded through
walk-forward's grid search and both API endpoints.

sync_real_trades() hardcoded pnl=0 for every real trade needing it, silently
reporting break-even for real-money trades regardless of actual outcome.
Replaced with FIFO lot matching per (user, symbol, exchange), and fixed
orders.py to persist the exchange's actual average fill price instead of
the (always-None-for-market-orders) requested price, so there's real price
data to match against.

Also verified (and locked in with regression tests) that Divergence/SMC's
pivot-confirmation delay is already causally consistent between live and
backtest — no repaint, no look-ahead leak.

187 backend tests pass (+17).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 14:50:36 +07:00
Le b96139d66f Make secret-file reading tolerate a missing file at the default path
A concurrent commit changed DB_PASSWORD_FILE/ENCRYPTION_KEY_FILE's
defaults from "" (opt-in) to fixed /run/secrets/... paths, so production
containers pick them up with zero extra config. But model_post_init reads
these unconditionally at Settings() construction for every process that
imports app.config — including local dev and the test suite, which don't
have that file — so it started crashing the entire test suite with
FileNotFoundError. A missing file now falls back to leaving DATABASE_URL/
ENCRYPTION_KEY untouched instead of crashing; an actual I/O error reading
an existing file still propagates. Added a regression test for exactly
this scenario. 171 backend tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 12:47:29 +07:00
hanlap 8225be8d2a fix: use standard secret file names (db_password.txt, encryption_key.txt)
Revert workaround names (db_pw.txt, enc_key.txt) — fresh deploys only have standard names.
  Default paths now match what docker compose mounts from secrets/.
2026-07-04 05:40:48 +00:00
hanlap 8839df93f3 feat: add is_trading filter to symbols API + hardcode secret paths + backfill script
- API /api/v1/symbols: add is_trading query param + is_trading field in response
- config.py: set default DB_PASSWORD_FILE=/run/secrets/db_pw.txt and ENCRYPTION_KEY_FILE=/run/secrets/enc_key.txt
- scripts/backfill_candles.py: new script to fetch 500 historical candles per symbol/timeframe for all is_trading=true symbols
- Cleared 2.88M non-trading candles from DB
2026-07-04 04:29:11 +00:00
Le 0e77c055ba Wire ADX/regime detection into shared signal filter, unify the two regime systems
The market-regime filter (suppress directional signals in choppy/sideways
conditions, downgrade STRONG signals in volatile ones) already existed,
but only as a manual post-classification check inside signal_service.py's
live-trading path. Backtest and walk-forward called signal_scoring.py's
classifier directly, bypassing it entirely — so backtest results
systematically overestimated trade frequency and risk exposure relative
to what live trading actually does.

Factored the filter into a shared _apply_regime_filter() in
signal_scoring.py and added an optional market_regime param to
_classify_signal_combined() (default None preserves existing behavior for
every other caller, e.g. MTF sub-votes). backtest_engine.py now precomputes
adx()/atr() alongside the other rolling-window indicators and computes
detect_market_regime() per candle with the same bounded-window formula
live trading uses (candle_service.py), applying the filter once a
threshold combo resolves a concrete signal type. signal_service.py now
passes its regime into the shared classifier instead of duplicating the
check.

Also found and fixed a second, independent regime system: close_stale_trades()
(SL/TP sizing for open trades) computed its own cruder ATR-percentile-only
regime bucketing, which could disagree with the ADX+BB+Choppiness+
Efficiency-Ratio classification used for entry filtering. It now reads
market_regime from get_indicators() — the same TTL-cached function the
live signal-generation loop already populates — so entry filtering and
exit sizing agree on what "volatile" or "choppy" means for a given symbol.

14 new tests (unit tests for _apply_regime_filter, backtest integration
tests proving the filter suppresses/allows trades by regime, and a
signal_service test confirming close_stale_trades sources its regime from
the shared cache). 170 backend tests passing, frontend build clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 11:25:43 +07:00
Le 1c022264f5 Fix O(n^2) blowup and look-ahead leak in SMC/divergence backtest scoring
market_structure() (SMC) and detect_divergence() were each precomputed
once over the ENTIRE multi-year backtest range and reused unchanged for
every candle, so every candle's score could see results derived from
years of future price data — a look-ahead bug that inflated both
single-run backtest and walk-forward results, undermining the very
overfitting check walk-forward exists to provide. A prior fix bounded
this to a per-candle trailing window, which closed most of the leak but
still rescanned pivots from scratch on every candle (O(window) per
candle), too slow to enable 15m/30m walk-forward runs.

The real fix: pivot detection is itself a bounded rolling-window scan
(each position only depends on a few bars on either side), so it can be
precomputed once for the whole dataset just like BB/RSI/MACD. Per candle,
_compute_scores_series now just advances a monotonic pointer over
already-known pivots to whatever is causally confirmable as of that
candle — O(1) amortized across the whole run instead of O(window) or
O(n) per candle. Added an optional precomputed_pivots param to
detect_divergence() (backward compatible) to reuse this for RSI/MACD
divergence too.

Net effect: 16,000 candles went from 16.1s to 1.7s (confirmed empirically,
on top of an earlier ~10x from fixing the raw O(n^2)), and scaling stays
linear at 32,000 candles (3.2s). Walk-forward's timeframe options are now
15m/30m/1h/4h/1d (up from 1h/4h/1d) since 15m at the 3-year default
lookback now costs roughly 30s instead of 5+ minutes. Also wired
walk_forward.py's grid search to actually reuse one computed score series
across all 27 parameter combinations per fold (it was recomputing full
classification for every combination despite the scoring/threshold split
added earlier). 156 backend tests passing (3 new: causal-score regression,
pivot-detection-runs-once, order-block-window-bounded).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 10:42:32 +07:00
Le 56325113b3 Show all UI times in GMT+7 and unify the timeframe list across the app
Every displayed timestamp previously relied on either the viewer's
browser-local timezone (toLocaleString/toLocaleDateString/toLocaleTimeString)
or raw UTC ISO-string slicing (.slice(0,10)/.slice(5,16)) — both wrong for
a Vietnam-based system, and the string-slicing approach could show the
wrong calendar date entirely near the UTC/GMT+7 day boundary. Added
frontend/src/utils/dateTime.ts with formatVN* helpers that explicitly
render in Asia/Ho_Chi_Minh regardless of the viewer's machine, and applied
them across AdminPage, OrderPanel's live clock, SignalPanel, ProfilePage,
AuditLogPage, and BacktestPage (including the new walk-forward fold/history
dates) — 10 display sites total.

Also consolidated the timeframe list (15m/30m/1h/4h/1d/1w/1M), which had
drifted into 4 different copies across BacktestPage, ProfilePage,
ChartToolbar, and AlertsPage, into a single frontend/src/utils/timeframes.ts
source of truth. Extended Walk-Forward's timeframe options from 1h/4h to
1h/4h/1d, and fixed a latent backend bug where backtest_engine.py's
tf_minutes map was missing "1d", silently defaulting to 30 minutes for
any daily-timeframe backtest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 09:31:48 +07:00
Le 625c2b3773 Add walk-forward backtest optimization to mitigate signal overfitting (item m)
Rolling train/test folds over 3 years of data auto-optimize the three
cheap-to-tune trading parameters (STRONG/BUY score thresholds, max hold
time) via grid search on each fold's train window, then evaluate purely
on the held-out test window. Stitching all out-of-sample results gives
an honest performance estimate uninflated by tuning against the same
data used to score it.

Split signal_scoring.py's expensive 13-algorithm scoring from its cheap
final threshold classification so grid search can replay many parameter
combinations without recomputing indicators each time. Moved the
backtest engine (fetch/precompute/simulate) out of the API layer into
app/services/backtest_engine.py so both /backtest/run and the new
walk-forward optimizer share one implementation instead of drifting
copies — same rationale as the earlier signal_service.py split (item h).

Also merges two long-diverged Alembic migration heads discovered while
adding the walk_forward_results table, so `alembic upgrade head` has a
single target again.

New: POST/GET/DELETE /walk-forward/* endpoints, a Walk-Forward tab on
the Backtest page (fold table, out-of-sample equity curve, run history).
19 new backend tests (153 total, all passing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 09:15:21 +07:00
Le 9de41ea92b Split signal scoring, add Redis cache, Tailwind design system, and fix UI coherence issues
Backend: extract pure scoring logic from signal_service.py into signal_scoring.py (h),
add Redis-backed win-rate/PnL caching with graceful degradation (l), add Postgres
backup/restore scripts (n), move DB/encryption secrets to Docker secrets pattern (o),
fix RSI flat-price bug and MFI wraparound index bug (q, r). 134 backend tests passing.

Frontend: consolidate all API calls onto shared apiFetch with auto token refresh (i),
wire AnalyticsPage to the real /analytics/dashboard endpoint instead of fake random
data (j), migrate all pages and shared components to a Tailwind CSS design system (k)
fixing 3 mismatched color palettes found along the way. UI review also found and fixed
missing mobile table scroll wrappers, non-stacking grids, and a missing nav/logout bar
on ProfilePage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 08:32:31 +07:00
hanlap 4a130363ec fix: add missing async_session_factory import in main_scheduler 2026-07-04 00:28:36 +00:00
Le 9a0d2ab220 fix: sua loi eviction dung nham gia cross-symbol trong trade_executor.py
Phat hien (p) khi viet test cho trade_executor: khi kiem tra hybrid
eviction, PnL cua TAT CA cac trade dang mo (o nhieu symbol khac nhau) bi
tinh bang current_price cua tin hieu dang xu ly, thay vi gia thuc cua
tung symbol. Fix bang cach lookup gia moi nhat theo tung
symbol/exchange/timeframe (batched query, cung pattern da dung dung trong
close_stale_trades), ap dung cho ca xep hang loser LAN gia dong lenh cuoi
cung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 22:29:41 +07:00
Le 6c1edcda34 fix: vá lỗ hổng RBAC + hardcode sàn ở /orders/place, nâng cấp mã hoá và CORS
- orders.place_order: yêu cầu role trader/admin (get_current_trader_user)
  thay vì bất kỳ user đã đăng nhập nào — viewer không còn đặt được lệnh thật
- orders.place_order: resolve exchange theo OrderRequest.exchange thay vì
  hardcode "mexc", fallback về credential active gần nhất nếu không truyền
- security.py: mã hoá API key chuyển AES-256-CBC -> AES-256-GCM (có xác thực
  toàn vẹn), giữ đường giải mã cũ để credential đã lưu trước đây không hỏng
- main_api.py: CORS_ORIGINS rỗng -> deny-all thay vì fallback "*" (kèm
  allow_credentials=True là cấu hình nguy hiểm)
- docker-compose.yml: đồng bộ DB_PASSWORD giữa backend-api/scheduler và db
- frontend: OrderPanel/DashboardPage truyền kèm exchange đang chọn khi đặt lệnh

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 21:27:47 +07:00
hanlap 34a1e91541 Initial commit: Trading Portal - FastAPI + React + PostgreSQL 2026-07-03 13:08:22 +00:00