Codex LD-TM LSTM Implementation, Architecture, Model Design, and Runbook
Date: 2026-04-22
Implementation name: LD-TM LSTM, Long-term Deep Temporal Model
Status: implemented locally. Azure deployment is intentionally deferred.
Executive Summary
LD-TM is a daily, multi-horizon prediction pipeline for the DGX trading system. It trains one LSTM model per ticker, writes inference outputs into PostgreSQL, fills actual results later, exposes prediction history through a Streamlit dashboard, and supplies live database context to a local LLM query layer.
The current implementation covers 103 tickers, including NDX-style equities plus TQQQ and SQQQ. The latest observed snapshot set contains 103 ticker rows for 2026-04-22. The database currently holds 104 snapshot rows across 103 tickers, with first observed snapshot date 2026-04-21 and latest snapshot date 2026-04-22.
Observed local performance from ldtm_run_log:
| Mode | Successful runs | Average seconds | Min seconds | Max seconds |
|---|---|---|---|---|
| infer | 105 | 0.60 | 0.33 | 0.88 |
| train | 105 | 44.78 | 2.71 | 128.94 |
Training aggregate:
| Metric | Value |
|---|---|
| Median train time | 35.71 sec |
| P90 train time | 84.08 sec |
| Average epochs before early stop | 21.3 |
| Average best validation loss | 0.711670 |
Inference aggregate:
| Metric | Value |
|---|---|
| Average inference time | 0.605 sec |
| Median inference time | 0.611 sec |
| P90 inference time | 0.812 sec |
Current observed container memory snapshot:
| Container | Memory usage |
|---|---|
trading-dashboard-test |
40.32 MiB |
trtllm-server |
2.91 GiB |
trading-postgres |
93.88 MiB |
prometheus |
45.64 MiB |
grafana |
113.8 MiB |
GPU query result:
| GPU | Name | Reported total memory | Reported used memory | Utilization |
|---|---|---|---|---|
| 0 | NVIDIA GB10 | N/A | N/A | 0% |
The GB10 runtime reports GPU memory as N/A through nvidia-smi, so the orchestrator uses a fallback concurrency model for MIG-like or virtual GPU reporting.
Implementation Inventory
| Area | Path | Purpose |
|---|---|---|
| Model config | model/ldtm/config.py |
Hyperparameters and shared model configuration |
| Dataset | model/ldtm/dataset.py |
OHLCV load, feature engineering, sliding windows, labels |
| Model | model/ldtm/model.py |
Two-layer LSTM with three prediction heads |
| Training | model/ldtm/trainer.py |
AdamW training loop, AMP, scheduler, early stopping |
| Inference | model/ldtm/predict.py |
Load checkpoint, build latest window, output predictions |
| Run log schema | model/ldtm/schema.sql |
ldtm_run_log table |
| Snapshot schema | model/ldtm/snapshots_schema.sql |
ldtm_daily_snapshots table and ldtm_accuracy_30d view |
| Snapshot writer | model/ldtm/snapshot_writer.py |
Converts inference log rows into daily snapshots |
| Fill-back | model/ldtm/snapshot_fillback.py |
Writes actual closes and error metrics after targets mature |
| Orchestrator | model/ldtm/orchestrate.py |
GPU-aware parallel train/infer dispatch |
| CLI runner | model/ldtm/run_ldtm.sh |
User-facing train/infer command |
| Dashboard | dashboard/app.py |
Streamlit dashboard |
| Blob export | dashboard/export_to_blob.py |
Optional static JSON export for Azure Blob |
| LLM query | llm/llm_query.py |
OpenAI-compatible local LLM query over live DB context |
| Cron install | schedule/install_cron.sh |
Idempotent cron job installer |
| Daily inference cron | schedule/run_ldtm_infer.sh |
Inference, snapshots, fill-back |
| Canary retrain cron | schedule/run_ldtm_canary_retrain.sh |
Weekly NVDA/TQQQ/AAPL retrain |
| Monthly retrain cron | schedule/run_ldtm_monthly_retrain.sh |
First-Sunday full retrain |
| Blob export cron | schedule/run_blob_export.sh |
Optional nightly Blob export |
Architecture
market_data_daily
|
v
OHLCV feature builder
|
v
30-trading-day normalized windows
|
v
LD-TM LSTM model per ticker
|
+--> trainer.py -> checkpoints -> ldtm_run_log
|
+--> predict.py -> ldtm_run_log
|
v
snapshot_writer.py
|
v
ldtm_daily_snapshots
|
+--> snapshot_fillback.py -> actuals, errors, accuracy
|
+--> ldtm_accuracy_30d view
|
+--> dashboard/app.py
|
+--> llm/llm_query.py
|
+--> optional export_to_blob.py
Runtime services:
| Service | Role |
|---|---|
trading-postgres |
Durable relational store for market data, predictions, run logs, summaries |
model-ldtm image |
Training and inference runtime |
trading-dashboard image |
Streamlit dashboard |
llm-query image |
CLI and dashboard query bridge into local OpenAI-compatible LLM |
trtllm-server |
Local LLM serving endpoint at http://localhost:8000/v1 |
| Monitoring stack | Prometheus, Grafana, node exporter, cAdvisor, DCGM exporter |
AI Model Design
LD-TM uses one trained model per ticker. The model consumes the most recent 30 valid trading days and predicts three future closing prices:
| Horizon | Definition |
|---|---|
next_day |
Next available trading day's close |
next_monday |
First future Monday trading close after the window |
one_month |
Close approximately 21 trading days after the window |
Input tensor:
X shape = (batch_size, 30, 11)
The 11 features are:
| Index | Feature | Meaning |
|---|---|---|
| 0 | open | Daily open |
| 1 | high | Daily high |
| 2 | low | Daily low |
| 3 | close | Daily close |
| 4 | volume | Daily volume |
| 5 | ret1 | 1-day log return |
| 6 | ret5 | 5-day log return |
| 7 | rsi14 | Wilder 14-period RSI |
| 8 | ma5 | 5-day simple moving average |
| 9 | ma10 | 10-day simple moving average |
| 10 | ma20 | 20-day simple moving average |
Network architecture:
Input: (B, 30, 11)
|
v
LSTM, hidden_size=128, num_layers=2, dropout=0.2, batch_first=True
|
v
Last hidden state: (B, 128)
|
+--> Head next_day: Linear(128, 64) -> ReLU -> Linear(64, 1)
+--> Head next_monday: Linear(128, 64) -> ReLU -> Linear(64, 1)
+--> Head one_month: Linear(128, 64) -> ReLU -> Linear(64, 1)
Training settings:
| Setting | Value |
|---|---|
| Window size | 30 trading days |
| Input size | 11 |
| Hidden size | 128 |
| LSTM layers | 2 |
| Dropout | 0.2 |
| Batch size | 32 |
| Epochs | 100 |
| Early stopping patience | 10 |
| Optimizer | AdamW |
| Learning rate | 1e-3 |
| Weight decay | 1e-4 |
| Scheduler | CosineAnnealingLR |
| Loss | Sum of MSE over three horizons |
| AMP | Enabled when CUDA is available |
| Split | 70% train, 15% validation, 15% test by chronological window order |
Mathematical Concepts and Formulas
Daily log return:
ret1_t = ln(close_t / close_(t-1))
Five-day log return:
ret5_t = ln(close_t / close_(t-5))
Moving average for window length k:
MA_k(t) = (1 / k) * sum(close_(t-i), i = 0 to k-1)
RSI:
RS_t = avg_gain_t / (avg_loss_t + epsilon)
RSI_t = 100 - 100 / (1 + RS_t)
Per-window min-max normalization:
x_scaled[t, f] = (x[t, f] - min_f) / (max_f - min_f + epsilon)
Future-close label scaling:
y_scaled = (future_close - close_min) / (close_max - close_min + epsilon)
Prediction inverse transform:
pred_close = y_pred_scaled * (close_max - close_min) + close_min
Multi-horizon training loss:
loss = MSE(y_hat_next_day, y_next_day)
+ MSE(y_hat_next_monday, y_next_monday)
+ MSE(y_hat_one_month, y_one_month)
Percent error:
pct_error = ((predicted_close - actual_close) / actual_close) * 100
Next-day direction:
next_day_direction_pred =
"UP" if next_day_close_pred > run_date_close
"DOWN" otherwise
Direction accuracy:
direction_accuracy_pct =
100 * count(next_day_direction_correct = true)
/ count(next_day_direction_correct is not null)
Implied one-month return:
implied_1m_return_pct =
((one_month_close_pred / next_day_close_pred) - 1) * 100
PostgreSQL implementation:
ROUND((((one_month_close_pred / NULLIF(next_day_close_pred, 0)) - 1) * 100)::numeric, 2)
The explicit ::numeric cast fixed the PostgreSQL error:
function round(double precision, integer) does not exist
Database Tables and Views
market_data_daily
Expected upstream table:
| Column | Type | Purpose |
|---|---|---|
| ticker | TEXT | Ticker symbol |
| date | DATE | Trading date |
| open | FLOAT | Open price |
| high | FLOAT | High price |
| low | FLOAT | Low price |
| close | FLOAT | Close price |
| volume | BIGINT | Volume |
Primary key:
(ticker, date)
ldtm_run_log
Run ledger for training and inference:
| Column | Purpose |
|---|---|
| id | Surrogate primary key |
| run_at | Timestamp of run |
| ticker | Ticker symbol |
| mode | train or infer |
| status | success or failed |
| duration_sec | Runtime in seconds |
| epochs_run | Training epochs completed, null for inference |
| best_val_loss | Best validation loss, null for inference |
| next_day_close | Inference output, null for training |
| next_monday_close | Inference output, null for training |
| one_month_close | Inference output, null for training |
| error_msg | Failure details |
ldtm_daily_snapshots
Daily prediction and evaluation table, one row per (run_date, ticker).
| Column | Purpose |
|---|---|
| id | Surrogate primary key |
| run_date | Date prediction was generated |
| ticker | Ticker |
| generated_at | Insert/update timestamp |
| next_day_close_pred | Predicted next trading close |
| next_monday_close_pred | Predicted next Monday close |
| one_month_close_pred | Predicted close about 21 trading days out |
| run_date_close | Close on prediction date, if available |
| next_day_actual | Actual next trading close after fill-back |
| next_day_actual_date | Actual date used |
| next_day_pct_error | Percent error for next-day prediction |
| next_day_direction_pred | UP/DOWN predicted from run-date close |
| next_day_direction_actual | UP/DOWN actual from run-date close |
| next_day_direction_correct | Boolean correctness |
| next_monday_actual | Actual next Monday close after fill-back |
| next_monday_actual_date | Actual next Monday date |
| next_monday_pct_error | Percent error for next-Monday prediction |
| one_month_actual | Actual close around 21 trading days later |
| one_month_actual_date | Actual date used |
| one_month_pct_error | Percent error for one-month prediction |
| source_run_log_id | FK to the inference run |
Observed latest sample outputs for 2026-04-22:
| Ticker | Next day pred | Next Monday pred | One month pred |
|---|---|---|---|
| AAPL | 268.55 | 268.74 | 275.05 |
| ABNB | 144.57 | 143.57 | 144.68 |
| ADBE | 245.30 | 247.47 | 265.87 |
| ADI | 380.51 | 382.01 | 376.79 |
| ADP | 204.62 | 205.47 | 215.16 |
ldtm_accuracy_30d
Dashboard view for recent accuracy:
| Column | Meaning |
|---|---|
| ticker | Ticker |
| evaluated_days | Number of filled next-day predictions |
| avg_abs_pct_error | Average absolute next-day percent error |
| avg_abs_pct_error_weekly | Average absolute next-Monday percent error |
| direction_accuracy_pct | Percent of filled next-day directions correct |
| latest_run_date | Latest prediction date |
| latest_next_day_pred | Latest next-day prediction |
At the time of this document, the view exists but evaluation fields are mostly empty because actuals have not yet matured for the newest predictions.
Outputs Returned
Training final output:
[LDTM] RESULT: ticker=AAPL epochs=... val_loss=... duration=...s
Inference JSON:
{
"ticker": "AAPL",
"next_day_close": 268.55,
"next_monday_close": 268.74,
"one_month_close": 275.05
}
Snapshot writer output:
[snapshot_writer] Wrote N snapshots for YYYY-MM-DD
Fill-back output:
[snapshot_fillback] Updated N rows
Dashboard URL:
http://localhost:8501
Dashboard tabs:
| Tab | Output |
|---|---|
| Ticker View | Prediction history, actual close, next-day actual, one-month prediction, error chart |
| Today's Predictions | All latest ticker rows sorted by implied one-month return |
| Accuracy Leaderboard | 30-day direction accuracy and average absolute error |
| TQQQ Signal | Latest TQQQ strategy JSON summary |
| LLM Query | Natural language answer using predictions, accuracy, training metadata, and headlines |
Current Prediction Distribution
For latest run date 2026-04-22:
| Bucket | Count |
|---|---|
| Bullish, implied one-month return greater than 1% | 68 |
| Neutral, between -1% and 1% | 26 |
| Bearish, below -1% | 9 |
Top implied one-month returns:
| Ticker | Implied 1m return | Next day pred | One month pred |
|---|---|---|---|
| AMD | 11.62% | 282.22 | 315.00 |
| APP | 8.52% | 457.32 | 496.28 |
| INTU | 8.44% | 408.80 | 443.29 |
| ADBE | 8.39% | 245.30 | 265.87 |
| CSGP | 7.57% | 39.88 | 42.90 |
Lowest implied one-month returns:
| Ticker | Implied 1m return | Next day pred | One month pred |
|---|---|---|---|
| ODFL | -21.02% | 220.74 | 174.35 |
| SQQQ | -6.19% | 56.66 | 53.15 |
| DDOG | -3.59% | 128.96 | 124.33 |
| DASH | -1.91% | 186.34 | 182.78 |
| MAR | -1.87% | 377.10 | 370.03 |
These are model outputs, not trading advice.
Scheduler Design
| Time | Job |
|---|---|
| 6:00 PM Mon-Fri | Market data ingestion |
| 6:15 PM Mon-Fri | LD-TM inference, snapshot write, fill-back |
| 6:30 PM Mon-Fri | News ingestion |
| 7:00 PM Mon-Fri | Optional Azure Blob export |
| 2:00 AM Saturday | Canary retrain for NVDA, TQQQ, AAPL |
| 1:00 AM Sunday | Monthly retrain script, guarded to first Sunday only |
run_ldtm_monthly_retrain.sh is scheduled every Sunday at 1 AM, but exits unless the day of month is 1 through 7.
Runbook
1. Start from project root
cd /home/aimikamirai/projects/dgx-trading-system
2. Confirm .env
Required variables:
DB_HOST
DB_PORT
DB_NAME
DB_USER
DB_PASSWORD
Optional variables:
AZURE_BLOB_CONN_STR
AZURE_BLOB_CONTAINER
AZURE_BLOB_URL
LLM_BASE_URL
LLM_API_KEY
LLM_MODEL
Azure deployment is not required. Blob export remains optional and should be treated as inactive until AZURE_BLOB_CONN_STR exists.
3. Build model image
docker build -t model-ldtm ./model/ldtm
4. Initialize LD-TM run log
bash model/ldtm/run_ldtm.sh --init-db
5. Initialize snapshot schema
docker exec -i trading-postgres psql -U postgres -d trading < model/ldtm/snapshots_schema.sql
6. Train one ticker
bash model/ldtm/run_ldtm.sh --ticker AAPL --mode train --epochs 100 --env_file .env
7. Infer one ticker
bash model/ldtm/run_ldtm.sh --ticker AAPL --mode infer --env_file .env
8. Train and infer one ticker
bash model/ldtm/run_ldtm.sh --ticker AAPL --mode both --env_file .env
9. Train or infer a group
bash model/ldtm/run_ldtm.sh --group mega_cap --mode train --parallel 4 --env_file .env
Available groups:
mega_cap, semis, software, internet, consumer, healthcare, industrial,
telecom, financials, hardware, intl, etfs
10. Infer all tickers
bash model/ldtm/run_ldtm.sh --all --mode infer --parallel 16 --env_file .env
11. Run the daily inference pipeline manually
bash schedule/run_ldtm_infer.sh
This performs:
all ticker inference -> snapshot writer -> snapshot fill-back
12. Build and run dashboard
docker build -t trading-dashboard ./dashboard
docker run -d --name trading-dashboard-test \
--network host --env-file .env \
-e DATA_SOURCE=db \
trading-dashboard
Open:
http://localhost:8501
Health check:
curl -s http://localhost:8501/healthz
13. Run LLM query
The LLM query layer expects an OpenAI-compatible endpoint:
http://localhost:8000/v1
Example:
docker compose --profile llm run --rm llm-query \
python llm_query.py --group mega_cap \
--question "Which tickers have the strongest 1-month signal and why?"
14. Install cron jobs
Ensure scripts are executable:
chmod +x schedule/run_ldtm_infer.sh \
schedule/run_ldtm_canary_retrain.sh \
schedule/run_ldtm_monthly_retrain.sh \
schedule/run_blob_export.sh
Install:
bash schedule/install_cron.sh
Verify:
crontab -l
15. Optional Blob export
Do not deploy to Azure right now. When Blob export is desired later:
- Add
AZURE_BLOB_CONN_STRto.env. - Optionally set
AZURE_BLOB_CONTAINER. - Run:
docker compose --profile blob run --rm blob-export
Operations and Troubleshooting
PostgreSQL ROUND error
Symptom:
function round(double precision, integer) does not exist
Fix:
ROUND((some_double_expression)::numeric, 2)
No snapshot rows
Check latest inference logs:
SELECT ticker, mode, status, next_day_close, run_at
FROM ldtm_run_log
WHERE mode = 'infer'
ORDER BY run_at DESC
LIMIT 20;
Accuracy is empty
This is expected immediately after first snapshots. ldtm_accuracy_30d needs future market data to fill next_day_actual, next_monday_actual, and one_month_actual.
Dashboard shows no data
Check:
SELECT COUNT(*), MAX(run_date) FROM ldtm_daily_snapshots;
Then confirm DATA_SOURCE=db and DB env vars are available inside the dashboard container.
LLM query fails
Check:
docker ps
Confirm the local LLM endpoint is up:
trtllm-server on port 8000
Performance Notes and Efficiency Plan
The current LD-TM model is small enough that per-ticker inference is fast, averaging about 0.605 seconds. Training is dominated by per-ticker dataset loading, DataLoader overhead, Docker startup overhead, and epoch count before early stopping. Average training time is 44.78 seconds per ticker, with median 35.71 seconds and P90 84.08 seconds.
The dashboard memory footprint is small, around 40 MiB at the observed moment. The local LLM server is the major resident memory consumer at about 2.91 GiB CPU/container memory, not counting GPU allocation that may not be reported by the GB10 runtime.
The orchestrator estimates GPU slots as:
slots = max(1, min(16, vram_mib / 600))
When GPU memory is N/A, it uses:
fallback_slots_per_gpu = 4
Recommended improvements:
| Change | Expected benefit |
|---|---|
| Keep a warm inference worker instead of launching one Docker container per ticker | Removes most Docker startup overhead |
| Batch tickers inside one process | Reduces Python and model initialization overhead |
| Load all checkpoints once per run | Avoid repeated disk IO |
| Use one container with internal multiprocessing | Better than many short-lived containers |
| Increase DataLoader workers for larger datasets | Helps if CPU preprocessing becomes bottleneck |
| Persist engineered features | Avoid recomputing RSI/MA/log returns for every training run |
| Use pinned memory when CUDA path is active | Faster CPU to GPU transfers |
| Use FP16 AMP consistently | Already enabled on CUDA for training; can extend to inference |
Use torch.compile where stable |
May reduce model overhead for repeated inference |
| Export ONNX/TensorRT engines for inference | Best latency path for fixed architecture |
| Group small models into batched inference service | Higher GPU utilization |
FP16 AMP is already used in training when CUDA is available. Flash Attention is not the primary lever for LD-TM because the model is an LSTM, not a Transformer attention stack. Flash Attention is more relevant to the local Mistral/TensorRT-LLM service. For LD-TM, better acceleration paths are FP16 inference, warm model execution, ONNX export, TensorRT engines where supported, and a persistent batched service.
Design Notes and Risks
- Per-window normalization makes the model robust to price scale, but predictions can exceed the local min/max regime.
- Accuracy metrics are intentionally sparse until actual market closes mature.
- Top and bottom implied returns are raw model signals and need accuracy weighting before becoming trading candidates.
- One model per ticker is operationally simple and isolates ticker behavior, but it prevents cross-sectional learning.
- The local LLM is an explanation and query layer, not the source of prediction truth.
- Blob export is ready as an optional static publishing path, but Azure deployment is paused by request.
Recommended Next Steps
- Let fill-back run for several trading days before judging direction accuracy.
- Add a dashboard confidence score blending implied return, direction accuracy, evaluated days, and average absolute percent error.
- Build a persistent inference service to reduce all-ticker inference time.
- Add a feature cache table or parquet cache for engineered features.
- Add benchmark logging for GPU memory, CPU memory, wall-clock per stage, and queue time.
- Add a model card row per ticker with last train date, epochs, validation loss, inference latency, and latest signal.
- Keep Azure deployment deferred until local accuracy, cost, and security posture are satisfactory.