Commit graph

112 commits

Author SHA1 Message Date
Marko Djordjevic
36182986b6 Extract magic numbers and colors to named constants in CandleChart.tsx
Replace inline magic numbers (8px, 60s, 2, 1, 0.15, 0.25, 100) and hardcoded
color strings with named module-level constants for better maintainability and
clarity. Organized constants into logical groups:
- Magic number constants (tolerance, intervals, sizes, alphas)
- Dark theme colors
- Light theme colors
- Candlestick colors
- Prediction overlay colors
- Predefined label colors

This improves code readability and makes it easier to adjust UI parameters globally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:24:32 +01:00
Marko Djordjevic
d6d844a003 fix: detect candle interval dynamically instead of hardcoded 60s
Replace the hardcoded 60-second assumption in CandleChart.tsx with
dynamic interval detection: compute interval as candles[1].time -
candles[0].time when at least 2 candles are available, fall back to
60 otherwise. Used for span-to-prediction-time mapping iteration.

Marks task 8.6 as done in tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:23:11 +01:00
Marko Djordjevic
8395f23744 feat: add bounded prediction cache (max 100 entries, FIFO eviction)
- Before inserting into predictionCacheRef, check if size >= 100
- If so, evict oldest entry via cache.keys().next().value (FIFO)
- Applied at both cache write sites (lines ~549 and ~671 in page.tsx)
- Marks task 8.5 as done in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:22:38 +01:00
Marko Djordjevic
acf31d3edf perf: split SpanAnnotationManager reconciliation into two effects
Separate the single reconciliation effect into:
1. Full reconciliation effect — runs only when spanAnnotations list,
   series, chart, or candles change. Rebuilds all primitives.
2. Selection-only effect — runs only when selectedSpanId changes.
   Calls setSelected() on existing primitives instead of detaching
   and reattaching all of them.

This avoids tearing down and rebuilding every primitive on each
selection change, which was the main unnecessary overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:21:46 +01:00
Marko Djordjevic
aa27fe93f6 perf: remove fitContent() from reconciliation effect, call only on initial load
Move chart.timeScale().fitContent() out of the span annotation reconciliation
effect (which ran on every annotation/selection change) into a dedicated effect
guarded by a hasInitializedRef, so it fires only once when chart and series
first become available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:20:37 +01:00
Marko Djordjevic
131d2912d5 fix: use chart.applyOptions() for theme changes instead of re-creating chart
Remove resolvedTheme from the chart initialization useEffect dependency
array so theme changes no longer destroy and re-create the entire chart.
Add a separate useEffect that calls chartRef.current.applyOptions() with
the new layout, grid, timeScale and rightPriceScale colors whenever
resolvedTheme changes, avoiding the memory leak and flicker caused by
full chart reconstruction on every theme toggle.

Closes task 8.2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:19:44 +01:00
Marko Djordjevic
0cd7a34c99 fix: replace useState with useRef for preview primitive to prevent memory leak
The preview primitive in SpanAnnotationManager was stored in useState,
causing unnecessary re-renders and potential memory leaks since the
primitive was not cleaned up on unmount. Changed to useRef, updated all
read/write sites, removed from dependency arrays, and added unmount
cleanup effect that detaches the primitive from the series.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:18:48 +01:00
Marko Djordjevic
e5b0cc2540 fix: resolve stale closure in SpanAnnotationManager keyboard handler
Use selectedSpanIdRef to avoid capturing stale selectedSpanId in the
keyboard event handler closure. Wrap handleDeleteSpan in useCallback
with stable dependencies (reads selectedSpanIdRef.current instead of
the prop directly). Remove selectedSpanId from the useEffect dependency
array since the ref is used instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:16:55 +01:00
Marko Djordjevic
73c88f8211 fix: resolve stale closures in CandleChart click handler
Convert drawingState, selectedLineId, dragState, and annotations to
refs that stay in sync with state via useEffect. The chart click handler
(subscribeClick) captured stale closure values for these variables.
Now reads from refs (e.g. drawingStateRef.current) so the handler
always sees the latest state, and removed the stale state variables
from the useEffect dependency array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:15:24 +01:00
Marko Djordjevic
45a23047dd fix: add AbortController to fetchPredictions and handleFetchBatchPredictions
Prevent race conditions by aborting in-flight requests when a new
request is triggered. Each function now:
- Aborts the previous request via a stored AbortController ref
- Passes signal to all fetch() calls
- Silently discards AbortError in catch blocks

Completes task 7.2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 15:12:40 +01:00
Marko Djordjevic
4c53ef7cae fix: add response.ok checks before .json() in CandleChart.tsx
Added HTTP error checks before calling .json() in the three fetch
functions (fetchCandles, fetchAnnotations, fetchAnnotationTypes)
to prevent silent failures on non-2xx responses.

Marks task 4.12 as complete in tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:21:54 +01:00
Marko Djordjevic
4436cd655f fix: add response.ok checks before .json() in page.tsx fetch calls
Guard all four fetch() calls in src/app/page.tsx against non-2xx HTTP
responses by throwing before attempting to parse the body as JSON.
Affected functions: fetchCharts, fetchAnnotations, fetchSpanAnnotations,
fetchSpanLabelTypes.

Marks task 4.11 as completed in code-review-fix/tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:21:21 +01:00
Marko Djordjevic
b2129ad626 security: add CSV injection protection to all export routes
Add sanitizeCsvCell() helper to both export routes that prefixes cell
values starting with =, +, @, or - with a single quote to prevent CSV
formula injection attacks.

Applied to:
- src/app/api/export/route.ts: timestamp and label_type columns
- src/app/api/span-annotations/export/route.ts: start_time, end_time,
  label, and outcome columns

Closes task 4.10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:20:36 +01:00
Marko Djordjevic
15adf09b73 fix: add parseInt(value, 10) with isNaN() guards to all integer query param parsing
- Add radix 10 to all parseInt() calls parsing integer query/path parameters
- Add isNaN() guards returning HTTP 400 with descriptive error messages
- Updated routes: annotations, candles, export, export/spans, annotation-types/[id], span-annotations, span-annotations/[id], span-label-types/[id]
- Ensures strict integer parsing and prevents invalid parameter values from reaching database queries
2026-02-18 11:19:26 +01:00
Marko Djordjevic
1678da2d9d fix: wrap chart cascade delete in db.transaction() and add spanAnnotations deletion
- Import spanAnnotations from schema
- Wrap all delete operations in db.transaction() for atomicity
- Delete spanAnnotations first to satisfy FK constraints, then annotations, candles, chart
- Mark task 4.8 as done in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:17:19 +01:00
Marko Djordjevic
103bfa89cb fix: require chartId for bulk delete in annotations route (task 4.7)
Reject DELETE ?all=true without chartId with HTTP 400 to prevent
accidental deletion of annotations across all charts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:16:37 +01:00
Marko Djordjevic
aace19b7f4 fix: replace error.message with generic "Internal server error" in all API catch blocks
Prevents leaking internal error details to clients across 7 route files:
health, candles, annotations, annotations/[id], upload, export, span-annotations/export.
Server-side console.error logging preserved for debugging.

Closes task 4.6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:16:02 +01:00
Marko Djordjevic
81e3554d82 feat: add Zod schema validation to patterns/detect route
- Import z from zod
- Add CandleSchema validating time, open, high, low, close (number), volume (optional number)
- Add PatternDetectRequestSchema validating candles array and patterns array of non-empty strings
- Use safeParse() and return HTTP 400 with error details on validation failure
- Forward only validated data to the inference service
- Mark task 4.5 as completed in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:14:36 +01:00
Marko Djordjevic
2e02d155af feat: add Zod schema validation to training/start route (task 4.4)
Validates model_type as a non-empty string using .safeParse(); returns
HTTP 400 with error details on invalid input. Marks task 4.4 as done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:14:00 +01:00
Marko Djordjevic
4cffc223b3 feat: add Zod schema validation to model/load route
Validate run_id in POST /api/model/load using Zod:
- run_id must be a non-empty string matching /^[a-zA-Z0-9_-]+$/
- Returns HTTP 400 with error details if validation fails
- Validated data is forwarded to the inference service

Marks task 4.3 as complete in tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:13:13 +01:00
Marko Djordjevic
5c399037c3 feat: add Zod validation to predict/batch route (task 4.2)
Add BatchPredictRequestSchema with Zod to validate pair, timeframe,
start_date, and end_date fields. Returns HTTP 400 with flattened error
details on invalid input. Forward only validated data to the inference
service.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:12:38 +01:00
Marko Djordjevic
3361236d3f feat: add Zod schema validation to predict API route
- Add CandleSchema validating time, open, high, low, close (number) and optional volume
- Add PredictRequestSchema validating pair (non-empty string), timeframe (non-empty string), candles array
- Use safeParse() and return HTTP 400 with error details on invalid input
- Forward only validated data to the inference service
- Mark task 4.1 as done in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:11:58 +01:00
Marko Djordjevic
4a3e4a48ba feat: forward X-API-Key header from Next.js proxy routes to ML service
All 12 Next.js API routes that proxy requests to the ML service
(INFERENCE_API_URL / localhost:8001) now include the X-API-Key header
read from process.env.API_KEY. Affected routes:
- /api/predict
- /api/predict/batch
- /api/model/info
- /api/model/load
- /api/training/start
- /api/training/runs
- /api/training/runs/[run_id] (DELETE)
- /api/training/dataset-info
- /api/training/active
- /api/training/build-dataset
- /api/patterns/available
- /api/patterns/detect

Marks task 3.3 as complete in openspec/changes/code-review-fix/tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:06:18 +01:00
Marko Djordjevic
577bb2e56e feat: add API key auth middleware for /api/* routes (task 3.1)
- Create src/middleware.ts with Next.js middleware
- Reads API_KEY env var and checks X-API-Key header on all /api/* routes
- Skips auth for /api/health endpoint
- Fails open (with warning) when API_KEY is not configured
- Returns 401 Unauthorized when key is missing or mismatched
- Mark task 3.1 as complete in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:02:51 +01:00
Marko Djordjevic
94bc5768d1 feat: add file type validation to upload endpoint
- Validate filename ends with .csv (case-insensitive)
- Validate MIME type is text/* or application/csv or text/csv
- Return HTTP 400 with error message if validation fails
- Mark task 2.4 as complete
2026-02-18 11:01:28 +01:00
Marko Djordjevic
0e239dc3da security: add file size (10MB) and row count (500k) limits to upload route
- Reject uploads larger than 10MB before reading file content
- Reject CSVs with more than 500,000 data rows after parsing
- Checks placed as early as possible in the handler flow
- Mark task 2.3 as done in tasks.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:01:02 +01:00
Marko Djordjevic
870f92d208 feat: add run_id format validation in DELETE training/runs endpoint
Validate that run_id matches /^[a-zA-Z0-9_-]+$ regex before interpolating into the API URL.
Returns HTTP 400 with 'Invalid run_id format' error if validation fails.
This prevents potential URL injection attacks and invalid identifier usage.
2026-02-18 10:58:54 +01:00
Marko Djordjevic
d3dcfcea7d feat: auto-build training dataset from DB annotations before training
- Add build_dataset_from_db() that exports candles from DB, runs feature
  engineering, and ingests span annotations into labeled CSV
- Call it automatically in _run_training_background before training starts
- Add POST /training/build-dataset endpoint for standalone use
- Add Next.js proxy route /api/training/build-dataset
- Update TrainingPanel: remove dataset-missing block on Start Training,
  show informational message that dataset builds automatically
2026-02-18 00:24:39 +01:00
Marko Djordjevic
d5fc4662e9 fix: replace all SQLite references with PostgreSQL in scripts and lazy-init db connection
- Rewrite scripts/run-migrations.js for PostgreSQL (was better-sqlite3)
- Rewrite scripts/load-initial-data.js for PostgreSQL (was better-sqlite3)
- Make db connection lazy in src/lib/db/index.ts to avoid build-time errors
  when DATABASE_URL is not available in Docker build stage
2026-02-17 23:48:47 +01:00
Marko Djordjevic
2481fda68c fix: remove all SQLite references (migrate.ts, migration script, package.json) 2026-02-17 23:34:12 +01:00
Marko Djordjevic
69634909d1 fix: correct timestamp/boolean types for PostgreSQL schema (Date not int, bool not 0/1) 2026-02-17 22:50:31 +01:00
Marko Djordjevic
e00bd4d804 fix: await params in DELETE route for Next.js 15 async params 2026-02-17 22:33:38 +01:00
Marko Djordjevic
6ef102cf21 fix: training panel stuck button, stale runs on startup, add delete model 2026-02-17 22:23:50 +01:00
Marko Djordjevic
d34dc9d729 fix: make sidebar scrollable so training/predictions panels are always visible 2026-02-17 22:10:54 +01:00
Marko Djordjevic
2faa8879f3 feat: add keyboard shortcuts R/S/L/D/T/? with shortcuts modal 2026-02-17 20:19:25 +01:00
Marko Djordjevic
028a3382d6 fix: pass confidence=null for talib span annotations (DB column is integer, not float) 2026-02-17 20:14:13 +01:00
Marko Djordjevic
d416aa409c fix: show TA-lib pattern spans on chart and add delete all annotations button
- Fix SpanAnnotationManager price range calculation: use direct candle
  filtering by time range instead of dummy Candle objects that fell back
  to 0/0 high/low, causing invisible rectangles
- Add handleDeleteAllAnnotations in page.tsx (deletes all span annotations
  and regular annotations for current chart)
- Add 'Delete All Annotations' button in sidebar below TA-Lib panel
2026-02-17 20:12:23 +01:00
Marko Djordjevic
7901a1a257 fix: use annotation's saved color for TrendLine instead of annotationType default color 2026-02-17 20:03:54 +01:00
Marko Djordjevic
c404e79678 fix: normalize span annotation timestamps to Unix epoch seconds in all API responses 2026-02-17 20:02:14 +01:00
Marko Djordjevic
900443d078 fix: convert Unix epoch seconds to Date for span annotation start_time/end_time on insert 2026-02-17 20:01:09 +01:00
Marko Djordjevic
4352732630 fix: change is_active type from number to boolean to match API response 2026-02-17 19:59:48 +01:00
Marko Djordjevic
a75c9307d4 feat: add Settings button in sidebar with links to annotation-types and span-label-types 2026-02-17 19:51:42 +01:00
Marko Djordjevic
3a114cccd0 fix: convert Unix epoch seconds to Date before inserting annotation timestamp 2026-02-17 19:46:24 +01:00
Marko Djordjevic
148fe22cd7 fix: guard crosshair move handler against re-entrant recursion from applyOptions 2026-02-17 19:44:37 +01:00
Marko Djordjevic
0ba3592679 fix: make Line tool button static next to Rect and Span 2026-02-17 19:42:21 +01:00
Marko Djordjevic
04a8303f4f fix: convert timestamp columns to Unix epoch seconds in candles and annotations API 2026-02-17 19:40:48 +01:00
Marko Djordjevic
a9bfe36e47 fix: handle test_ prefixed metric keys from training runs API 2026-02-17 19:00:22 +01:00
Marko Djordjevic
12a9603fce feat: add TalibPatternPanel, TrainingPanel, ModelSelector UI components (tasks 5-8)
- TalibPatternPanel: pattern checkboxes, detect button, results summary, clear-all and per-pattern delete
- TrainingPanel: model type selector, dataset info, start training, polling, run history
- ModelSelector: dropdown of completed runs, wired into PredictionPanel for model switching
- page.tsx: integrate all three panels into sidebar, wire callbacks (model load, annotations refresh)
- tasks.md: mark all 39 tasks complete
2026-02-17 18:55:52 +01:00
Marko Djordjevic
2a02669222 feat: add FastAPI model/load endpoint and all Next.js proxy routes (tasks 2-4) 2026-02-17 18:47:04 +01:00
Marko Djordjevic
bfe437857b feat: add Python migration script and successfully test SQLite to PostgreSQL data migration
- Created scripts/migrate-sqlite-to-postgres.py as alternative to TypeScript version
- Handles all type conversions: timestamps, booleans, and JSONB fields
- Successfully migrated all 2,836 rows from SQLite to PostgreSQL
- Verified data integrity: all 6 tables migrated correctly
- Charts: 1, Candles: 2,592, Annotations: 4, Span annotations: 223
2026-02-17 14:01:21 +01:00