Files
stonks-oracle/.kiro/specs/override-trade-tab/tasks.md
T
Celes Renata 913fe8b0b3 feat: override trade tab — manual order entry with auto-registration
Backend:
- OverrideOrderRequest/Response Pydantic models with ticker, quantity, price validators
- POST /api/trading/override/order endpoint (enqueue to Redis broker queue)
- auto_register_symbol() module for untracked ticker registration via Symbol Registry
- Unit tests (17) and property-based tests (3 x 100 examples)

Frontend:
- OverrideTradePanel component (order form + positions display)
- Override tab in TradingEngine page with URL search param navigation
- Override Trade button on Trading Controls page
- useSubmitOverrideOrder mutation hook
- MSW handler and 13 component/integration tests

Steering:
- Updated steering docs for Ubuntu dev machine with nvm/Node 24
2026-04-17 07:02:30 +00:00

154 lines
10 KiB
Markdown

# Implementation Plan: Override Trade Tab
## Overview
Add a manual order entry interface to the Stonks Oracle trading engine. The implementation spans backend (FastAPI endpoint + auto-registration module), frontend (Override tab with order form and positions display, navigation button, API hook), MSW test handlers, property-based tests for the 3 correctness properties, and frontend component tests. Each task builds incrementally, wiring components together as they are created.
## Tasks
- [x] 1. Backend: Override order validation model and endpoint
- [x] 1.1 Add `OverrideOrderRequest` and `OverrideOrderResponse` Pydantic models to `services/trading/app.py`
- `OverrideOrderRequest` with fields: `ticker` (str), `side` (Literal["buy","sell"]), `quantity` (float > 0), `order_type` (Literal["market","limit","stop","stop_limit"], default "market"), `limit_price` (Optional[float]), `stop_price` (Optional[float])
- Add a `@field_validator("ticker")` that uppercases and validates against `^[A-Z]{1,10}$`
- Add a `@model_validator(mode="after")` that enforces `limit_price` required when order_type is "limit" or "stop_limit", and `stop_price` required when order_type is "stop" or "stop_limit"
- `OverrideOrderResponse` with fields: `job_id` (str), `status` (str), `ticker` (str), `side` (str), `quantity` (float), `auto_registered` (bool)
- _Requirements: 2.1, 2.2, 3.1, 3.5_
- [x] 1.2 Add `POST /api/trading/override/order` endpoint to `services/trading/app.py`
- Accept `OverrideOrderRequest` body
- Call `auto_register_symbol()` from `services/trading/override.py` if ticker is untracked (check via Symbol Registry `GET /companies?ticker=...`)
- Generate idempotency key as `"override-{uuid4()}"`
- Build job payload with `ticker`, `side`, `quantity`, `order_type`, `limit_price`, `stop_price`, `source: "manual_override"`, and `idempotency_key`
- Enqueue job to Redis `stonks:queue:broker` via `RPUSH`
- Return 202 with `OverrideOrderResponse`
- Return 503 if Redis is unreachable
- Use `engine.redis` for Redis access and `config` for registry base URL
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 9.1_
- [x] 2. Backend: Auto-registration module
- [x] 2.1 Create `services/trading/override.py` with `auto_register_symbol()` function
- Use `httpx.AsyncClient` to call Symbol Registry HTTP endpoints
- Check if ticker exists via `GET /companies?ticker={ticker}`
- If not found, `POST /companies` to create company (legal_name = ticker, active = true)
- Handle 409 conflict gracefully (fetch existing company and proceed)
- Create two default sources: `market_api` and `news_api` via `POST /companies/{id}/sources`
- Fetch active watchlists via `GET /watchlists`; add company to first active watchlist, or create "Manual Overrides" watchlist if none exist
- Source and watchlist failures are logged but do not block order enqueuing (best-effort)
- Return `(auto_registered: bool, company_id: str)`
- Registry base URL derived from env var or defaults to `http://symbol-registry:8000`
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6_
- [x] 2.2 Write unit tests for `auto_register_symbol()` in `tests/test_override.py`
- Mock httpx calls to Symbol Registry
- Test new symbol registration (company + sources + watchlist)
- Test existing symbol skip
- Test 409 conflict handling
- Test source/watchlist failure tolerance
- _Requirements: 4.1, 4.2, 4.3, 4.5, 4.6_
- [x] 3. Backend: Override endpoint unit tests and property-based tests
- [x] 3.1 Write unit tests for override endpoint in `tests/test_override.py`
- Test valid order returns 202 with correct response shape
- Test invalid ticker returns 422
- Test missing limit_price for limit order returns 422
- Test missing stop_price for stop order returns 422
- Test non-positive quantity returns 422
- Test enqueued job has correct structure and `source: "manual_override"`
- _Requirements: 3.1, 3.2, 3.4, 3.5, 9.1_
- [x] 3.2 Write property test for ticker validation and normalization in `tests/test_pbt_override.py`
- **Property 1: Ticker validation and normalization**
- Use Hypothesis `@settings(max_examples=100)` to generate arbitrary strings
- Assert: after uppercasing, accepted iff matches `^[A-Z]{1,10}$`; normalized output is always uppercased input
- **Validates: Requirements 2.2, 8.1**
- [x] 3.3 Write property test for override job payload completeness in `tests/test_pbt_override.py`
- **Property 2: Override job payload completeness**
- Use Hypothesis to generate valid override order requests
- Assert: enqueued payload contains all required fields, `source == "manual_override"`, `idempotency_key` starts with `"override-"`
- **Validates: Requirements 3.2, 9.1**
- [x] 3.4 Write property test for invalid override order rejection in `tests/test_pbt_override.py`
- **Property 3: Invalid override order rejection**
- Use Hypothesis to generate orders violating at least one validation rule
- Assert: endpoint returns 422 with at least one descriptive error message
- **Validates: Requirements 3.5, 2.6**
- [x] 4. Checkpoint — Backend tests
- Ensure all backend tests pass with `.venv/bin/python -m pytest tests/test_override.py tests/test_pbt_override.py -x --tb=short -q`, ask the user if questions arise.
- [x] 5. Frontend: API hook for submitting override orders
- [x] 5.1 Add `useSubmitOverrideOrder` mutation hook to `frontend/src/api/tradingHooks.ts`
- Define `OverrideOrderRequest` interface: `ticker`, `side` ("buy"|"sell"), `quantity`, `order_type` ("market"|"limit"|"stop"|"stop_limit"), optional `limit_price`, optional `stop_price`
- Define `OverrideOrderResponse` interface: `job_id`, `status`, `ticker`, `side`, `quantity`, `auto_registered`
- Use `apiPost<OverrideOrderResponse>('trading', '/api/trading/override/order', body)`
- On success, invalidate `orders`, `positions`, and `companies` query keys
- _Requirements: 2.5, 3.4, 7.1_
- [x] 6. Frontend: OverrideTradePanel component
- [x] 6.1 Create `frontend/src/pages/trading/OverrideTradePanel.tsx`
- Order form section with controlled inputs: ticker (text, auto-uppercase), side (buy/sell toggle), quantity (number), order type (select: market/limit/stop/stop_limit), conditional limit_price and stop_price fields
- Client-side validation: ticker 1-10 alpha chars, positive quantity, required price fields based on order type
- Inline validation error display on invalid inputs
- Submit via `useSubmitOverrideOrder` mutation
- Success: show green toast/banner with job_id and "queued" status, reset form
- 422 error: display validation errors inline
- 503 error: show "Broker service is unavailable" banner
- Network error: show connectivity error banner
- Submit button disabled during flight with loading state
- Positions display section using existing `usePositions()` hook from `api/hooks.ts`
- Positions table showing: ticker, quantity, avg entry price, current price, unrealized P&L
- Loading spinner while positions load, "No current positions" message when empty
- _Requirements: 1.2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 6.1, 6.2, 6.3, 6.4, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 8.1_
- [x] 7. Frontend: Override tab integration in TradingEnginePage
- [x] 7.1 Update `frontend/src/pages/TradingEngine.tsx` to add Override tab
- Add `{ id: 'override', label: 'Override' }` to the TABS array
- Move tab state from `useState` to URL search params using TanStack Router `useSearch` / `useNavigate` for the `/trading/engine` route
- Default to `'overview'` when no `tab` param is present
- Import and render `OverrideTradePanel` when `activeTab === 'override'`
- Update the route definition in `frontend/src/routes.tsx` to accept `tab` search param via `validateSearch`
- _Requirements: 1.1, 1.2, 1.3_
- [x] 8. Frontend: Navigation button on Trading Controls page
- [x] 8.1 Add "Override Trade" button to `frontend/src/pages/Trading.tsx`
- Add a TanStack Router `Link` component inside the Trading Mode card section
- Navigate to `/trading/engine` with search param `{ tab: 'override' }`
- Style with `rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700`
- _Requirements: 5.1, 5.2, 5.3_
- [x] 9. Frontend: MSW handlers for testing
- [x] 9.1 Add MSW handler for `POST /trading/api/trading/override/order` in `frontend/src/test/mocks/handlers.ts`
- Return 202 with mock `OverrideOrderResponse` containing `job_id`, `status: "queued"`, echoed `ticker`, `side`, `quantity`, and `auto_registered: false`
- _Requirements: 3.4, 7.1_
- [x] 10. Frontend: Component and integration tests
- [x] 10.1 Write frontend tests for Override tab in `frontend/src/test/override.test.tsx`
- Test override tab renders in tab bar
- Test override tab shows form and positions sections
- Test override tab accessible via URL param `?tab=override`
- Test order form fields are present (ticker, side, quantity, order type)
- Test conditional price fields show/hide based on order type
- Test form validation errors for invalid inputs
- Test successful order submission shows success message and resets form
- Test 422 error display
- Test submit button loading state during submission
- Test positions table renders with mock data
- Test positions loading state
- Test positions empty state
- Test "Override Trade" button on Trading page exists and links correctly
- _Requirements: 1.1, 1.2, 1.3, 2.1, 2.3, 2.4, 2.6, 5.1, 5.2, 6.1, 6.2, 6.3, 6.4, 7.1, 7.2, 7.5, 7.6_
- [x] 11. Final checkpoint
- Ensure all tests pass: backend with `.venv/bin/python -m pytest tests/test_override.py tests/test_pbt_override.py -x --tb=short -q` and frontend with `cd frontend && npx vitest --run`, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- Unit tests validate specific examples and edge cases
- The broker_service does not need modification — it already processes the full job payload and persists `decision_trace` JSONB
- No new database migrations are required; override orders use existing `orders`, `order_events`, and `risk_evaluations` tables