first commit
This commit is contained in:
1
.kiro/specs/signal-dashboard/.config.kiro
Normal file
1
.kiro/specs/signal-dashboard/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"specId": "fa730cf4-a14d-4f62-8993-fd7db6fe25cc", "workflowType": "requirements-first", "specType": "feature"}
|
||||
645
.kiro/specs/signal-dashboard/design.md
Normal file
645
.kiro/specs/signal-dashboard/design.md
Normal file
@@ -0,0 +1,645 @@
|
||||
# Design Document: Signal Dashboard
|
||||
|
||||
## Overview
|
||||
|
||||
Signal Dashboard is a React 18 + TypeScript SPA that consumes the existing Stock Data Backend REST API (`/api/v1/`). It provides authenticated users with views for watchlist monitoring, per-ticker analysis, trade setup scanning, composite-score rankings, and admin management.
|
||||
|
||||
The frontend lives in `frontend/` within the existing project root. Vite builds static assets to `frontend/dist/`, which Nginx serves on `signal.thiessen.io`. API requests to `/api/v1/` are proxied to the FastAPI backend — no CORS needed.
|
||||
|
||||
### Key Technical Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Build tool | Vite 5 | Fast HMR, native TS/React support, small output |
|
||||
| Routing | React Router v6 | Standard, supports layout routes and guards |
|
||||
| Server state | TanStack Query v5 | Caching, deduplication, background refetch |
|
||||
| Client state | Zustand | Minimal auth store, no boilerplate |
|
||||
| Styling | Tailwind CSS v3 | Utility-first, dark mode built-in, small bundle |
|
||||
| Charts | Recharts | React-native charting, composable, lightweight |
|
||||
| HTTP | Axios | Interceptors for auth/envelope unwrapping |
|
||||
| Testing | Vitest + React Testing Library + fast-check | Vite-native test runner, property-based testing |
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Browser
|
||||
Router[React Router]
|
||||
Pages[Page Components]
|
||||
Hooks[TanStack Query Hooks]
|
||||
Store[Zustand Auth Store]
|
||||
API[API Client - Axios]
|
||||
end
|
||||
|
||||
Router --> Pages
|
||||
Pages --> Hooks
|
||||
Hooks --> API
|
||||
API --> Store
|
||||
Store --> API
|
||||
|
||||
subgraph Server
|
||||
Nginx[Nginx - static files + proxy]
|
||||
Backend[FastAPI Backend]
|
||||
end
|
||||
|
||||
API -->|/api/v1/*| Nginx
|
||||
Nginx -->|proxy_pass| Backend
|
||||
Nginx -->|static| Browser
|
||||
```
|
||||
|
||||
### Request Flow
|
||||
|
||||
1. Component mounts → calls a TanStack Query hook (e.g., `useWatchlist()`)
|
||||
2. Hook calls an API client function (e.g., `api.watchlist.list()`)
|
||||
3. Axios sends request with JWT Bearer header from Zustand store
|
||||
4. Axios response interceptor unwraps `{ status, data, error }` envelope
|
||||
5. On 401 → Zustand clears token, React Router redirects to `/login`
|
||||
6. TanStack Query caches the result, component renders data
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── index.html
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── vite.config.ts
|
||||
├── tailwind.config.ts
|
||||
├── postcss.config.js
|
||||
├── src/
|
||||
│ ├── main.tsx # App entry, providers
|
||||
│ ├── App.tsx # Router + layout
|
||||
│ ├── api/
|
||||
│ │ ├── client.ts # Axios instance, interceptors
|
||||
│ │ ├── auth.ts # login, register
|
||||
│ │ ├── watchlist.ts # watchlist CRUD
|
||||
│ │ ├── tickers.ts # ticker CRUD
|
||||
│ │ ├── scores.ts # scores, rankings, weights
|
||||
│ │ ├── trades.ts # trade setups
|
||||
│ │ ├── ohlcv.ts # OHLCV data
|
||||
│ │ ├── indicators.ts # technical indicators
|
||||
│ │ ├── sr-levels.ts # support/resistance
|
||||
│ │ ├── sentiment.ts # sentiment data
|
||||
│ │ ├── fundamentals.ts # fundamental data
|
||||
│ │ ├── ingestion.ts # manual data fetch
|
||||
│ │ ├── admin.ts # admin endpoints
|
||||
│ │ └── health.ts # health check
|
||||
│ ├── hooks/
|
||||
│ │ ├── useAuth.ts # login/register/logout mutations
|
||||
│ │ ├── useWatchlist.ts # watchlist queries + mutations
|
||||
│ │ ├── useTickers.ts # ticker queries + mutations
|
||||
│ │ ├── useScores.ts # scores, rankings queries
|
||||
│ │ ├── useTrades.ts # trade setup queries
|
||||
│ │ ├── useTickerDetail.ts # parallel queries for detail view
|
||||
│ │ └── useAdmin.ts # admin queries + mutations
|
||||
│ ├── stores/
|
||||
│ │ └── authStore.ts # Zustand: token, user, role
|
||||
│ ├── pages/
|
||||
│ │ ├── LoginPage.tsx
|
||||
│ │ ├── RegisterPage.tsx
|
||||
│ │ ├── WatchlistPage.tsx
|
||||
│ │ ├── TickerDetailPage.tsx
|
||||
│ │ ├── ScannerPage.tsx
|
||||
│ │ ├── RankingsPage.tsx
|
||||
│ │ └── AdminPage.tsx
|
||||
│ ├── components/
|
||||
│ │ ├── layout/
|
||||
│ │ │ ├── AppShell.tsx # Sidebar + main content
|
||||
│ │ │ ├── Sidebar.tsx
|
||||
│ │ │ └── MobileNav.tsx
|
||||
│ │ ├── auth/
|
||||
│ │ │ └── ProtectedRoute.tsx
|
||||
│ │ ├── charts/
|
||||
│ │ │ └── CandlestickChart.tsx
|
||||
│ │ ├── ui/
|
||||
│ │ │ ├── ScoreCard.tsx
|
||||
│ │ │ ├── Toast.tsx
|
||||
│ │ │ ├── Skeleton.tsx
|
||||
│ │ │ ├── Badge.tsx
|
||||
│ │ │ └── ConfirmDialog.tsx
|
||||
│ │ ├── watchlist/
|
||||
│ │ │ ├── WatchlistTable.tsx
|
||||
│ │ │ └── AddTickerForm.tsx
|
||||
│ │ ├── scanner/
|
||||
│ │ │ └── TradeTable.tsx
|
||||
│ │ ├── rankings/
|
||||
│ │ │ ├── RankingsTable.tsx
|
||||
│ │ │ └── WeightsForm.tsx
|
||||
│ │ ├── ticker/
|
||||
│ │ │ ├── SentimentPanel.tsx
|
||||
│ │ │ ├── FundamentalsPanel.tsx
|
||||
│ │ │ ├── IndicatorSelector.tsx
|
||||
│ │ │ └── SROverlay.tsx
|
||||
│ │ └── admin/
|
||||
│ │ ├── UserTable.tsx
|
||||
│ │ ├── SettingsForm.tsx
|
||||
│ │ ├── JobControls.tsx
|
||||
│ │ └── DataCleanup.tsx
|
||||
│ ├── lib/
|
||||
│ │ ├── format.ts # Number/date formatting utilities
|
||||
│ │ └── types.ts # Shared TypeScript interfaces
|
||||
│ └── styles/
|
||||
│ └── globals.css # Tailwind directives + custom vars
|
||||
└── tests/
|
||||
├── unit/
|
||||
└── property/
|
||||
```
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### API Client (`src/api/client.ts`)
|
||||
|
||||
Central Axios instance with interceptors:
|
||||
|
||||
```typescript
|
||||
// Axios instance configuration
|
||||
const apiClient = axios.create({
|
||||
baseURL: '/api/v1/',
|
||||
timeout: 30_000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
// Request interceptor: attach JWT
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = useAuthStore.getState().token;
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor: unwrap envelope, handle 401
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
const envelope = response.data as APIEnvelope;
|
||||
if (envelope.status === 'error') throw new ApiError(envelope.error);
|
||||
return envelope.data;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
useAuthStore.getState().logout();
|
||||
}
|
||||
const msg = error.response?.data?.error ?? error.message ?? 'Network error';
|
||||
throw new ApiError(msg);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Auth Store (`src/stores/authStore.ts`)
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
token: string | null;
|
||||
username: string | null;
|
||||
role: 'admin' | 'user' | null;
|
||||
login: (token: string) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
- `login()` decodes the JWT payload to extract `sub` (username) and `role`, stores token in `localStorage`
|
||||
- `logout()` clears token from state and `localStorage`, TanStack Query cache is cleared on logout
|
||||
|
||||
### Protected Route (`src/components/auth/ProtectedRoute.tsx`)
|
||||
|
||||
```typescript
|
||||
// Wraps routes that require authentication
|
||||
// Props: requireAdmin?: boolean
|
||||
// If no token → redirect to /login
|
||||
// If requireAdmin && role !== 'admin' → redirect to /watchlist
|
||||
```
|
||||
|
||||
### Router Layout
|
||||
|
||||
```typescript
|
||||
// Route structure
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route element={<AppShell />}>
|
||||
<Route path="/" element={<Navigate to="/watchlist" />} />
|
||||
<Route path="/watchlist" element={<WatchlistPage />} />
|
||||
<Route path="/ticker/:symbol" element={<TickerDetailPage />} />
|
||||
<Route path="/scanner" element={<ScannerPage />} />
|
||||
<Route path="/rankings" element={<RankingsPage />} />
|
||||
<Route element={<ProtectedRoute requireAdmin />}>
|
||||
<Route path="/admin" element={<AdminPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
```
|
||||
|
||||
### TanStack Query Hooks Pattern
|
||||
|
||||
Each domain has a hook file that exports query/mutation hooks:
|
||||
|
||||
```typescript
|
||||
// Example: useWatchlist.ts
|
||||
export function useWatchlist() {
|
||||
return useQuery({
|
||||
queryKey: ['watchlist'],
|
||||
queryFn: () => api.watchlist.list(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddToWatchlist() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (symbol: string) => api.watchlist.add(symbol),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['watchlist'] }),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Key UI Components
|
||||
|
||||
**ScoreCard**: Displays composite score with a colored ring/bar (green > 70, yellow 40-70, red < 40) and expandable dimension breakdown.
|
||||
|
||||
**CandlestickChart**: Recharts `ComposedChart` with custom `Bar` shapes for OHLCV candles. S/R levels rendered as `ReferenceLine` components with color coding (green = support, red = resistance).
|
||||
|
||||
**Toast System**: Lightweight toast using React context + portal. Auto-dismiss after 4 seconds. Error toasts in red, success in green.
|
||||
|
||||
**Skeleton**: Tailwind `animate-pulse` placeholder blocks matching the shape of cards/tables during loading states.
|
||||
|
||||
### Formatting Utilities (`src/lib/format.ts`)
|
||||
|
||||
```typescript
|
||||
formatPrice(n: number): string // "1,234.56"
|
||||
formatPercent(n: number): string // "12.34%"
|
||||
formatLargeNumber(n: number): string // "1.23B", "456.7M", "12.3K"
|
||||
formatDate(d: string): string // "Jan 15, 2025"
|
||||
formatDateTime(d: string): string // "Jan 15, 2025 2:30 PM"
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
### TypeScript Interfaces (`src/lib/types.ts`)
|
||||
|
||||
```typescript
|
||||
// API envelope (before unwrapping)
|
||||
interface APIEnvelope<T = unknown> {
|
||||
status: 'success' | 'error';
|
||||
data: T | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Auth
|
||||
interface TokenResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
// Watchlist
|
||||
interface WatchlistEntry {
|
||||
symbol: string;
|
||||
entry_type: 'auto' | 'manual';
|
||||
composite_score: number | null;
|
||||
dimensions: DimensionScore[];
|
||||
rr_ratio: number | null;
|
||||
rr_direction: string | null;
|
||||
sr_levels: SRLevelSummary[];
|
||||
added_at: string;
|
||||
}
|
||||
|
||||
interface DimensionScore {
|
||||
dimension: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
interface SRLevelSummary {
|
||||
price_level: number;
|
||||
type: 'support' | 'resistance';
|
||||
strength: number;
|
||||
}
|
||||
|
||||
// OHLCV
|
||||
interface OHLCVBar {
|
||||
id: number;
|
||||
ticker_id: number;
|
||||
date: string;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Scores
|
||||
interface ScoreResponse {
|
||||
symbol: string;
|
||||
composite_score: number | null;
|
||||
composite_stale: boolean;
|
||||
weights: Record<string, number>;
|
||||
dimensions: DimensionScoreDetail[];
|
||||
missing_dimensions: string[];
|
||||
computed_at: string | null;
|
||||
}
|
||||
|
||||
interface DimensionScoreDetail {
|
||||
dimension: string;
|
||||
score: number;
|
||||
is_stale: boolean;
|
||||
computed_at: string | null;
|
||||
}
|
||||
|
||||
interface RankingEntry {
|
||||
symbol: string;
|
||||
composite_score: number;
|
||||
dimensions: DimensionScoreDetail[];
|
||||
}
|
||||
|
||||
interface RankingsResponse {
|
||||
rankings: RankingEntry[];
|
||||
weights: Record<string, number>;
|
||||
}
|
||||
|
||||
// Trade Setups
|
||||
interface TradeSetup {
|
||||
id: number;
|
||||
symbol: string;
|
||||
direction: string;
|
||||
entry_price: number;
|
||||
stop_loss: number;
|
||||
target: number;
|
||||
rr_ratio: number;
|
||||
composite_score: number;
|
||||
detected_at: string;
|
||||
}
|
||||
|
||||
// S/R Levels
|
||||
interface SRLevel {
|
||||
id: number;
|
||||
price_level: number;
|
||||
type: 'support' | 'resistance';
|
||||
strength: number;
|
||||
detection_method: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface SRLevelResponse {
|
||||
symbol: string;
|
||||
levels: SRLevel[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
// Sentiment
|
||||
interface SentimentScore {
|
||||
id: number;
|
||||
classification: 'bullish' | 'bearish' | 'neutral';
|
||||
confidence: number;
|
||||
source: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface SentimentResponse {
|
||||
symbol: string;
|
||||
scores: SentimentScore[];
|
||||
count: number;
|
||||
dimension_score: number | null;
|
||||
lookback_hours: number;
|
||||
}
|
||||
|
||||
// Fundamentals
|
||||
interface FundamentalResponse {
|
||||
symbol: string;
|
||||
pe_ratio: number | null;
|
||||
revenue_growth: number | null;
|
||||
earnings_surprise: number | null;
|
||||
market_cap: number | null;
|
||||
fetched_at: string | null;
|
||||
}
|
||||
|
||||
// Indicators
|
||||
interface IndicatorResult {
|
||||
indicator_type: string;
|
||||
values: Record<string, unknown>;
|
||||
score: number;
|
||||
bars_used: number;
|
||||
}
|
||||
|
||||
interface EMACrossResult {
|
||||
short_ema: number;
|
||||
long_ema: number;
|
||||
short_period: number;
|
||||
long_period: number;
|
||||
signal: 'bullish' | 'bearish' | 'neutral';
|
||||
}
|
||||
|
||||
// Tickers
|
||||
interface Ticker {
|
||||
id: number;
|
||||
symbol: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Admin
|
||||
interface AdminUser {
|
||||
id: number;
|
||||
username: string;
|
||||
role: string;
|
||||
has_access: boolean;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
interface SystemSetting {
|
||||
key: string;
|
||||
value: string;
|
||||
updated_at: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: Token storage round-trip
|
||||
|
||||
*For any* valid JWT token string, storing it via `authStore.login(token)` and then reading `authStore.token` and `localStorage.getItem('token')` should both return the original token string.
|
||||
|
||||
**Validates: Requirements 1.1, 1.6**
|
||||
|
||||
### Property 2: Bearer token attachment
|
||||
|
||||
*For any* non-null token in the auth store, every request made through the API client should include an `Authorization` header with value `Bearer {token}`.
|
||||
|
||||
**Validates: Requirements 1.3, 12.3**
|
||||
|
||||
### Property 3: Registration form validation
|
||||
|
||||
*For any* username string shorter than 1 character or password string shorter than 6 characters, the registration form should reject submission. *For any* username of length >= 1 and password of length >= 6, the form should allow submission.
|
||||
|
||||
**Validates: Requirements 1.2**
|
||||
|
||||
### Property 4: Route protection based on auth state
|
||||
|
||||
*For any* protected route path, if no token exists in the auth store, navigation should redirect to `/login`. If a valid token exists, navigation should render the protected component.
|
||||
|
||||
**Validates: Requirements 2.1, 2.2**
|
||||
|
||||
### Property 5: API envelope unwrapping
|
||||
|
||||
*For any* API response with `status: "success"`, the API client should return the `data` field. *For any* API response with `status: "error"`, the API client should throw an error containing the `error` field message.
|
||||
|
||||
**Validates: Requirements 12.2**
|
||||
|
||||
### Property 6: Watchlist entry rendering completeness
|
||||
|
||||
*For any* watchlist entry, the rendered output should contain the symbol, entry type (with a visual badge distinguishing "auto" from "manual"), composite score, dimension scores, R:R ratio, R:R direction, and S/R levels.
|
||||
|
||||
**Validates: Requirements 3.2, 3.7**
|
||||
|
||||
### Property 7: Symbol click navigation
|
||||
|
||||
*For any* symbol displayed in the watchlist table, scanner table, or rankings table, clicking that symbol should trigger navigation to `/ticker/{symbol}`.
|
||||
|
||||
**Validates: Requirements 3.6, 5.6, 6.4**
|
||||
|
||||
### Property 8: Score card rendering
|
||||
|
||||
*For any* score response with a composite score and dimension scores, the ScoreCard component should render the composite score value and one entry per dimension with its name and score.
|
||||
|
||||
**Validates: Requirements 4.4**
|
||||
|
||||
### Property 9: Sentiment panel rendering
|
||||
|
||||
*For any* sentiment response, the rendered SentimentPanel should display the classification, confidence value, and dimension score.
|
||||
|
||||
**Validates: Requirements 4.5**
|
||||
|
||||
### Property 10: Fundamentals panel rendering
|
||||
|
||||
*For any* fundamentals response, the rendered FundamentalsPanel should display P/E ratio, revenue growth, earnings surprise, and market cap (or a placeholder for null values).
|
||||
|
||||
**Validates: Requirements 4.6**
|
||||
|
||||
### Property 11: Trade setup rendering
|
||||
|
||||
*For any* trade setup, the rendered table row should contain the symbol, direction, entry price, stop loss, target, R:R ratio, composite score, and detection timestamp.
|
||||
|
||||
**Validates: Requirements 5.2**
|
||||
|
||||
### Property 12: Scanner filtering
|
||||
|
||||
*For any* list of trade setups, minimum R:R filter value, and direction filter selection: all displayed setups should have `rr_ratio >= minRR` and (if direction is not "both") `direction === selectedDirection`.
|
||||
|
||||
**Validates: Requirements 5.3, 5.4**
|
||||
|
||||
### Property 13: Scanner sorting
|
||||
|
||||
*For any* list of trade setups and a selected sort column, the displayed rows should be ordered by that column's values (ascending or descending based on sort direction).
|
||||
|
||||
**Validates: Requirements 5.5**
|
||||
|
||||
### Property 14: Rankings display order
|
||||
|
||||
*For any* rankings response, the rendered list should display entries in descending order by composite score, with each entry showing rank position, symbol, composite score, and all dimension scores.
|
||||
|
||||
**Validates: Requirements 6.1, 6.2**
|
||||
|
||||
### Property 15: Admin user table rendering
|
||||
|
||||
*For any* admin user record, the rendered table row should contain the username, role, and access status.
|
||||
|
||||
**Validates: Requirements 7.2**
|
||||
|
||||
### Property 16: Number formatting
|
||||
|
||||
*For any* finite number, `formatPrice` should produce a string with exactly 2 decimal places. `formatPercent` should produce a string ending with `%`. `formatLargeNumber` should produce a string with an appropriate suffix (`K` for thousands, `M` for millions, `B` for billions) for values >= 1000, and no suffix for smaller values.
|
||||
|
||||
**Validates: Requirements 13.4**
|
||||
|
||||
### Property 17: Weights form rendering
|
||||
|
||||
*For any* weights map (dimension name → number), the WeightsForm should render one labeled numeric input per dimension key.
|
||||
|
||||
**Validates: Requirements 11.1**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### API Client Error Strategy
|
||||
|
||||
All errors flow through the Axios response interceptor and are surfaced via the Toast system:
|
||||
|
||||
| Error Type | Detection | Behavior |
|
||||
|---|---|---|
|
||||
| 401 Unauthorized | `error.response.status === 401` | Clear auth store, redirect to `/login` |
|
||||
| API error envelope | `envelope.status === 'error'` | Throw `ApiError` with `envelope.error` message |
|
||||
| Network error | No `error.response` | Throw `ApiError` with "Network error — check your connection" |
|
||||
| Timeout | Axios timeout (30s) | Throw `ApiError` with "Request timed out" |
|
||||
| Unknown | Catch-all | Throw `ApiError` with `error.message` fallback |
|
||||
|
||||
### Component-Level Error Handling
|
||||
|
||||
- **TanStack Query `onError`**: Each mutation hook passes errors to the toast system
|
||||
- **Query error states**: Components check `isError` and render inline error messages
|
||||
- **Ticker Detail partial failure**: Each data section (scores, sentiment, fundamentals, S/R, OHLCV) is an independent query. If one fails, the others still render. Failed sections show an inline error with a retry button.
|
||||
- **Form validation**: Client-side validation before API calls (username length, password length, numeric inputs). Invalid submissions are blocked with inline field errors.
|
||||
|
||||
### Toast System
|
||||
|
||||
```typescript
|
||||
type ToastType = 'success' | 'error' | 'info';
|
||||
|
||||
interface Toast {
|
||||
id: string;
|
||||
type: ToastType;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Auto-dismiss after 4 seconds
|
||||
// Max 3 toasts visible at once (oldest dismissed first)
|
||||
// Error toasts: red accent, Success: green accent, Info: blue accent
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Testing Stack
|
||||
|
||||
| Tool | Purpose |
|
||||
|---|---|
|
||||
| Vitest | Test runner (Vite-native, fast) |
|
||||
| React Testing Library | Component rendering + DOM queries |
|
||||
| fast-check | Property-based testing |
|
||||
| MSW (Mock Service Worker) | API mocking for integration tests |
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests cover specific examples, edge cases, and integration points:
|
||||
|
||||
- **Auth flow**: Login stores token, logout clears token, 401 triggers logout
|
||||
- **API client**: Envelope unwrapping for success/error, timeout config, Bearer header
|
||||
- **Routing**: Unauthenticated redirect, admin-only route guard, non-admin redirect
|
||||
- **Component rendering**: Each page renders with mock data, loading skeletons appear, error states display
|
||||
- **Form validation**: Empty username rejected, short password rejected, valid inputs accepted
|
||||
- **Confirmation dialog**: Delete ticker shows confirm before API call
|
||||
- **Partial failure**: Ticker detail renders available sections when one query fails
|
||||
|
||||
### Property-Based Tests
|
||||
|
||||
Each correctness property maps to a single `fast-check` property test with minimum 100 iterations. Tests are tagged with the property reference:
|
||||
|
||||
```typescript
|
||||
// Feature: signal-dashboard, Property 16: Number formatting
|
||||
test.prop([fc.float({ min: -1e15, max: 1e15, noNaN: true })], (n) => {
|
||||
const result = formatPrice(n);
|
||||
expect(result).toMatch(/\.\d{2}$/);
|
||||
});
|
||||
```
|
||||
|
||||
Property tests focus on:
|
||||
- **Pure functions**: `format.ts` utilities (Property 16)
|
||||
- **Store logic**: Auth store token round-trip (Property 1)
|
||||
- **API client interceptors**: Envelope unwrapping (Property 5), Bearer attachment (Property 2)
|
||||
- **Filtering/sorting logic**: Scanner filter functions (Properties 12, 13)
|
||||
- **Component rendering**: Given generated data, components render required fields (Properties 6, 8–11, 14, 15, 17)
|
||||
- **Routing guards**: Protected route behavior based on auth state (Property 4)
|
||||
|
||||
### Test Configuration
|
||||
|
||||
- Vitest config in `frontend/vitest.config.ts` with jsdom environment
|
||||
- `fast-check` configured with `{ numRuns: 100 }` minimum per property
|
||||
- MSW handlers for all API endpoints used in integration tests
|
||||
- Each property test tagged: `Feature: signal-dashboard, Property {N}: {title}`
|
||||
200
.kiro/specs/signal-dashboard/requirements.md
Normal file
200
.kiro/specs/signal-dashboard/requirements.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
Signal Dashboard is a single-page application (SPA) frontend for the Stock Data Backend API. The Dashboard provides authenticated users with a visual interface to monitor watchlists, analyze individual tickers across multiple dimensions (technical, S/R, sentiment, fundamentals, momentum), scan for asymmetric risk:reward trade setups, view composite-score rankings, and manage system administration. The Dashboard consumes the existing REST API at `/api/v1/` and is served as static files by Nginx on the same domain (`signal.thiessen.io`).
|
||||
|
||||
Technology choice: React 18 + TypeScript + Vite, with TanStack Query for data fetching, Zustand for auth state, Tailwind CSS for styling, Recharts for charting, and React Router for navigation. This stack prioritizes maintainability, small bundle size, and a modern developer experience without framework bloat.
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Dashboard**: The Signal Dashboard SPA frontend application
|
||||
- **API_Client**: The HTTP client module that communicates with the backend REST API
|
||||
- **Auth_Module**: The authentication subsystem handling login, registration, token storage, and token refresh
|
||||
- **Watchlist_View**: The main overview page displaying the user's watchlist entries with enriched score data
|
||||
- **Ticker_Detail_View**: The per-ticker analysis page showing price chart, indicators, S/R levels, sentiment, and fundamentals
|
||||
- **Scanner_View**: The trade setup scanner page displaying R:R filtered setups
|
||||
- **Rankings_View**: The page displaying all tickers sorted by composite score
|
||||
- **Admin_Panel**: The administration interface for user management, job control, system settings, and data cleanup
|
||||
- **Router**: The client-side routing module controlling navigation and access guards
|
||||
- **Token_Store**: The client-side storage mechanism for JWT access tokens
|
||||
- **Chart_Component**: The interactive price chart component rendering OHLCV candlestick data with overlays
|
||||
- **Score_Card**: A UI component displaying a composite score and its dimension breakdown
|
||||
- **Toast_System**: The notification subsystem displaying transient success/error messages to the user
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement 1: JWT Authentication Flow
|
||||
|
||||
**User Story:** As a user, I want to log in and register so that I can access the dashboard securely.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN a user submits valid credentials on the login form, THE Auth_Module SHALL send a POST request to `/api/v1/auth/login` and store the returned JWT token in the Token_Store
|
||||
2. WHEN a user submits a registration form with a username (minimum 1 character) and password (minimum 6 characters), THE Auth_Module SHALL send a POST request to `/api/v1/auth/register` and display a success message via the Toast_System
|
||||
3. WHILE a valid JWT token exists in the Token_Store, THE API_Client SHALL include the token as a Bearer authorization header on all subsequent API requests
|
||||
4. WHEN the API returns a 401 Unauthorized response, THE Auth_Module SHALL clear the Token_Store and redirect the user to the login page
|
||||
5. IF the login or registration request fails, THEN THE Auth_Module SHALL display the error message from the API response via the Toast_System
|
||||
6. THE Token_Store SHALL persist the JWT token in browser localStorage so that sessions survive page reloads
|
||||
|
||||
### Requirement 2: Protected Routing and Role-Based Access
|
||||
|
||||
**User Story:** As a user, I want the app to enforce access control so that unauthenticated users cannot access protected pages and only admins can access admin features.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHILE no valid JWT token exists in the Token_Store, THE Router SHALL redirect navigation to any protected route to the login page
|
||||
2. WHILE a valid JWT token exists in the Token_Store, THE Router SHALL allow navigation to protected routes (Watchlist_View, Ticker_Detail_View, Scanner_View, Rankings_View)
|
||||
3. WHILE the authenticated user has an admin role, THE Router SHALL allow navigation to the Admin_Panel
|
||||
4. WHILE the authenticated user has a non-admin role, THE Router SHALL redirect navigation to the Admin_Panel to the Watchlist_View
|
||||
5. THE Router SHALL provide a navigation sidebar or top bar with links to all accessible views for the authenticated user
|
||||
|
||||
### Requirement 3: Watchlist Overview
|
||||
|
||||
**User Story:** As a user, I want to see my watchlist with composite scores, dimension breakdowns, and R:R ratios so that I can quickly assess my tracked tickers.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Watchlist_View loads, THE Dashboard SHALL fetch data from `GET /api/v1/watchlist` and display each entry as a card or row
|
||||
2. THE Watchlist_View SHALL display for each entry: symbol, entry type (auto/manual), composite score, dimension scores, R:R ratio, R:R direction, and nearest S/R levels
|
||||
3. WHEN a user clicks the add-to-watchlist control and enters a valid ticker symbol, THE Dashboard SHALL send a POST request to `/api/v1/watchlist/{symbol}` and refresh the watchlist
|
||||
4. WHEN a user clicks the remove button on a watchlist entry, THE Dashboard SHALL send a DELETE request to `/api/v1/watchlist/{symbol}` and remove the entry from the display
|
||||
5. IF the watchlist API request fails, THEN THE Dashboard SHALL display the error message via the Toast_System
|
||||
6. WHEN a user clicks on a watchlist entry symbol, THE Router SHALL navigate to the Ticker_Detail_View for that symbol
|
||||
7. THE Watchlist_View SHALL visually distinguish auto-populated entries from manual entries using a badge or label
|
||||
|
||||
### Requirement 4: Ticker Detail View
|
||||
|
||||
**User Story:** As a user, I want to see a comprehensive analysis of a single ticker including price chart, indicators, S/R levels, sentiment, and fundamentals so that I can make informed decisions.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Ticker_Detail_View loads for a given symbol, THE Dashboard SHALL fetch data in parallel from: `GET /api/v1/ohlcv/{symbol}`, `GET /api/v1/scores/{symbol}`, `GET /api/v1/sr-levels/{symbol}`, `GET /api/v1/sentiment/{symbol}`, and `GET /api/v1/fundamentals/{symbol}`
|
||||
2. THE Chart_Component SHALL render OHLCV data as a candlestick chart with date on the x-axis and price on the y-axis
|
||||
3. THE Chart_Component SHALL overlay S/R levels as horizontal lines on the price chart, color-coded by type (support in green, resistance in red)
|
||||
4. THE Ticker_Detail_View SHALL display the composite score and all dimension scores using Score_Card components
|
||||
5. THE Ticker_Detail_View SHALL display sentiment data including classification (bullish/bearish/neutral), confidence, and the time-decay weighted dimension score
|
||||
6. THE Ticker_Detail_View SHALL display fundamental data including P/E ratio, revenue growth, earnings surprise, and market cap
|
||||
7. WHEN a user selects an indicator type (ADX, EMA, RSI, ATR, volume_profile, pivot_points), THE Dashboard SHALL fetch data from `GET /api/v1/indicators/{symbol}/{indicator_type}` and display the result with its normalized score
|
||||
8. WHEN a user requests the EMA cross signal, THE Dashboard SHALL fetch data from `GET /api/v1/indicators/{symbol}/ema-cross` and display the signal (bullish/bearish/neutral) with short and long EMA values
|
||||
9. IF any data fetch fails for the Ticker_Detail_View, THEN THE Dashboard SHALL display an inline error message for the failed section while rendering the remaining sections normally
|
||||
|
||||
### Requirement 5: Trade Setup Scanner
|
||||
|
||||
**User Story:** As a user, I want to scan for trade setups with favorable risk:reward ratios so that I can find asymmetric opportunities.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Scanner_View loads, THE Dashboard SHALL fetch data from `GET /api/v1/trades` and display all trade setups in a sortable table
|
||||
2. THE Scanner_View SHALL display for each trade setup: symbol, direction (long/short), entry price, stop loss, target, R:R ratio, composite score, and detection timestamp
|
||||
3. THE Scanner_View SHALL allow the user to filter trade setups by minimum R:R ratio using a numeric input
|
||||
4. THE Scanner_View SHALL allow the user to filter trade setups by direction (long, short, or both)
|
||||
5. THE Scanner_View SHALL allow the user to sort the table by any column (R:R ratio, composite score, symbol, detection time)
|
||||
6. WHEN a user clicks on a trade setup symbol, THE Router SHALL navigate to the Ticker_Detail_View for that symbol
|
||||
|
||||
### Requirement 6: Rankings View
|
||||
|
||||
**User Story:** As a user, I want to see all tickers ranked by composite score so that I can identify the strongest opportunities.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Rankings_View loads, THE Dashboard SHALL fetch data from `GET /api/v1/rankings` and display tickers sorted by composite score descending
|
||||
2. THE Rankings_View SHALL display for each ticker: rank position, symbol, composite score, and all dimension scores
|
||||
3. THE Rankings_View SHALL display the current scoring weights used for composite calculation
|
||||
4. WHEN a user clicks on a ranked ticker symbol, THE Router SHALL navigate to the Ticker_Detail_View for that symbol
|
||||
|
||||
### Requirement 7: Admin Panel — User Management
|
||||
|
||||
**User Story:** As an admin, I want to manage user accounts so that I can control access to the platform.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Admin_Panel user management section loads, THE Dashboard SHALL fetch data from `GET /api/v1/admin/users` and display all users in a table
|
||||
2. THE Admin_Panel SHALL display for each user: username, role, and access status
|
||||
3. WHEN an admin clicks the create-user control and submits a username, password, role, and access flag, THE Dashboard SHALL send a POST request to `/api/v1/admin/users` and refresh the user list
|
||||
4. WHEN an admin toggles a user's access status, THE Dashboard SHALL send a PUT request to `/api/v1/admin/users/{user_id}/access` with the new access flag
|
||||
5. WHEN an admin resets a user's password, THE Dashboard SHALL send a PUT request to `/api/v1/admin/users/{user_id}/password` with the new password
|
||||
6. IF any admin user management request fails, THEN THE Dashboard SHALL display the error message via the Toast_System
|
||||
|
||||
### Requirement 8: Admin Panel — System Settings and Jobs
|
||||
|
||||
**User Story:** As an admin, I want to manage system settings, scheduled jobs, and data cleanup so that I can maintain the platform.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the Admin_Panel settings section loads, THE Dashboard SHALL fetch data from `GET /api/v1/admin/settings` and display all settings as editable fields
|
||||
2. WHEN an admin updates a system setting value, THE Dashboard SHALL send a PUT request to `/api/v1/admin/settings/{key}` with the new value
|
||||
3. WHEN an admin toggles the registration setting, THE Dashboard SHALL send a PUT request to `/api/v1/admin/settings/registration` with the enabled flag
|
||||
4. WHEN an admin toggles a scheduled job on or off, THE Dashboard SHALL send a PUT request to `/api/v1/admin/jobs/{job_name}/toggle` with the enabled flag
|
||||
5. WHEN an admin triggers a scheduled job manually, THE Dashboard SHALL send a POST request to `/api/v1/admin/jobs/{job_name}/trigger` and display a confirmation via the Toast_System
|
||||
6. WHEN an admin submits a data cleanup request with an older-than-days value, THE Dashboard SHALL send a POST request to `/api/v1/admin/data/cleanup` with the specified value and display the result via the Toast_System
|
||||
|
||||
### Requirement 9: Ticker Management
|
||||
|
||||
**User Story:** As a user, I want to add and remove tickers from the system so that I can track the stocks I care about.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN a user submits a new ticker symbol via the add-ticker form, THE Dashboard SHALL send a POST request to `/api/v1/tickers` with the symbol and refresh the ticker list
|
||||
2. WHEN a user views the ticker list, THE Dashboard SHALL fetch data from `GET /api/v1/tickers` and display all registered tickers
|
||||
3. WHEN a user clicks the delete button on a ticker, THE Dashboard SHALL display a confirmation dialog before sending a DELETE request to `/api/v1/tickers/{symbol}`
|
||||
4. IF a ticker deletion or creation request fails, THEN THE Dashboard SHALL display the error message via the Toast_System
|
||||
5. WHEN a ticker is successfully deleted, THE Dashboard SHALL remove the ticker from the displayed list without requiring a full page reload
|
||||
|
||||
### Requirement 10: Data Ingestion Trigger
|
||||
|
||||
**User Story:** As a user, I want to manually trigger data ingestion for a specific ticker so that I can get fresh data on demand.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN a user clicks the fetch-data button on the Ticker_Detail_View, THE Dashboard SHALL send a POST request to `/api/v1/ingestion/fetch/{symbol}`
|
||||
2. WHILE the ingestion request is in progress, THE Dashboard SHALL display a loading indicator on the fetch-data button
|
||||
3. WHEN the ingestion request completes successfully, THE Dashboard SHALL display a success message via the Toast_System and refresh the OHLCV data on the Ticker_Detail_View
|
||||
4. IF the ingestion request fails, THEN THE Dashboard SHALL display the error message via the Toast_System
|
||||
|
||||
### Requirement 11: Score Weight Configuration
|
||||
|
||||
**User Story:** As a user, I want to adjust the scoring dimension weights so that I can customize the composite score calculation to my strategy.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE Dashboard SHALL display the current scoring weights on the Rankings_View with editable numeric inputs for each dimension
|
||||
2. WHEN a user modifies one or more weight values and submits the form, THE Dashboard SHALL send a PUT request to `/api/v1/scores/weights` with the updated weights map
|
||||
3. WHEN the weight update succeeds, THE Dashboard SHALL refresh the rankings data to reflect the new composite scores
|
||||
4. IF the weight update request fails, THEN THE Dashboard SHALL display the error message via the Toast_System
|
||||
|
||||
### Requirement 12: API Client and Error Handling
|
||||
|
||||
**User Story:** As a developer, I want a centralized API client with consistent error handling so that all API interactions follow the same patterns.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE API_Client SHALL send all requests to the base URL `/api/v1/` using the JSON content type
|
||||
2. THE API_Client SHALL unwrap the API envelope (`{ status, data, error }`) and return the `data` field on success or throw an error with the `error` field on failure
|
||||
3. THE API_Client SHALL attach the JWT Bearer token from the Token_Store to every authenticated request
|
||||
4. WHEN the API_Client receives a network error or timeout, THE API_Client SHALL throw a descriptive error that the calling component can display via the Toast_System
|
||||
5. THE API_Client SHALL set a request timeout of 30 seconds for all API calls
|
||||
|
||||
### Requirement 13: Responsive Layout and Visual Design
|
||||
|
||||
**User Story:** As a user, I want the dashboard to have a clean, modern interface that works on desktop and tablet screens so that I can use it comfortably.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE Dashboard SHALL use a sidebar navigation layout on screens wider than 1024 pixels and a collapsible hamburger menu on narrower screens
|
||||
2. THE Dashboard SHALL use a dark color scheme with accent colors for positive (green) and negative (red) values consistent with financial data conventions
|
||||
3. THE Dashboard SHALL apply smooth transitions (duration 150ms to 300ms) for page navigation, modal openings, and interactive element state changes
|
||||
4. THE Dashboard SHALL display numeric financial values with appropriate formatting: prices to 2 decimal places, percentages with a percent sign, large numbers with abbreviations (K, M, B)
|
||||
5. THE Dashboard SHALL display loading skeleton placeholders while data is being fetched from the API
|
||||
|
||||
### Requirement 14: Static SPA Build and Deployment
|
||||
|
||||
**User Story:** As a developer, I want the frontend to build as static files that Nginx can serve alongside the backend API so that deployment is simple.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE Dashboard SHALL produce a static build output (HTML, CSS, JS) in a `dist/` directory via a single build command
|
||||
2. THE Dashboard SHALL use hash-based filenames for JS and CSS assets to enable long-term browser caching
|
||||
3. THE Dashboard SHALL support client-side routing with a fallback to `index.html` for all non-API routes (Nginx `try_files` configuration)
|
||||
4. THE Dashboard SHALL proxy API requests to `/api/v1/` on the same origin, requiring no CORS configuration in production
|
||||
5. WHEN the `GET /api/v1/health` endpoint returns a success response, THE Dashboard SHALL consider the backend available
|
||||
238
.kiro/specs/signal-dashboard/tasks.md
Normal file
238
.kiro/specs/signal-dashboard/tasks.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# Implementation Plan: Signal Dashboard
|
||||
|
||||
## Overview
|
||||
|
||||
Incremental build of the Signal Dashboard SPA in `frontend/`. Each phase wires up end-to-end before moving on, so there's always a runnable app. Backend API is already live — we consume it as-is.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Scaffold Vite + React + TypeScript project
|
||||
- [x] 1.1 Initialize `frontend/` with Vite React-TS template, install dependencies (react, react-dom, react-router-dom, @tanstack/react-query, zustand, axios, recharts, tailwindcss, postcss, autoprefixer)
|
||||
- Create `package.json`, `tsconfig.json`, `vite.config.ts`, `tailwind.config.ts`, `postcss.config.js`
|
||||
- Configure Vite proxy for `/api/v1/` to backend during dev
|
||||
- Configure Tailwind with dark mode class strategy
|
||||
- Create `src/main.tsx`, `src/App.tsx`, `src/styles/globals.css` with Tailwind directives
|
||||
- _Requirements: 14.1, 14.4, 13.2_
|
||||
|
||||
- [x] 1.2 Create shared TypeScript interfaces and formatting utilities
|
||||
- Create `src/lib/types.ts` with all data model interfaces (APIEnvelope, TokenResponse, WatchlistEntry, OHLCVBar, ScoreResponse, TradeSetup, SRLevel, SentimentResponse, FundamentalResponse, IndicatorResult, EMACrossResult, Ticker, AdminUser, SystemSetting, etc.)
|
||||
- Create `src/lib/format.ts` with `formatPrice`, `formatPercent`, `formatLargeNumber`, `formatDate`, `formatDateTime`
|
||||
- _Requirements: 13.4_
|
||||
|
||||
- [ ]* 1.3 Write property tests for formatting utilities
|
||||
- **Property 16: Number formatting**
|
||||
- **Validates: Requirements 13.4**
|
||||
- Install vitest, @testing-library/react, fast-check as dev dependencies
|
||||
- Create `frontend/vitest.config.ts` with jsdom environment
|
||||
- Create `frontend/tests/property/format.test.ts`
|
||||
- Test `formatPrice` always produces 2 decimal places, `formatPercent` ends with `%`, `formatLargeNumber` uses correct suffix
|
||||
|
||||
- [x] 2. API client and auth store
|
||||
- [x] 2.1 Create Axios API client with interceptors
|
||||
- Create `src/api/client.ts` with base URL `/api/v1/`, 30s timeout, JSON content type
|
||||
- Add request interceptor to attach Bearer token from auth store
|
||||
- Add response interceptor to unwrap `{ status, data, error }` envelope
|
||||
- Add 401 handler that clears auth store and redirects to login
|
||||
- Create `ApiError` class for typed error handling
|
||||
- _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5_
|
||||
|
||||
- [x] 2.2 Create Zustand auth store
|
||||
- Create `src/stores/authStore.ts` with token, username, role state
|
||||
- `login(token)` decodes JWT payload, extracts `sub` and `role`, persists to localStorage
|
||||
- `logout()` clears state and localStorage
|
||||
- Initialize from localStorage on store creation for session persistence
|
||||
- _Requirements: 1.1, 1.6_
|
||||
|
||||
- [x] 2.3 Create API module files for each domain
|
||||
- Create `src/api/auth.ts` (login, register)
|
||||
- Create `src/api/watchlist.ts` (list, add, remove)
|
||||
- Create `src/api/tickers.ts` (list, create, delete)
|
||||
- Create `src/api/scores.ts` (getScores, getRankings, updateWeights)
|
||||
- Create `src/api/trades.ts` (list)
|
||||
- Create `src/api/ohlcv.ts` (getOHLCV)
|
||||
- Create `src/api/indicators.ts` (getIndicator, getEMACross)
|
||||
- Create `src/api/sr-levels.ts` (getLevels)
|
||||
- Create `src/api/sentiment.ts` (getSentiment)
|
||||
- Create `src/api/fundamentals.ts` (getFundamentals)
|
||||
- Create `src/api/ingestion.ts` (fetchData)
|
||||
- Create `src/api/admin.ts` (users CRUD, settings, jobs, cleanup)
|
||||
- Create `src/api/health.ts` (check)
|
||||
- _Requirements: 12.1, 12.2_
|
||||
|
||||
- [ ]* 2.4 Write property tests for API client and auth store
|
||||
- **Property 1: Token storage round-trip**
|
||||
- **Property 2: Bearer token attachment**
|
||||
- **Property 5: API envelope unwrapping**
|
||||
- **Validates: Requirements 1.1, 1.3, 1.6, 12.2, 12.3**
|
||||
|
||||
- [x] 3. Checkpoint — Verify foundation
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 4. Routing, layout, and auth pages
|
||||
- [x] 4.1 Create ProtectedRoute component and router setup
|
||||
- Create `src/components/auth/ProtectedRoute.tsx` — redirects to `/login` if no token, redirects non-admin away from admin routes
|
||||
- Set up React Router in `src/App.tsx` with route structure from design (login, register, protected shell with watchlist, ticker detail, scanner, rankings, admin)
|
||||
- _Requirements: 2.1, 2.2, 2.3, 2.4_
|
||||
|
||||
- [x] 4.2 Create AppShell layout with sidebar navigation
|
||||
- Create `src/components/layout/AppShell.tsx` — sidebar + main content area with `<Outlet />`
|
||||
- Create `src/components/layout/Sidebar.tsx` — nav links to watchlist, scanner, rankings, admin (admin link only if role is admin)
|
||||
- Create `src/components/layout/MobileNav.tsx` — hamburger menu for screens < 1024px
|
||||
- Apply dark color scheme with Tailwind dark mode classes
|
||||
- Add smooth transitions (150-300ms) for navigation and interactive elements
|
||||
- _Requirements: 2.5, 13.1, 13.2, 13.3_
|
||||
|
||||
- [x] 4.3 Create Login and Register pages
|
||||
- Create `src/pages/LoginPage.tsx` with username/password form, calls `useAuth().login` mutation
|
||||
- Create `src/pages/RegisterPage.tsx` with username (min 1 char) / password (min 6 chars) validation, calls `useAuth().register` mutation
|
||||
- Create `src/hooks/useAuth.ts` with login/register/logout mutations using TanStack Query
|
||||
- Display API errors via toast on failure, redirect to watchlist on login success
|
||||
- _Requirements: 1.1, 1.2, 1.4, 1.5_
|
||||
|
||||
- [x] 4.4 Create shared UI components
|
||||
- Create `src/components/ui/Toast.tsx` — toast context + portal, auto-dismiss 4s, max 3 visible, color-coded (red/green/blue)
|
||||
- Create `src/components/ui/Skeleton.tsx` — Tailwind `animate-pulse` placeholder blocks
|
||||
- Create `src/components/ui/Badge.tsx` — small label component for entry types
|
||||
- Create `src/components/ui/ConfirmDialog.tsx` — modal confirmation dialog
|
||||
- Create `src/components/ui/ScoreCard.tsx` — composite score display with colored ring (green > 70, yellow 40-70, red < 40) and dimension breakdown
|
||||
- _Requirements: 13.3, 13.5, 1.5_
|
||||
|
||||
- [ ]* 4.5 Write property tests for routing and registration validation
|
||||
- **Property 3: Registration form validation**
|
||||
- **Property 4: Route protection based on auth state**
|
||||
- **Validates: Requirements 1.2, 2.1, 2.2**
|
||||
|
||||
- [x] 5. Checkpoint — Verify auth flow and navigation
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 6. Watchlist view
|
||||
- [x] 6.1 Create TanStack Query hooks for watchlist
|
||||
- Create `src/hooks/useWatchlist.ts` with `useWatchlist()` query, `useAddToWatchlist()` mutation, `useRemoveFromWatchlist()` mutation
|
||||
- Invalidate watchlist query on add/remove success
|
||||
- Surface errors to toast system
|
||||
- _Requirements: 3.1, 3.3, 3.4, 3.5_
|
||||
|
||||
- [x] 6.2 Create WatchlistPage and sub-components
|
||||
- Create `src/pages/WatchlistPage.tsx` — fetches watchlist, renders table/cards, loading skeletons, error state
|
||||
- Create `src/components/watchlist/WatchlistTable.tsx` — displays symbol (clickable → `/ticker/{symbol}`), entry type badge (auto/manual), composite score, dimension scores, R:R ratio, R:R direction, nearest S/R levels, remove button
|
||||
- Create `src/components/watchlist/AddTickerForm.tsx` — input + submit to add symbol to watchlist
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.6, 3.7_
|
||||
|
||||
- [ ]* 6.3 Write property tests for watchlist rendering
|
||||
- **Property 6: Watchlist entry rendering completeness**
|
||||
- **Property 7: Symbol click navigation** (watchlist portion)
|
||||
- **Validates: Requirements 3.2, 3.6, 3.7**
|
||||
|
||||
- [x] 7. Ticker detail view
|
||||
- [x] 7.1 Create TanStack Query hooks for ticker detail
|
||||
- Create `src/hooks/useTickerDetail.ts` with parallel queries for OHLCV, scores, S/R levels, sentiment, fundamentals
|
||||
- Each query is independent — partial failure renders available sections
|
||||
- _Requirements: 4.1, 4.9_
|
||||
|
||||
- [x] 7.2 Create TickerDetailPage with chart and data panels
|
||||
- Create `src/pages/TickerDetailPage.tsx` — orchestrates parallel data fetching, renders sections with independent loading/error states
|
||||
- Create `src/components/charts/CandlestickChart.tsx` — Recharts ComposedChart with custom Bar shapes for OHLCV candles, date x-axis, price y-axis
|
||||
- Create `src/components/ticker/SROverlay.tsx` — renders S/R levels as ReferenceLine components on chart (green = support, red = resistance)
|
||||
- Render ScoreCard for composite + dimension scores
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.9_
|
||||
|
||||
- [x] 7.3 Create sentiment, fundamentals, and indicator panels
|
||||
- Create `src/components/ticker/SentimentPanel.tsx` — displays classification, confidence, dimension score
|
||||
- Create `src/components/ticker/FundamentalsPanel.tsx` — displays P/E, revenue growth, earnings surprise, market cap (placeholder for nulls)
|
||||
- Create `src/components/ticker/IndicatorSelector.tsx` — dropdown to select indicator type (ADX, EMA, RSI, ATR, volume_profile, pivot_points), fetches from `/api/v1/indicators/{symbol}/{type}`, displays result with normalized score. Includes EMA cross signal display.
|
||||
- _Requirements: 4.5, 4.6, 4.7, 4.8_
|
||||
|
||||
- [x] 7.4 Add data ingestion trigger to ticker detail
|
||||
- Add fetch-data button to TickerDetailPage
|
||||
- POST to `/api/v1/ingestion/fetch/{symbol}`, show loading indicator on button, toast on success/failure, refresh OHLCV data on success
|
||||
- _Requirements: 10.1, 10.2, 10.3, 10.4_
|
||||
|
||||
- [ ]* 7.5 Write property tests for ticker detail components
|
||||
- **Property 8: Score card rendering**
|
||||
- **Property 9: Sentiment panel rendering**
|
||||
- **Property 10: Fundamentals panel rendering**
|
||||
- **Validates: Requirements 4.4, 4.5, 4.6**
|
||||
|
||||
- [x] 8. Checkpoint — Verify watchlist and ticker detail
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 9. Scanner view
|
||||
- [x] 9.1 Create TanStack Query hooks and scanner page
|
||||
- Create `src/hooks/useTrades.ts` with `useTrades()` query
|
||||
- Create `src/pages/ScannerPage.tsx` — fetches trade setups, renders filter controls and table, loading skeletons
|
||||
- Create `src/components/scanner/TradeTable.tsx` — sortable table displaying symbol (clickable → `/ticker/{symbol}`), direction, entry price, stop loss, target, R:R ratio, composite score, detection timestamp
|
||||
- Add filter controls: minimum R:R numeric input, direction dropdown (long/short/both)
|
||||
- Add column sorting (R:R ratio, composite score, symbol, detection time) with ascending/descending toggle
|
||||
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6_
|
||||
|
||||
- [ ]* 9.2 Write property tests for scanner filtering and sorting
|
||||
- **Property 11: Trade setup rendering**
|
||||
- **Property 12: Scanner filtering**
|
||||
- **Property 13: Scanner sorting**
|
||||
- **Validates: Requirements 5.2, 5.3, 5.4, 5.5**
|
||||
|
||||
- [x] 10. Rankings view
|
||||
- [x] 10.1 Create TanStack Query hooks and rankings page
|
||||
- Create `src/hooks/useScores.ts` with `useRankings()` query, `useUpdateWeights()` mutation
|
||||
- Create `src/pages/RankingsPage.tsx` — fetches rankings, renders table sorted by composite score descending, displays current weights
|
||||
- Create `src/components/rankings/RankingsTable.tsx` — displays rank position, symbol (clickable → `/ticker/{symbol}`), composite score, all dimension scores
|
||||
- Create `src/components/rankings/WeightsForm.tsx` — editable numeric inputs per dimension, submit updates weights via PUT, refreshes rankings on success
|
||||
- _Requirements: 6.1, 6.2, 6.3, 6.4, 11.1, 11.2, 11.3, 11.4_
|
||||
|
||||
- [ ]* 10.2 Write property tests for rankings and weights
|
||||
- **Property 14: Rankings display order**
|
||||
- **Property 17: Weights form rendering**
|
||||
- **Validates: Requirements 6.1, 6.2, 11.1**
|
||||
|
||||
- [x] 11. Checkpoint — Verify scanner and rankings
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 12. Ticker management
|
||||
- [x] 12.1 Create TanStack Query hooks and ticker management UI
|
||||
- Create `src/hooks/useTickers.ts` with `useTickers()` query, `useAddTicker()` mutation, `useDeleteTicker()` mutation
|
||||
- Add ticker list display to an appropriate location (e.g., admin page or dedicated section)
|
||||
- Add ticker form for adding new symbols
|
||||
- Delete button triggers ConfirmDialog before sending DELETE request
|
||||
- Remove ticker from display on successful delete without full page reload
|
||||
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
|
||||
|
||||
- [x] 13. Admin panel
|
||||
- [x] 13.1 Create admin hooks and user management section
|
||||
- Create `src/hooks/useAdmin.ts` with queries and mutations for users, settings, jobs, cleanup
|
||||
- Create `src/pages/AdminPage.tsx` — tabbed layout with user management, settings, jobs, data cleanup sections
|
||||
- Create `src/components/admin/UserTable.tsx` — displays username, role, access status; toggle access, reset password controls
|
||||
- Add create-user form (username, password, role, access flag)
|
||||
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_
|
||||
|
||||
- [x] 13.2 Create settings, jobs, and data cleanup sections
|
||||
- Create `src/components/admin/SettingsForm.tsx` — editable fields for each setting, registration toggle
|
||||
- Create `src/components/admin/JobControls.tsx` — toggle on/off per job, manual trigger button, toast confirmation
|
||||
- Create `src/components/admin/DataCleanup.tsx` — older-than-days input, submit cleanup, display result via toast
|
||||
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6_
|
||||
|
||||
- [ ]* 13.3 Write property test for admin user table rendering
|
||||
- **Property 15: Admin user table rendering**
|
||||
- **Validates: Requirements 7.2**
|
||||
|
||||
- [x] 14. Final wiring and polish
|
||||
- [x] 14.1 Add health check and loading states
|
||||
- Create health check query using `GET /api/v1/health` — display backend status indicator in sidebar
|
||||
- Ensure all pages show Skeleton placeholders during loading
|
||||
- Ensure all mutation errors surface through Toast system consistently
|
||||
- _Requirements: 14.5, 13.5, 12.4_
|
||||
|
||||
- [x] 14.2 Configure production build
|
||||
- Verify `vite build` outputs to `frontend/dist/` with hashed filenames
|
||||
- Add Nginx config snippet in comments or README for `try_files $uri $uri/ /index.html` and `/api/v1/` proxy
|
||||
- _Requirements: 14.1, 14.2, 14.3, 14.4_
|
||||
|
||||
- [x] 15. Final checkpoint — Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tasks marked with `*` are optional property test tasks and can be skipped for faster MVP
|
||||
- Each task references specific requirements for traceability
|
||||
- Backend API is already running — no backend changes needed
|
||||
- All 17 correctness properties are covered across optional test tasks
|
||||
- Checkpoints are placed after each major phase for incremental validation
|
||||
1
.kiro/specs/stock-data-backend/.config.kiro
Normal file
1
.kiro/specs/stock-data-backend/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"specId": "fa730cf4-a14d-4f62-8993-fd7db6fe25cc", "workflowType": "requirements-first", "specType": "feature"}
|
||||
1121
.kiro/specs/stock-data-backend/design.md
Normal file
1121
.kiro/specs/stock-data-backend/design.md
Normal file
File diff suppressed because it is too large
Load Diff
221
.kiro/specs/stock-data-backend/requirements.md
Normal file
221
.kiro/specs/stock-data-backend/requirements.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
This document defines the requirements for the Stock Data Backend — an opinionated investing-signal platform built with Python/FastAPI and PostgreSQL, focused on NASDAQ stocks. The platform's philosophy: find the path of least resistance (trend direction), identify key support/resistance zones, detect asymmetric risk-reward setups, and surface the best opportunities through a unified scoring pipeline. It does not attempt to predict price — it identifies where conditions are most favorable.
|
||||
|
||||
Every data source (OHLCV, technical indicators, sentiment, fundamentals) feeds into a single composite scoring and ranking system that auto-populates a watchlist and flags trade setups. Data ingestion is exclusively via the configured market data provider — users do not upload data directly.
|
||||
|
||||
This is an MVP focused on delivering actionable signals. Engineering concerns (API format, database indexing, logging, connection pooling, graceful shutdown) are design constraints, not requirements.
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Backend_Service**: The FastAPI-based Python web application that exposes REST API endpoints.
|
||||
- **Ticker**: A unique NASDAQ stock symbol (e.g., AAPL, MSFT) being tracked by the system.
|
||||
- **OHLCV_Record**: A single price data point containing Open, High, Low, Close, and Volume values for a specific Ticker on a specific date.
|
||||
- **Ticker_Registry**: The subsystem responsible for adding, removing, listing, and looking up tracked NASDAQ tickers.
|
||||
- **Price_Store**: The subsystem responsible for persisting and retrieving OHLCV price data in PostgreSQL.
|
||||
- **Ingestion_Pipeline**: The subsystem responsible for importing stock data into the Price_Store via the configured market data provider.
|
||||
- **Data_Collector**: A scheduled job that periodically fetches the latest price data for all tracked tickers and upserts it into the Price_Store.
|
||||
- **Auth_Service**: The subsystem responsible for user registration, login, JWT token management, and role-based access control.
|
||||
- **User**: A registered account with a username, hashed password, and assigned role (user or admin).
|
||||
- **Admin**: A User with the admin role who can manage other users and configure system settings.
|
||||
- **Access_Token**: A JWT token issued upon login, expires after 60 minutes.
|
||||
- **ADX**: Average Directional Index — measures trend strength (0-100). Values above 25 indicate a strong trend.
|
||||
- **EMA**: Exponential Moving Average — configurable period. EMA Cross (e.g., 20/50) determines directional bias.
|
||||
- **RSI**: Relative Strength Index — momentum oscillator (0-100). Overbought >70, oversold <30.
|
||||
- **ATR**: Average True Range — measures price volatility. Used for stop-loss and target placement.
|
||||
- **Volume_Profile**: Distribution of traded volume across price levels, producing POC, Value Area, HVN, and LVN.
|
||||
- **POC**: Point of Control — price level with highest traded volume.
|
||||
- **HVN**: High Volume Node — above-average volume level, acts as support/resistance magnet.
|
||||
- **LVN**: Low Volume Node — below-average volume level, acts as breakout zone.
|
||||
- **Pivot_Point**: A support or resistance level from swing highs and swing lows.
|
||||
- **SR_Level**: A support or resistance level tagged with type, strength score, and detection method.
|
||||
- **SR_Detector**: The subsystem that auto-calculates support and resistance levels.
|
||||
- **Sentiment_Score**: A record containing bullish/bearish/neutral classification, confidence (0-100), source, and timestamp for a Ticker.
|
||||
- **Fundamental_Data**: Key financial metrics: P/E ratio, revenue growth rate, earnings surprise %, and market cap.
|
||||
- **Composite_Score**: A weighted aggregate score (0-100) from all dimension scores for a Ticker.
|
||||
- **Dimension_Score**: A normalized score (0-100) for a single analysis dimension (technical, S/R quality, sentiment, fundamental, momentum).
|
||||
- **Scoring_Engine**: The subsystem that computes dimension scores, applies weights, and produces Composite_Scores.
|
||||
- **RR_Scanner**: The subsystem that scans for asymmetric risk-reward trade setups.
|
||||
- **Trade_Setup**: A detected trade opportunity with entry, stop-loss, target, R:R ratio, direction (long/short), and Composite_Score.
|
||||
- **Watchlist**: A curated list of top-ranked tickers from the Scoring_Engine, with manual add/remove support.
|
||||
- **System_Settings**: Persisted configuration values managed by admins.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement 1: Ticker Management
|
||||
|
||||
**User Story:** As a user, I want to manage the NASDAQ tickers I am tracking, so that I can control which stocks the system analyzes.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 1.1 WHEN a user submits a valid NASDAQ ticker symbol, THE Ticker_Registry SHALL create a new ticker entry and return the created ticker with its metadata.
|
||||
- 1.2 WHEN a user submits a ticker symbol that already exists, THE Backend_Service SHALL return a duplicate error.
|
||||
- 1.3 WHEN a user submits an empty or whitespace-only ticker symbol, THE Backend_Service SHALL reject the request with a validation error.
|
||||
- 1.4 WHEN a user requests the list of tracked tickers, THE Ticker_Registry SHALL return all tickers sorted alphabetically by symbol.
|
||||
- 1.5 WHEN a user requests deletion of a tracked ticker, THE Ticker_Registry SHALL remove the ticker and all associated data (OHLCV, scores, setups).
|
||||
- 1.6 WHEN a user requests deletion of a ticker that does not exist, THE Backend_Service SHALL return a not-found error.
|
||||
|
||||
### Requirement 2: OHLCV Price Data Storage
|
||||
|
||||
**User Story:** As a user, I want the system to store historical OHLCV price data, so that technical analysis and signal detection have a data foundation.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 2.1 THE Price_Store SHALL persist each OHLCV_Record with: ticker symbol, date, open, high, low, close, and volume.
|
||||
- 2.2 THE Price_Store SHALL enforce uniqueness on (ticker symbol, date).
|
||||
- 2.3 THE Backend_Service SHALL reject OHLCV_Records where high < low, any price is negative, volume is negative, or date is in the future.
|
||||
- 2.4 THE Backend_Service SHALL reject OHLCV_Records for tickers not in the Ticker_Registry.
|
||||
|
||||
### Requirement 3: Data Ingestion
|
||||
|
||||
**User Story:** As a user, I want the system to fetch stock data from the market data provider, so that my price history stays current.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 3.1 WHEN a user requests a data fetch for a ticker and date range, THE Ingestion_Pipeline SHALL fetch from the configured provider and upsert into the Price_Store.
|
||||
- 3.2 IF the provider is unreachable or errors, THE Ingestion_Pipeline SHALL return a descriptive error without modifying existing data.
|
||||
- 3.3 IF the provider returns a rate-limit error, THE Ingestion_Pipeline SHALL record progress and return a response indicating how many records were ingested, so the fetch can be resumed without gaps.
|
||||
- 3.4 WHEN a rate-limited fetch is resumed for the same ticker and date range, THE Ingestion_Pipeline SHALL continue from the last successfully ingested date.
|
||||
|
||||
### Requirement 4: Scheduled Data Collection
|
||||
|
||||
**User Story:** As a user, I want the system to automatically fetch the latest price data on a schedule, so that my data stays current without manual intervention.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 4.1 THE Data_Collector SHALL periodically fetch the latest daily OHLCV data for all tracked tickers.
|
||||
- 4.2 THE Data_Collector SHALL upsert records, updating existing ones if they already exist.
|
||||
- 4.3 WHEN the Data_Collector encounters an error for a specific ticker, it SHALL log the error and continue with remaining tickers.
|
||||
- 4.4 THE Data_Collector SHALL be configurable for frequency (daily, hourly) via configuration.
|
||||
- 4.5 IF a rate limit is hit during collection, THE Data_Collector SHALL record the last successful ticker and resume from there on the next run.
|
||||
|
||||
### Requirement 5: Technical Analysis
|
||||
|
||||
**User Story:** As a user, I want the system to compute key technical indicators, so that trend strength, momentum, and volatility feed into the scoring pipeline.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 5.1 THE Backend_Service SHALL compute the following from OHLCV data: ADX, EMA (default periods 20 and 50), RSI (default 14-period), ATR (default 14-period), Volume_Profile (POC, Value Area, HVN, LVN), and Pivot_Points (swing highs/lows).
|
||||
- 5.2 WHEN an indicator is requested for a Ticker and date range, THE Backend_Service SHALL return both raw values and a normalized score (0-100).
|
||||
- 5.3 WHEN an EMA Cross signal is requested, THE Backend_Service SHALL compare short vs long EMA and return directional bias (bullish, bearish, neutral).
|
||||
- 5.4 IF insufficient data exists to compute an indicator, THE Backend_Service SHALL return an error indicating the minimum data requirement.
|
||||
|
||||
### Requirement 6: Support/Resistance Detection
|
||||
|
||||
**User Story:** As a user, I want the system to auto-calculate support and resistance levels, so that I can see key price zones where buying or selling pressure concentrates.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 6.1 THE SR_Detector SHALL identify SR_Levels from Volume_Profile (HVN/LVN zones) and from Pivot_Points (swing highs/lows).
|
||||
- 6.2 THE SR_Detector SHALL assign each level a strength score (0-100) based on how many times price has respected that level.
|
||||
- 6.3 THE SR_Detector SHALL tag each level as "support" or "resistance" relative to current price.
|
||||
- 6.4 WHEN new OHLCV data arrives for a Ticker, THE SR_Detector SHALL recalculate its SR_Levels.
|
||||
- 6.5 THE SR_Detector SHALL merge levels from different methods within a configurable price tolerance (default 0.5%) into a single consolidated level.
|
||||
- 6.6 WHEN a user requests SR_Levels for a Ticker, they SHALL be returned sorted by strength descending with detection method indicated.
|
||||
|
||||
### Requirement 7: Sentiment Data
|
||||
|
||||
**User Story:** As a user, I want sentiment data to feed into the scoring pipeline, so that social mood is factored into signal detection.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 7.1 THE Backend_Service SHALL periodically collect sentiment data for all tracked tickers from a configured source at a configurable interval (default 30 minutes).
|
||||
- 7.2 EACH Sentiment_Score SHALL contain: classification (bullish/bearish/neutral), confidence (0-100), source identifier, and timestamp.
|
||||
- 7.3 IF the sentiment source is unreachable, THE Backend_Service SHALL log the error and retain existing data.
|
||||
- 7.4 WHEN computing the sentiment Dimension_Score, THE Scoring_Engine SHALL aggregate recent scores within a configurable lookback window (default 24h) using configurable source weights and time decay.
|
||||
|
||||
### Requirement 8: Fundamental Data
|
||||
|
||||
**User Story:** As a user, I want key fundamental metrics to feed into the scoring pipeline, so that financial quality is factored into signal detection.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 8.1 THE Backend_Service SHALL fetch and store Fundamental_Data for each tracked Ticker: P/E ratio, revenue growth rate, earnings surprise %, and market cap.
|
||||
- 8.2 THE Data_Collector SHALL periodically fetch updated Fundamental_Data (default daily).
|
||||
- 8.3 IF the data source is unreachable, THE Backend_Service SHALL log the error and retain the most recent data.
|
||||
- 8.4 WHEN new Fundamental_Data arrives, THE Scoring_Engine SHALL mark the fundamental Dimension_Score as stale.
|
||||
|
||||
### Requirement 9: Composite Scoring and Ranking
|
||||
|
||||
**User Story:** As a user, I want each stock scored across all dimensions with configurable weights, so that I can rank stocks by a single unified metric tuned to my preferences.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 9.1 THE Scoring_Engine SHALL compute a Dimension_Score (0-100) per Ticker for: technical, S/R quality, sentiment, fundamental, and momentum.
|
||||
- 9.2 THE Scoring_Engine SHALL compute a Composite_Score as the weighted average of available Dimension_Scores using user-configurable weights.
|
||||
- 9.3 WHEN a Ticker is missing data for one or more dimensions, THE Scoring_Engine SHALL use only available dimensions (re-normalizing weights) and indicate which are missing.
|
||||
- 9.4 WHEN underlying data changes, THE Scoring_Engine SHALL mark the affected Composite_Score as stale.
|
||||
- 9.5 WHEN a stale score is requested, THE Scoring_Engine SHALL recompute on-demand. No background recomputation.
|
||||
- 9.6 WHEN a user requests rankings, THE Scoring_Engine SHALL return tickers sorted by Composite_Score descending with all Dimension_Scores included.
|
||||
- 9.7 WHEN a user updates dimension weights, THE Scoring_Engine SHALL recompute all Composite_Scores.
|
||||
|
||||
### Requirement 10: Asymmetric R:R Trade Detection
|
||||
|
||||
**User Story:** As a user, I want the system to scan for trade setups with favorable risk-reward ratios, so that I see highly asymmetric opportunities without manual chart analysis.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 10.1 THE RR_Scanner SHALL periodically scan all tracked tickers for Trade_Setups meeting a configurable R:R threshold (default 3:1).
|
||||
- 10.2 FOR long setups: target = nearest SR_Level above price, stop = ATR-based distance below price.
|
||||
- 10.3 FOR short setups: target = nearest SR_Level below price, stop = ATR-based distance above price.
|
||||
- 10.4 EACH Trade_Setup SHALL include: entry price, stop-loss, target, R:R ratio, direction (long/short), and Composite_Score.
|
||||
- 10.5 WHEN underlying SR_Levels or price data changes, THE RR_Scanner SHALL recalculate and remove setups that no longer meet the threshold.
|
||||
- 10.6 THE RR_Scanner SHALL be configurable for scan frequency via configuration.
|
||||
- 10.7 IF a Ticker lacks sufficient SR_Levels or ATR data, THE RR_Scanner SHALL skip it and log the reason.
|
||||
- 10.8 WHEN a user requests trade setups, results SHALL be sorted by R:R descending (secondary: Composite_Score descending), with optional direction filter.
|
||||
|
||||
### Requirement 11: Watchlist
|
||||
|
||||
**User Story:** As a user, I want a watchlist of top-ranked stocks that auto-populates from scoring, so that I always have a curated shortlist of the best opportunities.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 11.1 THE Watchlist SHALL auto-include the top-X tickers by Composite_Score (X configurable, default 10).
|
||||
- 11.2 WHEN requested, THE Watchlist SHALL return each entry with Composite_Score, Dimension_Scores, R:R ratio (if setup exists), and active SR_Levels.
|
||||
- 11.3 Users MAY manually add/remove tickers. Manual additions are tagged and not subject to auto-population rules.
|
||||
- 11.4 THE Watchlist SHALL enforce a max size of auto-populate count + 10 manual additions (default max 20).
|
||||
- 11.5 WHEN Composite_Scores are recomputed, auto-populated entries SHALL update to reflect new rankings.
|
||||
- 11.6 THE Watchlist SHALL be sortable by Composite_Score, any Dimension_Score, or R:R ratio.
|
||||
|
||||
### Requirement 12: User Authentication
|
||||
|
||||
**User Story:** As a system owner, I want user registration and login with role-based access, so that only authorized users can access signals and analysis.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 12.1 WHEN registration is enabled and valid credentials are submitted, THE Auth_Service SHALL create a User with no API access by default.
|
||||
- 12.2 WHEN registration is disabled, THE Auth_Service SHALL reject registration.
|
||||
- 12.3 WHEN valid login credentials are submitted, THE Auth_Service SHALL return an Access_Token (60-minute expiry).
|
||||
- 12.4 WHEN invalid credentials are submitted, THE Auth_Service SHALL return an error without revealing which field was wrong.
|
||||
- 12.5 Unauthenticated requests to protected endpoints SHALL receive 401. Authenticated users without granted access SHALL receive 403.
|
||||
- 12.6 WHEN a token expires, THE Backend_Service SHALL return 401 indicating expiration.
|
||||
|
||||
### Requirement 13: Admin Management
|
||||
|
||||
**User Story:** As an admin, I want to manage users, control system settings, and perform data maintenance.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
- 13.1 WHEN the system initializes for the first time, a default admin account SHALL be created (username: "admin", password: "admin").
|
||||
- 13.2 Admins SHALL be able to: grant/revoke user access, toggle registration, list all users, reset user passwords, and create new user accounts.
|
||||
- 13.3 Admins SHALL be able to: enable/disable scheduled jobs, update system settings (frequencies, thresholds, weights, watchlist size), and trigger manual job runs.
|
||||
- 13.4 Admins SHALL be able to delete all data older than a specified number of days (OHLCV, sentiment, fundamentals). Ticker entries, user accounts, and latest scores SHALL be preserved.
|
||||
- 13.5 Admin endpoints SHALL be restricted to users with the admin role.
|
||||
|
||||
## Design Constraints
|
||||
|
||||
The following are engineering concerns to be addressed during design, not user-facing requirements:
|
||||
|
||||
- Consistent JSON API envelope (status, data, error fields) with appropriate HTTP status codes
|
||||
- OpenAPI/Swagger documentation endpoint
|
||||
- Versioned URL prefixes (/api/v1/)
|
||||
- Composite database index on (ticker, date) for range query performance
|
||||
- Date-only storage for OHLCV (no time component)
|
||||
- Database migrations for schema management
|
||||
- Structured JSON logging with configurable levels
|
||||
- Database connection pooling (default 5 connections)
|
||||
- Health check endpoint (unauthenticated)
|
||||
- Graceful shutdown (complete in-flight requests, stop jobs, close pool)
|
||||
- Market data provider behind an interface/protocol for swappability
|
||||
255
.kiro/specs/stock-data-backend/tasks.md
Normal file
255
.kiro/specs/stock-data-backend/tasks.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Implementation Plan: Stock Data Backend
|
||||
|
||||
## Overview
|
||||
|
||||
Incremental build of the investing-signal platform: foundation first (config, DB, models, auth), then domain services (tickers, OHLCV, ingestion, indicators, S/R, sentiment, fundamentals), then scoring/ranking (scoring engine, R:R scanner, watchlist), then scheduled jobs, deployment templates, and final wiring. Each step builds on the previous and ends integrated.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Project scaffolding, configuration, and database foundation
|
||||
- [x] 1.1 Create project structure with `pyproject.toml`, `.env.example`, `alembic.ini`, and `app/` package
|
||||
- Create `pyproject.toml` with dependencies: fastapi, uvicorn, sqlalchemy[asyncio], asyncpg, alembic, pydantic-settings, python-jose, passlib[bcrypt], apscheduler, httpx, alpaca-py, google-genai, hypothesis
|
||||
- Create `.env.example` with all environment variables from design
|
||||
- Create `app/__init__.py`, `app/config.py` (pydantic-settings `Settings` class)
|
||||
- Create `app/database.py` (async SQLAlchemy engine, session factory, connection pooling)
|
||||
- _Requirements: Design Constraints (connection pooling, config)_
|
||||
|
||||
- [x] 1.2 Create all SQLAlchemy ORM models and Alembic initial migration
|
||||
- Create `app/models/__init__.py` and model files: `ticker.py`, `ohlcv.py`, `user.py`, `sentiment.py`, `fundamental.py`, `score.py`, `sr_level.py`, `trade_setup.py`, `watchlist.py`, `settings.py`
|
||||
- Implement all 12 entities from the ERD: User, Ticker, OHLCVRecord, SentimentScore, FundamentalData, SRLevel, DimensionScore, CompositeScore, TradeSetup, WatchlistEntry, SystemSetting, IngestionProgress
|
||||
- Include composite unique constraints, indexes, and cascade deletes per design
|
||||
- Initialize Alembic (`alembic/env.py`) and generate initial migration
|
||||
- _Requirements: 2.1, 2.2, Design Constraints (composite index on ticker+date)_
|
||||
|
||||
- [x] 1.3 Create shared schemas, exception hierarchy, and API envelope
|
||||
- Create `app/schemas/common.py` with `APIEnvelope` model (status, data, error)
|
||||
- Create `app/middleware.py` with global exception handler mapping `AppError` subclasses to JSON envelope responses
|
||||
- Create exception classes: `AppError`, `ValidationError`, `NotFoundError`, `DuplicateError`, `AuthenticationError`, `AuthorizationError`, `ProviderError`, `RateLimitError`
|
||||
- _Requirements: Design Constraints (JSON envelope, HTTP status codes)_
|
||||
|
||||
- [x] 1.4 Create FastAPI app entry point with lifespan, health check, and dependency injection
|
||||
- Create `app/main.py` with FastAPI app, lifespan handler (DB pool startup/shutdown, default admin creation)
|
||||
- Create `app/dependencies.py` with `Depends()` factories for DB session, current user, admin guard
|
||||
- Create `app/routers/health.py` with unauthenticated `/api/v1/health` endpoint
|
||||
- Wire health router into app
|
||||
- _Requirements: 13.1, Design Constraints (health check, graceful shutdown, versioned URLs)_
|
||||
|
||||
- [x] 2. Authentication and admin services
|
||||
- [x] 2.1 Implement Auth Service and auth router
|
||||
- Create `app/services/auth_service.py`: registration (configurable on/off, creates no-access user), login (bcrypt verify, JWT generation with 60-min expiry), token validation
|
||||
- Create `app/schemas/auth.py`: RegisterRequest, LoginRequest, TokenResponse
|
||||
- Create `app/routers/auth.py`: `POST /api/v1/auth/register`, `POST /api/v1/auth/login`
|
||||
- Implement JWT middleware in `app/dependencies.py` for `get_current_user` and `require_admin`
|
||||
- _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6_
|
||||
|
||||
- [ ]* 2.2 Write property tests for auth (Properties 34-38)
|
||||
- **Property 34: Registration creates no-access user** — _Validates: Requirements 12.1_
|
||||
- **Property 35: Registration disabled rejects all attempts** — _Validates: Requirements 12.2_
|
||||
- **Property 36: Login returns valid JWT** — _Validates: Requirements 12.3_
|
||||
- **Property 37: Invalid credentials return generic error** — _Validates: Requirements 12.4_
|
||||
- **Property 38: Access control enforcement** — _Validates: Requirements 12.5_
|
||||
|
||||
- [x] 2.3 Implement Admin Service and admin router
|
||||
- Create `app/services/admin_service.py`: grant/revoke access, toggle registration, list users, reset passwords, create accounts, system settings CRUD, data cleanup (delete old OHLCV/sentiment/fundamentals preserving tickers/users/scores), job control
|
||||
- Create `app/schemas/admin.py`: UserManagement, SystemSettingUpdate, DataCleanupRequest
|
||||
- Create `app/routers/admin.py`: admin-only endpoints under `/api/v1/admin/`
|
||||
- _Requirements: 13.1, 13.2, 13.3, 13.4, 13.5_
|
||||
|
||||
- [ ]* 2.4 Write property tests for admin (Properties 39-40)
|
||||
- **Property 39: Admin user management operations** — _Validates: Requirements 13.2_
|
||||
- **Property 40: Data cleanup preserves structure** — _Validates: Requirements 13.4_
|
||||
|
||||
- [x] 3. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 4. Ticker management and OHLCV price storage
|
||||
- [x] 4.1 Implement Ticker Registry service and router
|
||||
- Create `app/services/ticker_service.py`: add (validate non-empty, uppercase, alphanumeric, check uniqueness), delete (cascade all associated data), list (sorted alphabetically)
|
||||
- Create `app/schemas/ticker.py`: TickerCreate, TickerResponse
|
||||
- Create `app/routers/tickers.py`: `POST /api/v1/tickers`, `GET /api/v1/tickers`, `DELETE /api/v1/tickers/{symbol}`
|
||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
|
||||
|
||||
- [ ]* 4.2 Write property tests for ticker management (Properties 1-4)
|
||||
- **Property 1: Ticker creation round-trip** — _Validates: Requirements 1.1_
|
||||
- **Property 2: Duplicate ticker rejection** — _Validates: Requirements 1.2_
|
||||
- **Property 3: Whitespace ticker rejection** — _Validates: Requirements 1.3_
|
||||
- **Property 4: Ticker deletion cascades** — _Validates: Requirements 1.5_
|
||||
|
||||
- [x] 4.3 Implement Price Store service and OHLCV router
|
||||
- Create `app/services/price_service.py`: upsert OHLCV (validate high >= low, prices >= 0, volume >= 0, date <= today, ticker exists), query by ticker + date range
|
||||
- Create `app/schemas/ohlcv.py`: OHLCVCreate, OHLCVResponse
|
||||
- Create `app/routers/ohlcv.py`: `POST /api/v1/ohlcv`, `GET /api/v1/ohlcv/{symbol}`
|
||||
- On upsert: invalidate LRU cache for ticker, mark composite score as stale
|
||||
- _Requirements: 2.1, 2.2, 2.3, 2.4_
|
||||
|
||||
- [ ]* 4.4 Write property tests for OHLCV (Properties 5-7)
|
||||
- **Property 5: OHLCV storage round-trip** — _Validates: Requirements 2.1, 2.2_
|
||||
- **Property 6: OHLCV validation rejects invalid records** — _Validates: Requirements 2.3_
|
||||
- **Property 7: OHLCV rejects unregistered tickers** — _Validates: Requirements 2.4_
|
||||
|
||||
- [x] 5. Market data provider and ingestion pipeline
|
||||
- [x] 5.1 Implement provider protocols and concrete implementations
|
||||
- Create `app/providers/protocol.py`: `MarketDataProvider` Protocol (fetch_ohlcv), `SentimentProvider` Protocol (fetch_sentiment), `FundamentalProvider` Protocol (fetch_fundamentals)
|
||||
- Create `app/providers/alpaca.py`: Alpaca OHLCV provider using `alpaca-py` SDK — fetches daily bars by ticker and date range
|
||||
- Create `app/providers/gemini_sentiment.py`: Gemini sentiment provider using `google-genai` with search grounding — sends structured prompt per ticker, parses JSON response (classification + confidence)
|
||||
- Create `app/providers/fmp.py`: Financial Modeling Prep fundamentals provider using `httpx` — fetches P/E, revenue growth, earnings surprise, market cap
|
||||
- _Requirements: Design Constraints (provider behind interface)_
|
||||
|
||||
- [x] 5.2 Implement Ingestion Pipeline service and router
|
||||
- Create `app/services/ingestion_service.py`: fetch + upsert with rate-limit handling (track `last_ingested_date`, return partial progress on rate limit, resume from last date + 1 day), provider error handling (descriptive error, no data modification)
|
||||
- Create `app/routers/ingestion.py`: `POST /api/v1/ingestion/fetch/{symbol}`
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4_
|
||||
|
||||
- [ ]* 5.3 Write property tests for ingestion (Properties 8-9)
|
||||
- **Property 8: Provider error preserves existing data** — _Validates: Requirements 3.2, 7.3, 8.3_
|
||||
- **Property 9: Rate-limit resume continuity** — _Validates: Requirements 3.3, 3.4, 4.5_
|
||||
|
||||
- [x] 6. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 7. Technical analysis and S/R detection
|
||||
- [x] 7.1 Implement LRU cache wrapper with invalidation
|
||||
- Create `app/cache.py`: LRU cache wrapper (max 1000 entries) keyed on ticker + date range + indicator type, with per-ticker invalidation method
|
||||
- _Requirements: Design Constraints (LRU cache)_
|
||||
|
||||
- [x] 7.2 Implement Technical Analysis service and indicators router
|
||||
- Create `app/services/indicator_service.py`: compute ADX (28+ bars), EMA (period+1 bars, default 20/50), RSI (15+ bars, 14-period), ATR (15+ bars, 14-period), Volume Profile (20+ bars, POC/Value Area/HVN/LVN), Pivot Points (5+ bars, swing highs/lows)
|
||||
- Each indicator returns raw values + normalized 0-100 score
|
||||
- Implement EMA cross signal (bullish/bearish/neutral based on short vs long EMA comparison)
|
||||
- Enforce minimum data requirements, return error if insufficient
|
||||
- Create `app/schemas/indicator.py`: IndicatorRequest, IndicatorResponse, EMACrossResponse
|
||||
- Create `app/routers/indicators.py`: `GET /api/v1/indicators/{symbol}/{indicator_type}`, `GET /api/v1/indicators/{symbol}/ema-cross`
|
||||
- _Requirements: 5.1, 5.2, 5.3, 5.4_
|
||||
|
||||
- [ ]* 7.3 Write property tests for indicators (Properties 11-14)
|
||||
- **Property 11: Score bounds invariant** — _Validates: Requirements 5.2, 6.2, 9.1_
|
||||
- **Property 12: Indicator minimum data enforcement** — _Validates: Requirements 5.4_
|
||||
- **Property 13: EMA cross directional bias** — _Validates: Requirements 5.3_
|
||||
- **Property 14: Indicator computation determinism** — _Validates: Requirements 5.1_
|
||||
|
||||
- [x] 7.4 Implement S/R Detector service and router
|
||||
- Create `app/services/sr_service.py`: detect SR levels from Volume Profile (HVN/LVN) and Pivot Points (swing highs/lows), assign strength scores (0-100 based on price respect count), merge levels within tolerance (default 0.5%), tag as support/resistance relative to current price, recalculate on new OHLCV data
|
||||
- Create `app/schemas/sr_level.py`: SRLevelResponse
|
||||
- Create `app/routers/sr_levels.py`: `GET /api/v1/sr-levels/{symbol}` (sorted by strength descending)
|
||||
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6_
|
||||
|
||||
- [ ]* 7.5 Write property tests for S/R detection (Properties 15-17)
|
||||
- **Property 15: SR level support/resistance tagging** — _Validates: Requirements 6.3_
|
||||
- **Property 16: SR level merging within tolerance** — _Validates: Requirements 6.5_
|
||||
- **Property 17: SR level detection from data** — _Validates: Requirements 6.1_
|
||||
|
||||
- [x] 8. Sentiment and fundamental data services
|
||||
- [x] 8.1 Implement Sentiment service and router
|
||||
- Create `app/services/sentiment_service.py`: store sentiment records (classification, confidence, source, timestamp), compute dimension score with time-decay weighted average over configurable lookback window (default 24h)
|
||||
- Create `app/schemas/sentiment.py`: SentimentResponse
|
||||
- Create `app/routers/sentiment.py`: `GET /api/v1/sentiment/{symbol}`
|
||||
- _Requirements: 7.1, 7.2, 7.3, 7.4_
|
||||
|
||||
- [ ]* 8.2 Write property tests for sentiment (Properties 18-19)
|
||||
- **Property 18: Sentiment score data shape** — _Validates: Requirements 7.2_
|
||||
- **Property 19: Sentiment dimension score uses time decay** — _Validates: Requirements 7.4_
|
||||
|
||||
- [x] 8.3 Implement Fundamental Data service and router
|
||||
- Create `app/services/fundamental_service.py`: store fundamental data (P/E, revenue growth, earnings surprise, market cap), mark fundamental dimension score as stale on new data
|
||||
- Create `app/schemas/fundamental.py`: FundamentalResponse
|
||||
- Create `app/routers/fundamentals.py`: `GET /api/v1/fundamentals/{symbol}`
|
||||
- _Requirements: 8.1, 8.2, 8.3, 8.4_
|
||||
|
||||
- [ ]* 8.4 Write property test for fundamentals (Property 20)
|
||||
- **Property 20: Fundamental data storage round-trip** — _Validates: Requirements 8.1_
|
||||
|
||||
- [x] 9. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 10. Scoring engine, R:R scanner, and watchlist
|
||||
- [x] 10.1 Implement Scoring Engine service and router
|
||||
- Create `app/services/scoring_service.py`: compute dimension scores (technical, sr_quality, sentiment, fundamental, momentum) each 0-100, compute composite score as weighted average of available dimensions with re-normalized weights, staleness marking/recomputation on demand, weight update triggers full recomputation
|
||||
- Create `app/schemas/score.py`: ScoreResponse, WeightUpdateRequest, RankingResponse
|
||||
- Create `app/routers/scores.py`: `GET /api/v1/scores/{symbol}`, `GET /api/v1/rankings`, `PUT /api/v1/scores/weights`
|
||||
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7_
|
||||
|
||||
- [ ]* 10.2 Write property tests for scoring (Properties 21-25)
|
||||
- **Property 21: Composite score is weighted average** — _Validates: Requirements 9.2_
|
||||
- **Property 22: Missing dimensions re-normalize weights** — _Validates: Requirements 9.3_
|
||||
- **Property 23: Staleness marking on data change** — _Validates: Requirements 9.4_
|
||||
- **Property 24: Stale score recomputation on demand** — _Validates: Requirements 9.5_
|
||||
- **Property 25: Weight update triggers full recomputation** — _Validates: Requirements 9.7_
|
||||
|
||||
- [x] 10.3 Implement R:R Scanner service and router
|
||||
- Create `app/services/rr_scanner_service.py`: scan tickers for trade setups (long: target = nearest SR above, stop = entry - ATR×multiplier; short: target = nearest SR below, stop = entry + ATR×multiplier), filter by R:R threshold (default 3:1), recalculate/prune on data change, skip tickers without sufficient SR/ATR data
|
||||
- Create `app/schemas/trade_setup.py`: TradeSetupResponse
|
||||
- Create `app/routers/trades.py`: `GET /api/v1/trades` (sorted by R:R desc, secondary composite desc, optional direction filter)
|
||||
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8_
|
||||
|
||||
- [ ]* 10.4 Write property tests for R:R scanner (Properties 26-29)
|
||||
- **Property 26: Trade setup R:R threshold filtering** — _Validates: Requirements 10.1_
|
||||
- **Property 27: Trade setup computation correctness** — _Validates: Requirements 10.2, 10.3_
|
||||
- **Property 28: Trade setup data completeness** — _Validates: Requirements 10.4_
|
||||
- **Property 29: Trade setup pruning on data change** — _Validates: Requirements 10.5_
|
||||
|
||||
- [x] 10.5 Implement Watchlist service and router
|
||||
- Create `app/services/watchlist_service.py`: auto-populate top-X by composite score (default 10), manual add/remove (tagged, not subject to auto-population), enforce cap (auto + 10 manual, default max 20), update auto entries on score recomputation
|
||||
- Create `app/schemas/watchlist.py`: WatchlistEntryResponse (includes composite score, dimension scores, R:R ratio, SR levels)
|
||||
- Create `app/routers/watchlist.py`: `GET /api/v1/watchlist`, `POST /api/v1/watchlist/{symbol}`, `DELETE /api/v1/watchlist/{symbol}` (sortable by composite, dimension, or R:R)
|
||||
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5, 11.6_
|
||||
|
||||
- [ ]* 10.6 Write property tests for watchlist (Properties 30-33)
|
||||
- **Property 30: Watchlist auto-population** — _Validates: Requirements 11.1_
|
||||
- **Property 31: Watchlist entry data completeness** — _Validates: Requirements 11.2_
|
||||
- **Property 32: Manual watchlist entries persist through auto-population** — _Validates: Requirements 11.3_
|
||||
- **Property 33: Watchlist size cap enforcement** — _Validates: Requirements 11.4_
|
||||
|
||||
- [x] 11. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 12. Scheduled jobs and sorting correctness
|
||||
- [x] 12.1 Implement APScheduler job definitions and scheduler integration
|
||||
- Create `app/scheduler.py`: define scheduled jobs for Data Collector (OHLCV fetch for all tickers, configurable frequency), Sentiment Collector (default 30 min), Fundamental Collector (default daily), R:R Scanner (configurable frequency)
|
||||
- Each job: process all tracked tickers independently (one failure doesn't stop others), log errors with structured JSON, handle rate limits (record last successful ticker, resume next run)
|
||||
- Wire scheduler into FastAPI lifespan (start on startup, shutdown gracefully)
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 7.1, 8.2, 10.6_
|
||||
|
||||
- [ ]* 12.2 Write property test for scheduled collection (Property 10)
|
||||
- **Property 10: Scheduled collection processes all tickers** — _Validates: Requirements 4.1, 4.3, 7.1, 8.2_
|
||||
|
||||
- [ ]* 12.3 Write property test for sorting correctness (Property 41)
|
||||
- **Property 41: Sorting correctness** — _Validates: Requirements 1.4, 6.6, 9.6, 10.8, 11.6_
|
||||
|
||||
- [x] 13. Test infrastructure and shared fixtures
|
||||
- [x] 13.1 Create test configuration and shared fixtures
|
||||
- Create `tests/conftest.py`: test DB session fixture (transaction rollback per test), FastAPI test client fixture, mock `MarketDataProvider`, hypothesis custom strategies (`valid_ticker_symbols`, `whitespace_strings`, `valid_ohlcv_records`, `invalid_ohlcv_records`, `dimension_scores`, `weight_configs`, `sr_levels`, `sentiment_scores`, `trade_setups`)
|
||||
- Create `tests/__init__.py`, `tests/unit/__init__.py`, `tests/property/__init__.py`
|
||||
- _Requirements: Design (Testing Strategy)_
|
||||
|
||||
- [x] 14. Deployment templates and CI/CD
|
||||
- [x] 14.1 Create deployment configuration files
|
||||
- Create `deploy/nginx.conf` (reverse proxy for signal.thiessen.io)
|
||||
- Create `deploy/stock-data-backend.service` (systemd unit file)
|
||||
- Create `deploy/setup_db.sh` (idempotent DB creation + migration script)
|
||||
- Create `.gitea/workflows/deploy.yml` (lint → test → deploy pipeline)
|
||||
- _Requirements: Design (Deployment and Infrastructure)_
|
||||
|
||||
- [x] 15. Final wiring and integration
|
||||
- [x] 15.1 Wire all routers into FastAPI app and verify OpenAPI docs
|
||||
- Register all routers in `app/main.py` under `/api/v1/` prefix
|
||||
- Verify Swagger/OpenAPI docs endpoint works at `/docs`
|
||||
- Ensure all middleware (logging, error handling, auth) is applied
|
||||
- _Requirements: Design Constraints (OpenAPI/Swagger, versioned URLs)_
|
||||
|
||||
- [ ]* 15.2 Write integration tests for key API flows
|
||||
- Test end-to-end: register → login → add ticker → fetch data → get indicators → get scores → get watchlist
|
||||
- Test auth enforcement: unauthenticated → 401, no-access user → 403, admin endpoints → 403 for non-admin
|
||||
- Test error flows: duplicate ticker → 409, invalid OHLCV → 400, missing ticker → 404
|
||||
- _Requirements: 1.1-1.6, 2.1-2.4, 12.1-12.6_
|
||||
|
||||
- [x] 16. 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
|
||||
- Each task references specific requirements for traceability
|
||||
- Checkpoints ensure incremental validation
|
||||
- Property tests validate the 41 correctness properties from the design document using `hypothesis`
|
||||
- Unit tests validate specific examples and edge cases
|
||||
- All code is Python 3.12+ with FastAPI, SQLAlchemy async, and PostgreSQL
|
||||
Reference in New Issue
Block a user