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
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user