Skip to content

Phase 9 Session 2 — HGT retraining on 140-ticker graph + GNN drift factor null result#8

Merged
shiviancodes merged 5 commits into
mainfrom
feature/phase9-session2
May 17, 2026
Merged

Phase 9 Session 2 — HGT retraining on 140-ticker graph + GNN drift factor null result#8
shiviancodes merged 5 commits into
mainfrom
feature/phase9-session2

Conversation

@shiviancodes

Copy link
Copy Markdown
Owner

Summary

Retrains the HGT link-prediction model on the Phase 8 Session 2 140-ticker graph, regenerates node_embeddings under model_version='hgt_link_pred_v2', and re-tests the
previously-excluded graph_gnn_embedding_drift factor at full power. The factor is definitively null and is registered as status='rejected'.

Motivation

Since Phase 8 Session 2, graph_gnn_embedding_drift has been excluded from every backtest because node_embeddings was populated by a model trained on the 30-ticker
graph. Running that model against the 140-ticker universe produces out-of-distribution embeddings. Phase 8 Session 2 deferred the retrain; this session closes it out.

Results

Step 1 — HGT retraining

Item Value
Entry python -m nexus.graph.gnn.trainer
Code changes to trainer/dataset none — HeteroData.metadata() extracted dynamically
Hyperparameters unchanged from Phase 2 (hidden=64, heads=4, layers=2, dropout=0.2, lr=5e-3, 300 epochs)
Params 155,184
Train BCE 0.348 → 0.197 (monotonic)
Best val AUC 0.9807 @ epoch 280 (Phase 2 30-ticker run: 0.9803 @ e=240)
Wall-clock 7m 03s
Gate PASS (≥ 0.60)

Step 2 — Embedding backfill + validation

  • python -m nexus.graph.backfill wrote 8,120 rows (140 companies × 58 month-ends, 2021-06-30 → 2026-03-31) under model_version='hgt_link_pred_v2' (UPSERTed over
    prior v1 rows). Wall-clock 86s.
  • Snapshot dates match centrality_history exactly — point-in-time integrity satisfied by construction.
  • Structural validation at 2026-03-31:
Check Result
Per-component std median 0.0515, range [0.018, 0.231] — non-degenerate
cos(NVDA, AMD) vs cos(NVDA, ARW) 0.9812 > 0.6315
cos(AMD, INTC) vs cos(AMD, AVT) 0.9606 > 0.9099
cos(LRCX, AMAT) vs cos(LRCX, ARW) 0.9902 > 0.7988

Step 3 — IC backtest for graph_gnn_embedding_drift

Horizon N mean_IC t_stat HLZ M=400 Bonferroni
21d 57 +0.0075 +0.382 fail (`
63d 55 +0.0101 +0.524 fail (`
126d 52 +0.0077 +0.368 fail (`

|t| < 1 at every horizon. Decision matrix verdict: NULL at all horizons → do NOT register as alpha. Recorded in signal_registry as status='rejected' with full
per-horizon evidence in regime_profile JSONB so future sessions do not silently re-test the factor.

The retrained embeddings are structurally meaningful (Step 2 gates passed decisively). What this rules out is that month-over-month cosine drift in those embeddings
predicts forward returns on the semiconductor universe at full N=140 power. The Phase 8 N=30 finding (mean_IC −0.006, DECLINING) is now confirmed at full power, not as a
small-sample artifact.

…_version hgt_link_pred_v2

Retrained the HGT link-prediction model on the Phase 8 Session 2 graph
(140 equities, 21–22 funds, 14 directors, 4 extended; 738 supply, 2,141
ownership, 14 board edges at the 2023-Q4 train target).

Training run:
- Entry: python -m nexus.graph.gnn.trainer (no code changes needed —
  HeteroData metadata is extracted dynamically; HGTConv adapts to the
  larger graph)
- Hyperparameters unchanged from Phase 2: hidden=64, heads=4, layers=2,
  dropout=0.2, lr=5e-3, weight_decay=1e-4, 300 epochs, val_every=20
- Params: 155,184
- Train BCE 0.348 → 0.197 (monotonic descent over 300 epochs)
- Val AUC trajectory: 0.9658 (e=20) → 0.9807 (e=280)
- Gate PASS (>= 0.60); checkpoint saved to models/hgt_link_pred.pt
- Wall-clock: 7m 03s

The Phase 2 30-ticker checkpoint reached 0.9803 at e=240. The 140-ticker
re-run reaches comparable AUC, confirming the link-prediction objective
remains informative at the expanded universe size.

MODEL_VERSION in nexus/graph/backfill.py bumped v1 -> v2 to distinguish
the retrained embeddings from the historical 30-ticker rows that
previously occupied node_embeddings.
…er hgt_link_pred_v2

Ran python -m nexus.graph.backfill (default window 2021-06-30 → 2026-03-31).
Snapshot dates match centrality_history exactly (point-in-time integrity
satisfied by construction).

DB-side result:
- node_embeddings: 8,120 rows = 140 companies × 58 month-ends
- embedding_dim = 64 (constant)
- model_version = 'hgt_link_pred_v2' on every row (UPSERT against the
  composite PK (company_id, snapshot_date) overwrote prior v1 rows)
- Wall-clock: 86s

Validation spot-checks at the latest snapshot (2026-03-31):
- per-component std median 0.0515 (non-degenerate; range [0.018, 0.231])
- per-row L2 norm median 1.92
- cos(NVDA, AMD)   = 0.9812  >  cos(NVDA, ARW) = 0.6315  (peer vs distributor)
- cos(AMD, INTC)   = 0.9606  >  cos(AMD, AVT)  = 0.9099  (x86 peers vs distributor)
- cos(LRCX, AMAT)  = 0.9902  >  cos(LRCX, ARW) = 0.7988  (equipment peers vs distributor)

Structural ordering is decisive; embeddings encode the supply-chain /
ownership topology as intended.

This commit is empty by design (no source change). It records the
operational milestone in the same history as the code commits that
flank it.
… horizons (t<1), not registered

Re-tested graph_gnn_embedding_drift on the 140-ticker universe using the
retrained v2 embeddings (the factor had been excluded from every backtest
since Phase 8 Session 2 via .pop() in research/phase8_backtest.py because
the prior 30-ticker embeddings were OOD on the expanded universe).

Multi-horizon IC results (58 monthly rebalances, avg cross-section ~138.6):

  horizon    N   mean_IC    t_stat   HLZ (M=400 Bonferroni)
  -------    --  --------   ------   ----------------------
  21d        57  +0.0075    +0.382   fail (|t| < 4.123)
  63d        55  +0.0101    +0.524   fail (|t| < 4.134)
  126d       52  +0.0077    +0.368   fail (|t| < 4.153)

|t| < 1 at every horizon. Regime/sub-window splits suppressed because
the |t| > 1.5 trigger never fires.

Verdict under the Phase 9 Session 1 decision matrix:
  NULL (|t| < 2 at all horizons) → do NOT register.

The retrained embeddings are structurally meaningful (the validation
spot-checks above showed peer/distributor cosine ordering passes
decisively). What this run rules out is that month-over-month cosine
drift in those embeddings predicts forward returns on the semiconductor
universe at full N=140 power. This matches the Phase 8 N=30 finding
(mean_IC -0.006, classified DECLINING) and is now confirmed at full power.

Adds two research artifacts:
- research/phase9_gnn_drift_backtest.py — multi-horizon IC + HLZ report
- research/_validate_embeddings.py — embedding validation spot-checks,
  underscore-prefixed to mark as a research/operator tool rather than
  a permanent code path

Neither script writes to the database.
…l_registry

Idempotent INSERT ... ON CONFLICT (name) DO UPDATE writes:
  name           = graph_gnn_embedding_drift
  type           = graph
  tier           = A
  status         = rejected
  t_stat         = +0.524   (highest |t| across tested horizons, at 63d)
  hlz_passes     = false
  regime_profile = JSONB with per-horizon n/mean_ic/t_stat, evidence note,
                   and reference to the Phase 8 N=30 DECLINING finding

Recording the verdict in signal_registry so future sessions do not
re-test the factor without first reading docs/progress/phase_9.md.
The factor remains computable on demand via the existing composer in
nexus/signals/factors/graph_based.py; this commit only adds the
registry row.
…initively null

Session 2 retrained HGT on the Phase 8 Session 2 140-ticker graph and
re-tested the previously-excluded graph_gnn_embedding_drift factor at
full power.

Outcome:
- HGT val AUC 0.9807 at epoch 280 (Phase 2 30-ticker: 0.9803 at e=240)
- node_embeddings re-backfilled: 8,120 rows under model_version
  'hgt_link_pred_v2' (140 companies x 58 monthly snapshots)
- Embedding structural validation passed (peer/distributor cosine
  ordering decisive at three test pairs)
- graph_gnn_embedding_drift IC: t=+0.382/+0.524/+0.368 at 21/63/126d;
  |t| < 1 at every horizon; HLZ fail by 10x
- Decision matrix verdict: NULL at all horizons -> registered as
  status='rejected' in signal_registry
- Paper trader Step 4 skipped per the hard constraint (was conditional
  on Step 3 approving registration); Phase 9 Session 1 result remains:
  CAGR +8.72%, Sharpe 0.488, Max DD -32.68%

The full Session 2 evidence record (training curve, validation
spot-checks, per-horizon IC + HLZ, registry JSONB profile) lives in
docs/progress/phase_9.md (gitignored, local-only). This commit appends
the PROGRESS.md row that points to it.

Phase 9 status: 1 fundamentals factor registered as alpha
(fundamental_margin_compression), 1 graph-GNN factor registered as
rejected (graph_gnn_embedding_drift). Next lane open.
@shiviancodes shiviancodes merged commit 398f841 into main May 17, 2026
1 check passed
@shiviancodes shiviancodes deleted the feature/phase9-session2 branch May 17, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant