- New GET /api/market/prices/{ticker} endpoint serving OHLCV data from
market_snapshots, deduped by bar_timestamp
- New useMarketPrices hook in frontend
- Trend chart now shows price (purple line) on a right Y axis ($)
alongside trend metrics (%) on the left Y axis
- Custom tooltip formats price as dollars, metrics as percentages
- Price line uses connectNulls for days with missing bar data
Trend charts blank:
- trend_windows uses upsert (1 row per ticker/window), so charts had
at most 1 data point. Added trend_history table (migration 024) that
appends every snapshot. New /api/trends/history endpoint serves the
time series. Frontend now uses useTrendHistory for charts and
useTrends for the latest summary card.
Competitor GUIDs:
- list_competitors query returned raw company_b_id UUIDs without
joining companies table. Added LEFT JOIN with CASE to resolve the
other company's ticker and legal_name. Updated Pydantic model to
include enriched fields. Frontend fallback changed from truncated
UUID to ticker/legal_name/Unknown.
- ID mismatch: API generated a throwaway UUID while BacktestReplay
generated its own internally. Frontend polled with wrong ID and
never found the DB row. Now pre-generate ID in endpoint and pass
it to BacktestReplay.
- Field name: API returned 'backtest_id' but frontend read 'data.id'.
Unified to 'id' everywhere.
- No polling: useBacktestResult fired once and never refreshed.
Added refetchInterval that polls every 2s while status is running.
- Response shape: GET endpoint nested results under 'result' object
but frontend expected flat fields. Flattened response to match
BacktestResult type.
- Added running/failed/completed status indicators in BacktestPanel.
- Add dedup check in recommendation worker: skip generation when latest
rec for same ticker+window has identical action/mode/confidence
- Widen position sizing range (1-10% portfolio, 0.3-2% max loss) and
factor in trend strength + evidence count for differentiated sizing
- API returns only latest recommendation per ticker by default (DISTINCT ON)
to eliminate duplicate rows in the frontend list view
- Sell path: looks up existing position, sells full quantity, returns proceeds to pool
- Correlation matrix: computed from 30-day market_snapshots on startup + every 5min
- Holidays: 10 major US market holidays for 2026 checked in trading window functions
- Strip SQL comments (-- and /* */) before checking for SELECT,
so queries with leading comments don't get rejected
- Show the actual error detail from the API response instead of
generic 'API error 400' in the SQL Explorer UI
Replaced the Gmail API (OAuth2) notification delivery with plain
SMTP using a Gmail app password. Much simpler setup — no Google
Cloud project, no OAuth2 flow, no extra dependencies.
- Rewrote _send_gmail() to use smtplib with smtp.gmail.com:587 TLS
- Added stonks-gmail-secrets to Helm chart (GMAIL_SENDER,
GMAIL_RECIPIENT, GMAIL_APP_PASSWORD)
- Added gmail secret to trading-engine deployment
- Updated runmefirst.sh to read gmail.app from kube dir
- Sender/recipient: celes@celestium.life
Alpaca returns 404 when you don't hold a position in a ticker.
The ingestion worker was logging this as an error and incrementing
the failure count. Now returns an empty items list instead, since
'no position' is a valid state, not an error.
The SQL Explorer was querying Trino which has zero tables. Rewrote to
use PostgreSQL directly:
Backend:
- GET /api/analytics/pg-schema: returns all public tables with column
names, types, and nullability from information_schema
- POST /api/analytics/pg-query: read-only SQL execution against
PostgreSQL with SELECT-only enforcement, auto LIMIT, and descriptive
error messages for syntax/table/query errors
Frontend:
- Schema browser shows all PostgreSQL tables with columns and types
- Click a table name → generates SELECT * FROM table LIMIT 100
- Pre-built Queries section with 12 seeded queries covering companies,
recommendations, trends, market prices, documents, global events,
trading decisions, ingestion health, reserve pool, sector exposure
- User-saved queries shown separately with delete buttons
- Chart builder, Monaco editor, and save functionality preserved
Migration 021: seeds 12 pre-built saved queries
The alpaca.url config file contains https://paper-api.alpaca.markets/v2
but the adapter code also prepends /v2/ to all paths, resulting in
/v2/v2/positions which returns 404. Now strips trailing /v2 or /v1
from the configured base URL since the adapter manages API versioning.
This was causing 1,017 consecutive broker sync failures.
When multiple recommendations for the same ticker produce 'act'
decisions, the second one would overwrite the first in
simulated_positions, losing the first position's value and causing
incorrect portfolio value calculations. Now skips if already holding.
- Map DB 'id' field to 'recommendation_id' for evaluate_recommendation()
- Ensure confidence is cast to float (asyncpg may return Decimal)
- Add per-day logging showing rec count, act/skip, positions, pool balance
- Helps diagnose why backtests produce 0 trades
Two tiers of market data:
1. Per-ticker prev bars (existing 50 sources, 15-min cadence) for
watchlist detail — trading decisions, stop-loss, position sizing
2. Grouped daily (new single source, once per day) for broad market
context — correlation analysis, sector rotation, competitive intel
Changes:
- Add grouped_daily endpoint to PolygonMarketAdapter with auto date
calculation (previous trading day, skip weekends)
- Add fetch_global_market_sources() to scheduler for sources without
company_id, scheduled once daily (86400s cadence)
- Update _persist_market_items to use item-level ticker from T field
and look up company_id dynamically for grouped daily bars
- Migration 020: make company_id nullable on sources and
market_snapshots tables, add grouped daily source row
- Fix backtest replay to query market_snapshots data->>'c' for prices
- Increase market_api polling cadence from 60s to 900s (15 min).
The prev-day bar endpoint returns the same data all day, so polling
every minute wastes API quota. 50 tickers at 15-min cadence = ~3.3
req/min, well within the 5/min rate limit.
- Reduce market_api rate limit from 30/min to 5/min to match.
- Fix backtest replay to query market_snapshots with data->>'c' for
close prices instead of nonexistent market_data.close_price column.
- Enrich backtest recommendations with prices from market_snapshots
and sectors from companies table.
The simulated timestamp was 10:00 UTC (6:00 AM ET) which is outside
the trading window. Changed to 11:00 AM ET so backtested decisions
actually pass the trading window check.
Phase 2 of the autonomous trading engine:
- Replace start()/stop() stubs with real async implementations
- Decision loop: polls recommendations from PostgreSQL, deduplicates
via Redis, evaluates through the full pipeline, submits orders to
stonks:queue:broker_orders
- Stop-loss monitor: fetches prices from Polygon API, checks crossings,
submits immediate sell orders, safety sell after 15 min without data
- Performance loop: computes metrics every 5 min during market hours,
persists daily snapshots at market close
- Risk tier scheduler: evaluates daily at 16:00 ET, persists tier changes
- Rebalance scheduler: evaluates Monday 09:45 ET, respects circuit breaker
- Notification dispatch: SNS + Gmail with rate limiting and retry
- Backtest replay: fetches historical data, simulates decisions, persists
- Real asyncpg/redis connections in FastAPI lifespan (graceful degradation)
- Migration 019: enable paper trading with conservative tier, 5 cap
- Added max_open_positions to TradingConfig with env var loading
- Phase 2 tasks added to autonomous-trading-engine spec
When the LLM returns empty summary and no key facts, raise ValueError
so the retry logic kicks in instead of persisting an empty event.
Also strip whitespace from summary and filter empty key_facts entries.
Cleaned up 17 empty events from the database.
Macro news documents have no ticker, causing upload_normalized_text
and upload_parser_output to produce paths like parsed//2026/...
which MinIO rejects as XMinioInvalidObjectName. Use '_global' as
the path segment when ticker is empty, matching the existing
macro prefix pattern in upload_raw_document.