- Pause/resume persisted in queue_state; drain loop checks isPaused() and skips - Error capture: every failed/backoff attempt logged to queue_errors with the full stack trace; admin queue page expands a failed job to stream it - Retry controls: retryJob(key), retrySource(kind), clearDone(olderThanMs) - Per-source scheduling: queue_schedules table + 30s enqueueDueSchedules loop (seed defaults sec-fetch 24h, yfinance 5min); admin UI lists/adds/deletes - Startup recovery: interrupted in_flight jobs reset to pending on boot - fix(edgar): archive URLs use the filer CIK (accession-number prefix), not the company CIK — resolves Cloudflare 429 that left SEC backfills sparse/empty - Migration: add queue_errors, queue_schedules, queue_state tables plus error/ scheduled_for columns on adapter_queue (idempotent ALTER on startup) Verified: full sec-fetch backfill now succeeds for all watched symbols (NVDA 14,675 institution filings, CIFR 274 insider txns, TSLA 6,011, etc.). 503 backend tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
553 lines
23 KiB
SQL
553 lines
23 KiB
SQL
-- Investor Flow — SQLite schema. Engineering contract: DESIGN.md §1 (Tier A/B/C/D),
|
|
-- §1.5 (LLM provenance), §2.6 (ticker-data dedupe), + locked review deltas (price_adjustments, trade_key_levels).
|
|
-- Tiers: A shared market cache (ownerId NULL) | B shared content | C per-user (ownerId NOT NULL) | D system.
|
|
-- Conventions: timestamps ISO8601 TEXT; observed_at/fetched_at provenance per P4.
|
|
-- Idempotent: CREATE ... IF NOT EXISTS. Safe to re-run on every boot.
|
|
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
-- ===== Tier D — System / Identity =====
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY, -- uuid
|
|
email TEXT UNIQUE NOT NULL,
|
|
pw_hash TEXT NOT NULL, -- scrypt(verifiable); design specified argon2id, adapted to built-in scrypt (reversible)
|
|
totp_secret TEXT, -- 2FA secret, encrypted at rest (slice 2)
|
|
is_2fa_enabled INTEGER NOT NULL DEFAULT 0,
|
|
is_admin INTEGER NOT NULL DEFAULT 0, -- 1 = operator (admin CLI/route access, Slice 25); never user-editable
|
|
oauth_subject TEXT, -- nullable; social OAuth link (slice 2)
|
|
oauth_provider TEXT, -- 'github' | 'google' | NULL
|
|
complexity TEXT NOT NULL DEFAULT 'beginner', -- beginner|intermediate|advanced
|
|
risk_tolerance TEXT NOT NULL DEFAULT 'moderate',
|
|
drawdown_tolerance REAL, -- onboarding max-drawdown % (beginner -20, etc.)
|
|
convexity_posture TEXT NOT NULL DEFAULT 'off', -- off|covered_income|cash_secured_entry|insurance_sleeve|leaps_conviction
|
|
backup_codes_hashed TEXT, -- JSON array of scrypt-hashed backup codes (slice 2)
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
expires_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sizing_unlocks (
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
unlock TEXT NOT NULL, -- 'tier_a_star' | 'tier_a' | ('covered_income'|...)
|
|
earned_at TEXT NOT NULL,
|
|
evidence TEXT, -- stats that justified unlock
|
|
PRIMARY KEY (user_id, unlock)
|
|
);
|
|
|
|
-- ===== Tier A — Shared market cache (no ownerId) =====
|
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
symbol TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
sector TEXT,
|
|
industry TEXT,
|
|
exchange TEXT,
|
|
ticker_kind TEXT NOT NULL, -- equity|crypto|etf|index (gates module applicability)
|
|
peers TEXT, -- JSON array
|
|
updated_at TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS price_candles (
|
|
symbol TEXT NOT NULL,
|
|
timeframe TEXT NOT NULL, -- '1m'|'5m'|'1h'|'1d'|'1w'
|
|
ts TEXT NOT NULL,
|
|
o REAL, h REAL, l REAL, c REAL, v REAL,
|
|
adj_close REAL, -- continuous split+dividend-adjusted close for backtests
|
|
observed_at TEXT NOT NULL, -- when fetched (provenance)
|
|
PRIMARY KEY (symbol, timeframe, ts)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS quotes (
|
|
symbol TEXT PRIMARY KEY,
|
|
price REAL,
|
|
bid REAL,
|
|
ask REAL,
|
|
change REAL,
|
|
change_pct REAL,
|
|
iv REAL, -- implied vol when available
|
|
observed_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS kv_cache (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
observed_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS options_chains (
|
|
symbol TEXT NOT NULL,
|
|
expiry TEXT NOT NULL,
|
|
strike REAL NOT NULL,
|
|
type TEXT NOT NULL, -- call|put
|
|
bid REAL, ask REAL, iv REAL,
|
|
delta REAL, gamma REAL, theta REAL, vega REAL, rho REAL,
|
|
open_interest INTEGER, volume INTEGER,
|
|
ts TEXT NOT NULL, -- snapshot timestamp
|
|
PRIMARY KEY (symbol, expiry, strike, type, ts)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS filings (
|
|
accession TEXT PRIMARY KEY, -- SEC accession number
|
|
form TEXT NOT NULL, -- 10-K|10-Q|8-K|13F-HR|13D|13G|4|...
|
|
symbol TEXT NOT NULL,
|
|
filed_at TEXT NOT NULL,
|
|
period TEXT,
|
|
primary_doc_url TEXT,
|
|
items TEXT, -- JSON: list of items (8-K materiality heuristics)
|
|
fetched_at TEXT NOT NULL,
|
|
raw_payload TEXT -- cached full XBRL/parsed doc
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS institution_filings (
|
|
filer_cik TEXT NOT NULL, -- institution's CIK
|
|
filer_name TEXT,
|
|
filer_sic TEXT, -- SIC code -> classifies MM/broker-dealer (ADR Q2)
|
|
symbol TEXT NOT NULL,
|
|
form TEXT NOT NULL, -- 13F-HR|13D|13G
|
|
shares REAL, -- shares reported
|
|
value_usd REAL,
|
|
reported_quarter TEXT NOT NULL, -- calendar quarter positions are "as of"
|
|
filed_at TEXT NOT NULL,
|
|
fetched_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS insider_transactions (
|
|
form4_id TEXT NOT NULL, -- accession + offset
|
|
symbol TEXT NOT NULL,
|
|
insider_name TEXT NOT NULL,
|
|
insider_role TEXT, -- officer|director|ten_pct_holder
|
|
tx_date TEXT NOT NULL,
|
|
tx_code TEXT NOT NULL, -- P|S|M|A|... (SEC codes)
|
|
tx_type TEXT NOT NULL, -- buy|sell|exercise|grant|vesting
|
|
shares REAL,
|
|
price REAL,
|
|
is_10b5_1 INTEGER NOT NULL DEFAULT 0, -- 1 = under 10b5-1 plan (Routine)
|
|
classification TEXT NOT NULL, -- informed_buy|informed_sell|routine
|
|
filed_at TEXT NOT NULL,
|
|
fetched_at TEXT NOT NULL,
|
|
PRIMARY KEY (form4_id, symbol, insider_name, tx_date)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sector_map (
|
|
symbol TEXT NOT NULL,
|
|
sector TEXT NOT NULL,
|
|
as_of TEXT NOT NULL,
|
|
rs_ratio REAL, -- relative strength vs SPX
|
|
rs_slope REAL, -- slope over N weeks (incipient detection)
|
|
breadth_thrust REAL, -- % members beating SPX
|
|
PRIMARY KEY (symbol, sector, as_of)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS macro_events (
|
|
event_id TEXT PRIMARY KEY, -- e.g. '2026-06-30-FOMC'
|
|
name TEXT NOT NULL,
|
|
event_ts TEXT NOT NULL,
|
|
impact TEXT, -- high|medium|low
|
|
forecast TEXT,
|
|
previous TEXT,
|
|
actual TEXT, -- populated after release
|
|
source TEXT -- 'FRED'|'econoday'|...
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS macro_regimes (
|
|
as_of TEXT PRIMARY KEY,
|
|
regime TEXT NOT NULL, -- trending_up|trending_down|range_bound|structurally_shifting
|
|
signals TEXT -- JSON: yield curve, inflation, rate path, liquidity, breadth
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS price_adjustments (
|
|
symbol TEXT NOT NULL,
|
|
ex_date TEXT NOT NULL, -- date the price line shifted
|
|
type TEXT NOT NULL, -- 'split'|'dividend'
|
|
ratio REAL NOT NULL, -- split ratio (3.0 = 3-for-1) or dividend amount ($/share)
|
|
PRIMARY KEY (symbol, ex_date, type)
|
|
);
|
|
|
|
-- ===== Tier B — Shared content =====
|
|
CREATE TABLE IF NOT EXISTS threads (
|
|
source TEXT NOT NULL, -- x|reddit
|
|
source_id TEXT NOT NULL, -- tweet id / reddit post id
|
|
symbol TEXT NOT NULL,
|
|
author TEXT NOT NULL,
|
|
body TEXT,
|
|
ts TEXT NOT NULL,
|
|
sentiment TEXT, -- bullish|bearish|neutral (computed)
|
|
llm_summary_hash TEXT, -- cross-ref to llm_summaries
|
|
fetched_at TEXT NOT NULL,
|
|
PRIMARY KEY (source, source_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin_audit (
|
|
id TEXT PRIMARY KEY, -- uuid
|
|
actor TEXT NOT NULL, -- admin user id (or 'cli' when none)
|
|
action TEXT NOT NULL, -- users.reset-password|users.gdpr-export|queue.reset-backoff|...
|
|
target TEXT, -- target user id / sourceKind / null
|
|
ts TEXT NOT NULL, -- ISO timestamp
|
|
meta TEXT -- optional JSON blob (never secrets)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS adapter_queue (
|
|
key TEXT PRIMARY KEY, -- e.g. "yfinance:quote:NVDA"
|
|
status TEXT NOT NULL, -- pending|in_flight|done|backoff|failed
|
|
last_attempt TEXT,
|
|
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
backoff_until TEXT,
|
|
error TEXT, -- last error message (for failed/backoff)
|
|
scheduled_for TEXT -- ISO timestamp; job won't run before this
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS queue_errors (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
queue_key TEXT NOT NULL,
|
|
attempt INTEGER NOT NULL,
|
|
error_message TEXT,
|
|
error_stack TEXT,
|
|
ts TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS queue_schedules (
|
|
source_kind TEXT PRIMARY KEY,
|
|
interval_ms INTEGER NOT NULL,
|
|
last_enqueued TEXT,
|
|
next_enqueue TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS queue_state (
|
|
key TEXT PRIMARY KEY, -- 'paused'
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_demand (
|
|
symbol TEXT PRIMARY KEY,
|
|
refcount INTEGER NOT NULL DEFAULT 0,
|
|
ticker_kind TEXT NOT NULL,
|
|
in_demand INTEGER NOT NULL DEFAULT 1, -- gate flag for the adapter queue
|
|
last_refreshed_at TEXT
|
|
);
|
|
|
|
-- ===== Tier C — Per-user (ownerId NOT NULL) =====
|
|
CREATE TABLE IF NOT EXISTS watchlists (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
symbols TEXT NOT NULL, -- JSON array
|
|
created_at TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlists_owner_name ON watchlists(owner_id, name);
|
|
|
|
CREATE TABLE IF NOT EXISTS portfolio_holdings (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
symbol TEXT NOT NULL,
|
|
qty REAL NOT NULL,
|
|
avg_cost REAL NOT NULL,
|
|
acquired_at TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'open' -- open|closed
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_portfolio_owner_symbol
|
|
ON portfolio_holdings(owner_id, symbol);
|
|
|
|
CREATE TABLE IF NOT EXISTS trades (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
trade_id TEXT NOT NULL,
|
|
symbol TEXT NOT NULL,
|
|
date TEXT NOT NULL,
|
|
status TEXT NOT NULL, -- planned|active|closed
|
|
environment TEXT, -- trending|ranging|transitioning
|
|
tier TEXT NOT NULL, -- A_STAR|A|B|C (Conviction Tier)
|
|
strategy_id TEXT, -- ref to strategies (nullable)
|
|
confluence_score INTEGER,
|
|
risk_pct REAL,
|
|
position_size REAL, -- computed by SizingEngine
|
|
entry_price REAL,
|
|
stop_loss REAL,
|
|
targets TEXT, -- JSON [{price, reason}]
|
|
justifications TEXT, -- JSON {entryWhy, stopWhy, targetWhy}
|
|
checklist_completed INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS strategies (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
regime_gate TEXT NOT NULL, -- JSON: gate config
|
|
setup TEXT NOT NULL, -- JSON: entry/exit checklist w/ params
|
|
risk_policy TEXT NOT NULL, -- JSON: sizing rules
|
|
created_at TEXT NOT NULL,
|
|
is_public INTEGER NOT NULL DEFAULT 0 -- shareable read-only (later phase)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS screener_filters (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
expressions TEXT NOT NULL, -- JSON: filter expression set
|
|
universe_kind TEXT NOT NULL, -- watchlist|portfolio|broader_sector
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS reports (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
scope TEXT NOT NULL, -- symbol|watchlist|portfolio|rotation|sizing_year|risk_posture
|
|
range_start TEXT,
|
|
range_end TEXT,
|
|
format TEXT NOT NULL, -- html|markdown|csv|json
|
|
payload_ref TEXT NOT NULL, -- file path in local reports dir
|
|
generated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alerts (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
symbol TEXT,
|
|
alert_type TEXT NOT NULL, -- informed_buy|informed_sell|new_13da|rotation_incipient|regime_shift|conviction_unlock|...
|
|
params TEXT NOT NULL, -- JSON thresholds
|
|
last_fired TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS trusted_accounts (
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL, -- x|reddit
|
|
handle TEXT NOT NULL,
|
|
PRIMARY KEY (owner_id, source, handle)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS saved_posts (
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL,
|
|
source_id TEXT NOT NULL,
|
|
note TEXT,
|
|
linked_trade_id TEXT REFERENCES trades(id) ON DELETE SET NULL,
|
|
saved_at TEXT NOT NULL,
|
|
PRIMARY KEY (owner_id, source, source_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS trade_key_levels (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
symbol TEXT NOT NULL,
|
|
type TEXT NOT NULL, -- swing_high|swing_low|supply|demand|support|resistance
|
|
price REAL NOT NULL,
|
|
timeframe TEXT, -- '1H'|'4H'|'1D'|...
|
|
strength TEXT, -- low|medium|high
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
-- ===== Shared LLM cache (output deterministic by prompt-hash; not per-user) =====
|
|
|
|
-- ===== Slice 22 — Emotion Logger: per-trade emotion snapshots =====
|
|
CREATE TABLE IF NOT EXISTS trade_executions (
|
|
id TEXT PRIMARY KEY,
|
|
trade_id TEXT NOT NULL REFERENCES trades(id) ON DELETE CASCADE,
|
|
status TEXT NOT NULL DEFAULT 'active', -- active|closed
|
|
started_at TEXT NOT NULL,
|
|
completed_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS emotion_logs (
|
|
id TEXT PRIMARY KEY,
|
|
trade_execution_id TEXT NOT NULL REFERENCES trade_executions(id) ON DELETE CASCADE,
|
|
timestamp INTEGER NOT NULL,
|
|
price_at_event REAL, -- price at moment of emotion event
|
|
emotion TEXT NOT NULL, -- e.g. 'fear', 'greed', 'FOMO', 'regret'
|
|
note TEXT, -- optional user note
|
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_emotion_logs_trade ON emotion_logs(trade_execution_id);
|
|
CREATE TABLE IF NOT EXISTS llm_summaries (
|
|
hash TEXT PRIMARY KEY, -- hash(prompt + source-payload + complexity)
|
|
feature TEXT NOT NULL, -- filing_summary|post_summary|dashboard_rollup|explainer|qa|sizing_explain|macro_commentary|...
|
|
complexity TEXT NOT NULL,
|
|
output TEXT NOT NULL,
|
|
cited_sources TEXT, -- JSON refs to cached rows (provenance)
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS rotation_signals (
|
|
id TEXT PRIMARY KEY,
|
|
sector TEXT NOT NULL,
|
|
detected_at TEXT NOT NULL,
|
|
phase TEXT NOT NULL, -- accumulation|expansion|distribution|markdown
|
|
confidence REAL,
|
|
price_resolved INTEGER NOT NULL DEFAULT 0, -- follows through within N=4 weeks?
|
|
inst_resolved INTEGER NOT NULL DEFAULT 0, -- 13F/insider flow later confirms?
|
|
resolved_at TEXT,
|
|
verdict TEXT -- real|false_alarm (gamma two-stage)
|
|
);
|
|
|
|
-- ===== Section 1.5 — LLM provenance (ADR-0006). Operator-managed, NOT user-editable. =====
|
|
CREATE TABLE IF NOT EXISTS llm_providers (
|
|
id TEXT PRIMARY KEY, -- 'local_ollama' | 'local_vllm' | 'zen_glm_5_2' | ...
|
|
base_url TEXT NOT NULL,
|
|
is_local INTEGER NOT NULL DEFAULT 0, -- 1 if localhost/127.0.0.1/::1/LAN subnet
|
|
review_adr_ref TEXT, -- ADR-XXXX or note documenting provenance review date
|
|
enabled INTEGER NOT NULL DEFAULT 0 -- only one with enabled=1 by default (local)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS llm_dispatch_audit (
|
|
id TEXT PRIMARY KEY,
|
|
feature TEXT NOT NULL,
|
|
payload_class TEXT NOT NULL, -- public_safe|sensitive
|
|
provider_id TEXT,
|
|
status TEXT NOT NULL, -- sent_local|sent_external|blocked_sensitive_external
|
|
ts TEXT NOT NULL
|
|
);
|
|
|
|
-- ===== Slice 16 — Sentiment / X-cookie / Reddit tables =====
|
|
-- sentiment_score: -1.0 (bearish) to +1.0 (bullish), computed by LLM post_summary or heuristic.
|
|
-- attribution: author_handle preserved on every cached post per M8.
|
|
-- cached_until: 7d rolling TTL; rows become stale after this window.
|
|
CREATE TABLE IF NOT EXISTS x_cookie_posts (
|
|
post_id TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'x',
|
|
author_handle TEXT NOT NULL,
|
|
cashtag TEXT, -- e.g. 'NVDA' — null if not a cashtag search
|
|
body_text TEXT,
|
|
posted_at TEXT NOT NULL,
|
|
engagement INTEGER NOT NULL DEFAULT 0, -- likes + retweets + replies
|
|
sentiment_score REAL, -- -1.0 to +1.0
|
|
attribution TEXT, -- original author handle (may differ from author_handle for quotes/retweets)
|
|
cached_until TEXT NOT NULL, -- 7d rolling; rows stale after this
|
|
PRIMARY KEY (post_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS reddit_posts (
|
|
post_id TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'reddit',
|
|
author_handle TEXT NOT NULL,
|
|
subreddit TEXT NOT NULL,
|
|
body_text TEXT,
|
|
posted_at TEXT NOT NULL,
|
|
engagement INTEGER NOT NULL DEFAULT 0, -- upvotes + comments
|
|
sentiment_score REAL, -- -1.0 to +1.0
|
|
attribution TEXT, -- original author handle (may differ for cross-posts)
|
|
cached_until TEXT NOT NULL, -- 7d rolling
|
|
PRIMARY KEY (post_id)
|
|
);
|
|
|
|
-- Indexes for slice-16 sentiment tables
|
|
CREATE INDEX IF NOT EXISTS idx_x_cookie_cashtag ON x_cookie_posts(cashtag, posted_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_reddit_subreddit ON reddit_posts(subreddit, posted_at DESC);
|
|
|
|
-- ===== Indexes (existing) =====
|
|
CREATE INDEX IF NOT EXISTS idx_pc_symbol_tf_ts ON price_candles(symbol, timeframe, ts);
|
|
CREATE INDEX IF NOT EXISTS idx_quote_obs ON quotes(observed_at);
|
|
CREATE INDEX IF NOT EXISTS idx_filing_symbol_form ON filings(symbol, form);
|
|
CREATE INDEX IF NOT EXISTS idx_instfil_sym_q ON institution_filings(symbol, reported_quarter);
|
|
CREATE INDEX IF NOT EXISTS idx_insider_sym_date ON insider_transactions(symbol, tx_date);
|
|
CREATE INDEX IF NOT EXISTS idx_threads_symbol_ts ON threads(symbol, ts);
|
|
CREATE INDEX IF NOT EXISTS idx_trades_owner ON trades(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_owner_sym ON alerts(owner_id, symbol);
|
|
CREATE INDEX IF NOT EXISTS idx_signals_sector ON rotation_signals(sector);
|
|
|
|
-- ===== Slice 18 — Risk Engine: Gentle-halt circuit breaker state =====
|
|
CREATE TABLE IF NOT EXISTS halt_state (
|
|
user_id TEXT PRIMARY KEY,
|
|
halted_until TEXT NOT NULL, -- ISO8601; 24h cooldown window
|
|
triggered_by TEXT NOT NULL, -- e.g. 'max_drawdown_tolerance_breach'
|
|
ts TEXT NOT NULL -- ISO8601; when the halt was recorded
|
|
);
|
|
|
|
-- ===== Slice 17 — AlertEngine: individual alert event instances =====
|
|
CREATE TABLE IF NOT EXISTS alert_events (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL, -- informed_buy|informed_sell|...
|
|
severity TEXT NOT NULL, -- info|warning|critical
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
symbol TEXT,
|
|
created_at TEXT NOT NULL,
|
|
acknowledged INTEGER NOT NULL DEFAULT 0,
|
|
dedup_key TEXT NOT NULL,
|
|
payload TEXT NOT NULL DEFAULT '{}', -- JSON
|
|
throttle_key TEXT -- for rate limiting: "user:type:symbol:YYYYMMDDHH"
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_alert_events_user ON alert_events(user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_events_dedup ON alert_events(dedup_key);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_events_throttle ON alert_events(throttle_key);
|
|
|
|
-- ===== Slice 12 — Strategy Lab + Backtest =====
|
|
CREATE TABLE IF NOT EXISTS strategies (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
components TEXT NOT NULL DEFAULT '[]', -- JSON array of StrategyComponent
|
|
unlocked INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS backtest_results (
|
|
id TEXT PRIMARY KEY,
|
|
strategy_id TEXT NOT NULL REFERENCES strategies(id) ON DELETE CASCADE,
|
|
symbol TEXT NOT NULL,
|
|
timeframe TEXT NOT NULL DEFAULT '1d',
|
|
equity_curve TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
exit_reasons TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
sample_size INTEGER NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_owner ON strategies(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_backtest_strategy ON backtest_results(strategy_id);
|
|
|
|
-- ===== Slice 13 — Saved Filter Sets =====
|
|
CREATE TABLE IF NOT EXISTS saved_filters (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
expression TEXT NOT NULL,
|
|
scope TEXT NOT NULL DEFAULT 'watchlist',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_saved_filters_user ON saved_filters(user_id);
|
|
|
|
-- ===== Slice 19 — Options Convexity Sleeve Unlock State =====
|
|
CREATE TABLE IF NOT EXISTS options_unlock (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
state INTEGER NOT NULL DEFAULT 0,
|
|
last_unlock_at TEXT,
|
|
understanding INTEGER NOT NULL DEFAULT 0 -- demonstrated understanding flag
|
|
);
|
|
|
|
-- ===== Slice 20 — Macro Regime History =====
|
|
CREATE TABLE IF NOT EXISTS regime_history (
|
|
id TEXT PRIMARY KEY,
|
|
date TEXT NOT NULL,
|
|
regime TEXT NOT NULL,
|
|
confidence INTEGER NOT NULL,
|
|
factors TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_regime_history_date ON regime_history(date DESC);
|
|
|
|
-- ===== Slice 21 — Thesis Monitoring =====
|
|
CREATE TABLE IF NOT EXISTS theses (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
symbol TEXT NOT NULL,
|
|
statement TEXT NOT NULL,
|
|
invalidation_criteria TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
status TEXT NOT NULL DEFAULT 'intact',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_theses_user ON theses(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_theses_symbol ON theses(symbol);
|