# Implementation Plan: UX Improvements ## Overview Implement four UX improvements: balanced S/R zone selection in the backend, visible levels filtering (backend + frontend), Trade Scanner explainer banner and R:R analysis columns, and Rankings weight slider form. Backend changes use Python (FastAPI/Pydantic), frontend changes use TypeScript (React). ## Tasks - [x] 1. Implement balanced S/R zone selection in `cluster_sr_zones()` - [x] 1.1 Refactor `cluster_sr_zones()` to use interleave-based balanced selection - In `app/services/sr_service.py`, after clustering and computing zones, split zones into `support_zones` and `resistance_zones` pools sorted by strength descending - Implement round-robin interleave picking: alternate strongest from each pool until `max_zones` is reached or both pools exhausted - If one pool is exhausted, fill remaining slots from the other pool - Sort final selected zones by strength descending before returning - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_ - [ ]* 1.2 Write property test: balanced zone selection guarantees both types - **Property 1: Balanced zone selection guarantees both types** - **Validates: Requirement 1.1** - In `tests/unit/test_cluster_sr_zones.py`, use Hypothesis to generate sets of levels with at least one support and one resistance zone, verify output contains at least one of each type when `max_zones >= 2` - [ ]* 1.3 Write property test: interleave selection correctness - **Property 2: Interleave selection correctness** - **Validates: Requirements 1.2, 1.3** - Verify the selected zones match the expected round-robin interleave from support and resistance pools - [ ]* 1.4 Write property test: zone output sorted by strength - **Property 3: Zone output is sorted by strength** - **Validates: Requirement 1.4** - For any input, verify returned zones are sorted by strength descending - [x] 1.5 Update existing unit tests for balanced selection behavior - Update `tests/unit/test_cluster_sr_zones.py` to add tests for: mixed support/resistance input produces balanced output, all-support input fills from support only, all-resistance input fills from resistance only, single zone edge case - _Requirements: 1.1, 1.2, 1.3, 1.5, 1.6_ - [x] 2. Implement `visible_levels` filtering in backend - [x] 2.1 Add `visible_levels` field to `SRLevelResponse` schema - In `app/schemas/sr_level.py`, add `visible_levels: list[SRLevelResult] = []` to `SRLevelResponse` - _Requirements: 2.1, 2.3_ - [x] 2.2 Compute `visible_levels` in the SR levels router - In `app/routers/sr_levels.py`, after computing zones, filter `level_results` to only those whose `price_level` falls within the `[low, high]` range of at least one zone - Set the filtered list as `visible_levels` on the `SRLevelResponse` - When zones list is empty, `visible_levels` should be empty - _Requirements: 2.1, 2.2, 2.4_ - [ ]* 2.3 Write property test: visible levels subset within zone bounds - **Property 4: Visible levels are a subset within zone bounds** - **Validates: Requirements 2.1, 2.2** - Verify every entry in `visible_levels` appears in `levels` and has a price within at least one zone's `[low, high]` range - [x] 2.4 Update router unit tests for `visible_levels` - In `tests/unit/test_sr_levels_router.py`, add tests verifying: `visible_levels` is present in response, `visible_levels` contains only levels within zone bounds, `visible_levels` is empty when zones are empty - _Requirements: 2.1, 2.2, 2.4_ - [x] 3. Checkpoint - Ensure all backend tests pass - Ensure all tests pass, ask the user if questions arise. - [x] 4. Update frontend types and S/R levels table filtering - [x] 4.1 Add `visible_levels` to frontend `SRLevelResponse` type - In `frontend/src/lib/types.ts`, add `visible_levels: SRLevel[]` to the `SRLevelResponse` interface - _Requirements: 2.1_ - [x] 4.2 Update `TickerDetailPage` to use `visible_levels` for the S/R table - In `frontend/src/pages/TickerDetailPage.tsx`, change `sortedLevels` to derive from `srLevels.data.visible_levels` instead of `srLevels.data.levels` - Keep sorting by strength descending - Hide the S/R Levels Table section when `visible_levels` is empty - Maintain existing color coding (green for support, red for resistance) - _Requirements: 3.1, 3.2, 3.3, 3.4_ - [x] 5. Add Trade Scanner explainer banner and R:R analysis columns - [x] 5.1 Add explainer banner to `ScannerPage` - In `frontend/src/pages/ScannerPage.tsx`, add a static informational banner above the filter controls - Banner text: describe that the scanner identifies asymmetric risk-reward trade setups using S/R levels as targets and ATR-based stops - Banner should be visible on initial page load without user interaction - _Requirements: 4.1, 4.2, 4.3_ - [x] 5.2 Add R:R analysis columns to `TradeTable` - In `frontend/src/components/scanner/TradeTable.tsx`, add computed columns: Risk $ (`|entry_price - stop_loss|`), Reward $ (`|target - entry_price|`), % to Stop (`risk / entry * 100`), % to Target (`reward / entry * 100`) - Color-code the existing R:R ratio column: green for ≥ 3.0, amber for ≥ 2.0, red for < 2.0 - Update the `SortColumn` type and `columns` array to include the new columns - Update `sortTrades` in `ScannerPage.tsx` to handle sorting by new computed columns - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8_ - [ ]* 5.3 Write property test: trade analysis computation correctness - **Property 5: Trade analysis computation correctness** - **Validates: Requirements 5.2, 5.3, 5.4, 5.5** - Using fast-check, for any trade with positive entry_price, stop_loss, and target, verify `risk_amount == |entry_price - stop_loss|`, `reward_amount == |target - entry_price|`, `stop_pct == risk_amount / entry_price * 100`, `target_pct == reward_amount / entry_price * 100` - [x] 6. Convert Rankings weight inputs to sliders - [x] 6.1 Replace number inputs with range sliders in `WeightsForm` - In `frontend/src/components/rankings/WeightsForm.tsx`, replace `` with `` - On mount, convert API decimal weights to 0–100 scale: `Math.round(w * 100)` - Display current whole-number value next to each slider - Show humanized label (replace underscores with spaces) - _Requirements: 6.1, 6.2, 6.3, 6.4_ - [x] 6.2 Implement weight normalization on submit - On submit, normalize slider values: divide each by the sum of all values - Disable submit button when all sliders are zero - Show validation message "At least one weight must be greater than zero" when all are zero - Send normalized decimal weights via existing `useUpdateWeights` mutation - _Requirements: 7.1, 7.2, 7.3, 7.4_ - [ ]* 6.3 Write property test: weight conversion round-trip - **Property 6: Weight conversion round-trip** - **Validates: Requirement 6.3** - Using fast-check, verify that converting decimal weights to slider scale and normalizing back preserves relative proportions within floating-point tolerance - [ ]* 6.4 Write property test: normalized weights sum to one - **Property 7: Normalized weights sum to one** - **Validates: Requirement 7.1** - Using fast-check, for any set of slider values (integers 0–100) where at least one > 0, verify normalized weights sum to 1.0 within ±1e-9 - [x] 7. Final checkpoint - Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. ## Notes - Tasks marked with `*` are optional and can be skipped for faster MVP - Backend uses Python (Hypothesis for property tests), frontend uses TypeScript/React (fast-check for property tests) - Each task references specific requirements for traceability - Checkpoints ensure incremental validation after backend and full implementation phases - Property tests validate universal correctness properties from the design document