Trading Database — Access Runbook
Database: trading (PostgreSQL 15)
Host container: trading-postgres
Last updated: 2026-05-05
Connection Details
| Parameter | Value |
|---|---|
| Host (LAN) | [DB-HOST-LAN-IP] |
| Host (Tailscale) | [DB-HOST-TAILSCALE-IP] |
| Port | 5432 |
| Database | trading |
| Auth method | scram-sha-256 (password) |
| SSL | not required (plain TCP within trusted network) |
The port is bound to 0.0.0.0:5432 on the DGX host — reachable from any system on the same LAN
or Tailscale mesh.
Credentials
Two read-only accounts have been provisioned. Both are members of the trading_reader role
which grants SELECT on all current and future tables in the public schema.
| System | Username | Default password |
|---|---|---|
| miraiclaw | [db-user] |
[YOUR-DB-PASSWORD] |
| miraistudio | [db-user-2] |
[YOUR-DB-PASSWORD] |
Change these passwords immediately after first connection:
ALTER USER [db-user] PASSWORD 'your-strong-password'; ALTER USER [db-user-2] PASSWORD 'your-strong-password';
Permissions: SELECT only. No INSERT, UPDATE, DELETE, CREATE, or DROP.
Connecting
psql (CLI)
psql -h [DB-HOST-LAN-IP] -p 5432 -U [db-user] -d trading
# or via Tailscale
psql -h [DB-HOST-TAILSCALE-IP] -p 5432 -U [db-user] -d trading
Python — psycopg2
import psycopg2
conn = psycopg2.connect(
host="[DB-HOST-LAN-IP]",
port=5432,
dbname="trading",
user="[db-user]",
password="[YOUR-DB-PASSWORD]",
)
cur = conn.cursor()
cur.execute("SELECT ticker, date, close FROM market_data_daily ORDER BY date DESC LIMIT 5")
rows = cur.fetchall()
conn.close()
Python — SQLAlchemy
from sqlalchemy import create_engine
engine = create_engine(
"postgresql+psycopg2://[db-user]:[YOUR-DB-PASSWORD]@[DB-HOST-LAN-IP]:5432/trading"
)
with engine.connect() as conn:
result = conn.execute("SELECT * FROM tqqq_signals ORDER BY signal_date DESC LIMIT 1")
print(result.fetchall())
Python — pandas
import pandas as pd
import psycopg2
conn = psycopg2.connect(
host="[DB-HOST-LAN-IP]", port=5432, dbname="trading",
user="[db-user]", password="[YOUR-DB-PASSWORD]"
)
df = pd.read_sql(
"SELECT * FROM market_data_daily WHERE ticker = 'NVDA' ORDER BY date DESC LIMIT 30",
conn
)
conn.close()
Node.js — pg
const { Pool } = require('pg');
const pool = new Pool({
host: '[DB-HOST-LAN-IP]',
port: 5432,
database: 'trading',
user: '[db-user-2]',
password: '[YOUR-DB-PASSWORD]',
});
const res = await pool.query(
"SELECT * FROM tqqq_signals ORDER BY signal_date DESC LIMIT 1"
);
console.log(res.rows);
await pool.end();
Environment variable pattern (recommended)
Store credentials in .env and load them at runtime rather than hardcoding:
DB_HOST=[DB-HOST-LAN-IP]
DB_PORT=5432
DB_NAME=trading
DB_USER=miraiclaw
DB_PASSWORD=[YOUR-DB-PASSWORD]
import os, psycopg2
conn = psycopg2.connect(
host=os.environ["DB_HOST"],
port=int(os.environ.get("DB_PORT", 5432)),
dbname=os.environ["DB_NAME"],
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
)
Data Freshness
Ingestion runs on the DGX via cron (weekdays, all times DGX local / US Central):
| Time | What refreshes |
|---|---|
| 18:00 | market_data_daily, options_metrics_daily |
| 18:15 | ldtm_daily_snapshots, ldtm_run_log, model_versions |
| 18:30 | news_headlines, news_articles |
| 19:00 | Azure Blob export (read replica not applicable) |
| Sat 02:00 | model_versions canary retrain |
| Sun 01:00 | model_versions full retrain |
tqqq_signals and tqqq_daily_summary are populated on demand (not on a fixed cron).
Table Reference
Market Data
market_data_daily
OHLCV daily bars for the full NDX-100 universe + TQQQ, SQQQ, SPY, QQQ, IWM, DIA, plus ETFs (GLD, SLV, TLT, IEF, SHY, HYG, LQD, USO, UNG, GLD, VXX, UUP, FXE, FXY, FXA, FXB).
| Column | Type | Notes |
|---|---|---|
ticker |
text | PK |
date |
date | PK |
open |
float8 | |
high |
float8 | |
low |
float8 | |
close |
float8 | |
volume |
int8 |
Primary key: (ticker, date). Upsert-safe — re-running ingestion is idempotent.
-- Latest close for all tickers
SELECT ticker, date, close
FROM market_data_daily
WHERE date = (SELECT MAX(date) FROM market_data_daily)
ORDER BY ticker;
-- 90-day history for NVDA
SELECT date, open, high, low, close, volume
FROM market_data_daily
WHERE ticker = 'NVDA'
ORDER BY date DESC
LIMIT 90;
market_data
NVDA 1-minute intraday bars (legacy table, NVDA only).
| Column | Type | Notes |
|---|---|---|
ticker |
text | |
timestamp |
timestamptz | timezone-aware |
open |
float8 | |
high |
float8 | |
low |
float8 | |
close |
float8 | |
volume |
int8 |
No primary key constraint — rows may duplicate on re-run. Filter by timestamp range for recent bars.
-- Last 390 minutes (one trading day) of NVDA 1-min bars
SELECT timestamp, open, high, low, close, volume
FROM market_data
WHERE ticker = 'NVDA'
AND timestamp >= NOW() - INTERVAL '1 day'
ORDER BY timestamp;
options_metrics_daily
Daily implied volatility (IV) and historical volatility (HV) metrics per ticker, sourced from Interactive Brokers.
| Column | Type | Notes |
|---|---|---|
ticker |
text | PK |
date |
date | PK |
iv_open |
float8 | IV at open |
iv_high |
float8 | IV daily high |
iv_low |
float8 | IV daily low |
iv_close |
float8 | IV at close |
hv_open |
float8 | HV at open |
hv_high |
float8 | HV daily high |
hv_low |
float8 | HV daily low |
hv_close |
float8 | HV at close |
source |
text | Always 'interactive_brokers' |
updated_at |
timestamptz | Last upsert time |
Primary key: (ticker, date).
-- IV/HV for TQQQ over the last 30 days
SELECT date, iv_close, hv_close, iv_close - hv_close AS iv_premium
FROM options_metrics_daily
WHERE ticker = 'TQQQ'
ORDER BY date DESC
LIMIT 30;
News
news_headlines
Article metadata from IB news feeds. One row per (ticker, article_id) combination — the same article may appear under multiple tickers.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK (serial) |
ticker |
text | |
provider |
text | IB news provider code |
article_id |
text | IB's article identifier |
headline |
text | |
published_at |
timestamptz | |
fetched_at |
timestamptz | When this row was inserted |
-- Latest 20 headlines for NVDA
SELECT ticker, provider, headline, published_at
FROM news_headlines
WHERE ticker = 'NVDA'
ORDER BY published_at DESC
LIMIT 20;
-- Join headline to body
SELECT h.ticker, h.headline, h.published_at, a.body
FROM news_headlines h
JOIN news_articles a ON a.article_id = h.article_id
WHERE h.ticker = 'AAPL'
ORDER BY h.published_at DESC
LIMIT 5;
news_articles
Full article body text. Not all headlines have bodies — some providers are headline-only or paywalled (IB returns error 10172 for those).
| Column | Type | Notes |
|---|---|---|
article_id |
text | PK — matches news_headlines.article_id |
article_type |
text | IB content type |
body |
text | Full article text |
fetched_at |
timestamptz |
-- How many headlines have bodies vs. not
SELECT
COUNT(*) FILTER (WHERE a.article_id IS NOT NULL) AS with_body,
COUNT(*) FILTER (WHERE a.article_id IS NULL) AS headline_only
FROM news_headlines h
LEFT JOIN news_articles a ON a.article_id = h.article_id;
LDTM Model (LSTM Multi-Horizon Predictions)
ldtm_daily_snapshots
One row per (run_date, ticker) written each evening after LDTM inference. Actuals are filled
back by snapshot_fillback.py once the future date's close is available.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
run_date |
date | Date the model ran |
ticker |
text | UNIQUE with run_date |
generated_at |
timestamptz | |
next_day_close_pred |
float8 | Predicted next-day close |
next_monday_close_pred |
float8 | Predicted next Monday close |
one_month_close_pred |
float8 | Predicted 1-month close |
run_date_close |
float8 | Actual close on run_date |
next_day_actual |
float8 | Filled back: actual next-day close |
next_day_actual_date |
date | |
next_day_pct_error |
float8 | (pred - actual) / actual * 100 |
next_day_direction_pred |
text | 'UP' / 'DOWN' |
next_day_direction_actual |
text | Filled back |
next_day_direction_correct |
bool | Filled back |
next_monday_actual |
float8 | Filled back |
next_monday_actual_date |
date | |
next_monday_pct_error |
float8 | |
one_month_actual |
float8 | Filled back |
one_month_actual_date |
date | |
one_month_pct_error |
float8 | |
source_run_log_id |
int8 | FK → ldtm_run_log.id |
Unique constraint: (run_date, ticker).
-- Today's predictions for all tickers
SELECT ticker, next_day_close_pred, next_monday_close_pred, one_month_close_pred, run_date_close
FROM ldtm_daily_snapshots
WHERE run_date = CURRENT_DATE
ORDER BY ticker;
-- Most recent prediction per ticker
SELECT DISTINCT ON (ticker)
ticker, run_date, next_day_close_pred, next_day_direction_pred
FROM ldtm_daily_snapshots
ORDER BY ticker, run_date DESC;
ldtm_accuracy_30d (VIEW)
Rolling 30-day accuracy summary per ticker, computed live from ldtm_daily_snapshots.
| Column | Type | Notes |
|---|---|---|
ticker |
text | |
evaluated_days |
int8 | Days with filled-back actuals |
avg_abs_pct_error |
numeric | Mean absolute % error (next-day) |
avg_abs_pct_error_weekly |
numeric | Mean absolute % error (next Monday) |
direction_accuracy_pct |
numeric | % of correct UP/DOWN calls |
latest_run_date |
date | |
latest_next_day_pred |
float8 | Most recent next-day prediction |
-- Top 10 tickers by direction accuracy
SELECT ticker, direction_accuracy_pct, avg_abs_pct_error, evaluated_days
FROM ldtm_accuracy_30d
ORDER BY direction_accuracy_pct DESC
LIMIT 10;
ldtm_run_log
One row per model run (train or infer) — written by model/ldtm/ containers.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
run_at |
timestamptz | |
ticker |
text | |
mode |
text | 'train' or 'infer' |
status |
text | 'ok' or 'FAIL' |
duration_sec |
float8 | |
epochs_run |
int4 | |
best_val_loss |
float8 | |
next_day_close |
float8 | Prediction from this run |
next_monday_close |
float8 | |
one_month_close |
float8 | |
error_msg |
text | Populated on failure |
-- Last 10 inference runs and their status
SELECT run_at, ticker, mode, status, duration_sec, next_day_close
FROM ldtm_run_log
WHERE mode = 'infer'
ORDER BY run_at DESC
LIMIT 10;
ldtm_backtest_results
Walk-forward backtest results — one row per (ticker, train_cutoff, test_date, horizon).
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
ticker |
text | |
train_cutoff |
date | Last date of training window |
test_date |
date | Date being predicted |
horizon |
text | 'next_day', 'next_monday', 'one_month' |
pred_close |
float8 | |
actual_close |
float8 | |
pct_error |
float8 | |
direction_pred |
text | 'UP' / 'DOWN' |
direction_actual |
text | |
direction_correct |
bool | |
created_at |
timestamptz |
-- Direction accuracy by ticker and horizon
SELECT ticker, horizon,
COUNT(*) AS n,
ROUND(100.0 * SUM(CASE WHEN direction_correct THEN 1 ELSE 0 END) / COUNT(*), 1) AS dir_acc_pct,
ROUND(AVG(ABS(pct_error))::numeric, 2) AS avg_abs_err
FROM ldtm_backtest_results
GROUP BY ticker, horizon
ORDER BY dir_acc_pct DESC;
ldtm_backtest_summary (VIEW)
Aggregated from ldtm_backtest_results — one row per (ticker, horizon).
| Column | Type |
|---|---|
ticker |
text |
horizon |
text |
n_predictions |
int8 |
n_folds |
int8 |
avg_abs_pct_error |
numeric |
direction_accuracy_pct |
numeric |
earliest_cutoff |
date |
latest_cutoff |
date |
latest_test_date |
date |
model_versions
LDTM model registry — tracks candidate → active → retired lifecycle.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
ticker |
text | |
version |
text | Semantic version string |
status |
text | 'candidate', 'active', 'retired' |
checkpoint_path |
text | Path inside container |
window_size |
int4 | LSTM lookback window |
hidden_size |
int4 | LSTM hidden units |
num_layers |
int4 | |
trained_on_date |
date | |
val_loss |
float8 | |
bt_direction_acc_nextday |
float8 | Backtest direction accuracy |
bt_avg_pct_error_nextday |
float8 | |
promoted_at |
timestamptz | When moved to active |
retired_at |
timestamptz | When retired |
created_at |
timestamptz |
-- Currently active models
SELECT ticker, version, val_loss, bt_direction_acc_nextday, promoted_at
FROM model_versions
WHERE status = 'active'
ORDER BY ticker;
TQQQ RF Classifier
tqqq_signals
Daily directional signal for TQQQ from the Random Forest classifier.
| Column | Type | Notes |
|---|---|---|
id |
int4 | PK |
signal_date |
date | UNIQUE with horizon_days |
generated_at |
timestamptz | |
direction |
text | 'BUY', 'SHORT', 'HOLD' |
prob_buy |
float8 | Class probability |
prob_short |
float8 | |
prob_hold |
float8 | |
validation_precision |
float8 | Precision on held-out validation set |
horizon_days |
int4 | Signal horizon in trading days |
use_iv |
bool | Whether IV features were included |
actual_date |
date | Filled back: settlement date |
actual_return |
float8 | Filled back: TQQQ return over horizon |
actual_direction |
text | Filled back |
was_correct |
bool | Filled back |
latest_features |
jsonb | Feature snapshot at signal time |
model_file |
text | Path to the RF pickle |
Unique constraint: (signal_date, horizon_days).
-- Latest signal
SELECT signal_date, direction, prob_buy, prob_short, prob_hold, horizon_days
FROM tqqq_signals
ORDER BY signal_date DESC, horizon_days
LIMIT 5;
-- Historical accuracy
SELECT
direction,
COUNT(*) AS n,
ROUND(100.0 * SUM(CASE WHEN was_correct THEN 1 ELSE 0 END) / NULLIF(COUNT(*) FILTER (WHERE was_correct IS NOT NULL), 0), 1) AS accuracy_pct
FROM tqqq_signals
GROUP BY direction;
tqqq_backtest_runs
Walk-forward backtest results for the TQQQ RF strategy.
| Column | Type | Notes |
|---|---|---|
id |
int4 | PK |
run_date |
date | |
generated_at |
timestamptz | |
backtest_start / backtest_end |
date | Window tested |
horizon_days |
int4 | |
retrain_every |
int4 | Days between retrains |
use_iv |
bool | |
initial_capital |
float8 | |
final_capital |
float8 | |
return_pct |
float8 | |
cagr |
float8 | |
max_drawdown |
float8 | |
sharpe |
float8 | |
total_trades |
int4 | |
win_rate |
float8 | |
bh_return_pct |
float8 | Buy-and-hold benchmark return |
bh_final_capital |
float8 | |
alpha |
float8 | Strategy return − B&H return |
trade_log |
jsonb | Per-trade detail |
equity_curve |
jsonb | Daily equity values |
model_file |
text |
-- Latest backtest summary
SELECT run_date, return_pct, cagr, sharpe, max_drawdown, alpha, total_trades, win_rate
FROM tqqq_backtest_runs
ORDER BY run_date DESC
LIMIT 1;
tqqq_montecarlo_runs
Bootstrap Monte Carlo simulation results (1000 sims) based on a backtest's trade distribution.
| Column | Type | Notes |
|---|---|---|
id |
int4 | PK |
run_date |
date | |
generated_at |
timestamptz | |
source_backtest_id |
int4 | FK → tqqq_backtest_runs.id |
n_simulations |
int4 | |
n_trades |
int4 | |
initial_capital |
float8 | |
p05_final / p25_final / p50_final / p75_final / p95_final |
float8 | Percentile final capital |
p05_return_pct / p50_return_pct / p95_return_pct |
float8 | Percentile returns |
p05_max_drawdown / p50_max_drawdown / p95_max_drawdown |
float8 | |
prob_profit |
float8 | P(final > initial) |
prob_double |
float8 | P(final > 2× initial) |
raw_results |
jsonb | Full simulation distribution |
tqqq_daily_summary
LLM-ready JSONB summary blob, generated by model-tqqq-summarize. One row per trading day.
| Column | Type | Notes |
|---|---|---|
id |
int4 | PK |
summary_date |
date | UNIQUE |
generated_at |
timestamptz | |
summary_json |
jsonb | Structured summary for LLM context |
-- Today's summary
SELECT summary_date, summary_json
FROM tqqq_daily_summary
WHERE summary_date = CURRENT_DATE;
TFT Model
tft_run_log
One row per TFT training or inference run.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
run_at |
timestamptz | |
target |
text | Primary target ticker |
mode |
text | 'train' or 'infer' |
status |
text | 'ok' or 'FAIL' |
duration_sec |
float8 | |
epochs_run |
int4 | |
best_val_loss |
float8 | |
sequence_length |
int4 | Input sequence length |
context_count |
int4 | Number of context tickers |
last_close |
float8 | Target ticker's last close |
next_day_return |
float8 | Predicted return (1d) |
next_week_return |
float8 | Predicted return (1wk) |
one_month_return |
float8 | Predicted return (1mo) |
next_day_close |
float8 | Predicted absolute close |
next_week_close |
float8 | |
one_month_close |
float8 | |
context_tickers |
text[] | Array of context tickers used |
checkpoint_path |
text | |
error_msg |
text |
Dashboard / Custom Jobs
custom_run_jobs
Jobs submitted through the Streamlit dashboard for on-demand LDTM runs.
| Column | Type | Notes |
|---|---|---|
id |
int8 | PK |
ticker |
text | |
model_type |
text | Default 'ldtm' |
status |
text | 'queued', 'running', 'done', 'failed' |
window_size |
int4 | |
epochs |
int4 | |
started_at |
timestamptz | |
completed_at |
timestamptz | |
duration_sec |
int4 | |
error_msg |
text | |
last_close |
float8 | |
next_day_pred |
float8 | |
next_monday_pred |
float8 | |
one_month_pred |
float8 | |
checkpoint_path |
text |
Useful Cross-Table Queries
-- Latest LDTM prediction + 30d accuracy for every ticker
SELECT
s.ticker,
s.run_date,
s.next_day_close_pred,
s.next_day_direction_pred,
s.run_date_close,
a.direction_accuracy_pct,
a.avg_abs_pct_error
FROM (
SELECT DISTINCT ON (ticker) *
FROM ldtm_daily_snapshots
ORDER BY ticker, run_date DESC
) s
LEFT JOIN ldtm_accuracy_30d a ON a.ticker = s.ticker
ORDER BY a.direction_accuracy_pct DESC NULLS LAST;
-- TQQQ composite view: today's signal + latest backtest + latest MC
SELECT
ts.signal_date,
ts.direction,
ts.prob_buy,
ts.prob_short,
tb.cagr,
tb.sharpe,
tb.alpha,
tm.prob_profit,
tm.p50_return_pct AS mc_median_return
FROM tqqq_signals ts
LEFT JOIN tqqq_backtest_runs tb ON tb.run_date = ts.signal_date
LEFT JOIN tqqq_montecarlo_runs tm ON tm.run_date = ts.signal_date
WHERE ts.signal_date = (SELECT MAX(signal_date) FROM tqqq_signals);
-- Recent news with bodies for a ticker
SELECT h.ticker, h.headline, h.published_at, a.body
FROM news_headlines h
JOIN news_articles a ON a.article_id = h.article_id
WHERE h.ticker = 'NVDA'
AND h.published_at >= NOW() - INTERVAL '7 days'
ORDER BY h.published_at DESC;
Access Control Notes
- Both
miraiclawandmiraistudioare read-only via thetrading_readerrole. trading_readerhasSELECTon all present and future tables (ALTER DEFAULT PRIVILEGESis set).- Neither user can modify data, create objects, or access system catalogs beyond standard
pg_catalogreads. - To revoke access:
REVOKE trading_reader FROM miraiclaw; - To add another system:
CREATE USER newsystem WITH PASSWORD '...'; GRANT trading_reader TO newsystem;
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Connection refused on port 5432 |
trading-postgres container not running |
docker start trading-postgres |
password authentication failed |
Wrong password or user | Verify user exists: SELECT rolname FROM pg_roles; |
FATAL: no pg_hba.conf entry |
Client IP not covered | Check pg_hba.conf — current rule allows all IPs with password |
| Stale data | Cron job failed | Check schedule/logs/cron.log on DGX |
permission denied for table X |
New table added before DEFAULT PRIVILEGES took effect |
GRANT SELECT ON TABLE X TO trading_reader; |