Explain a trading signal by building a feature-contribution graph and running single-entry forward-push PageRank from the signal output node. Top-K ranked features are returned as a markdown table AND persisted to trading-analysis as a SignedAttributionArtifact (ADR-126 Phase 6).
-
Retrieve the signal from the canonical trading-signals namespace (ADR-126 Phase 1 + Phase 2 lifecycle):
mcp__ruflo__memory_retrieve({
key: "SIGNAL_ID",
namespace: "trading-signals"
})
The signal entry includes modelId, prediction, and the feature vector at the time of inference.
-
Extract per-feature contribution scores from the model:
npx neural-trader --predict --signal "$SIGNAL_ID" --explain --json
The expected output shape:
{
features: Array<{ name: string; contribution: number }>;
attention?: Array<{ head: string; cooccur: Array<[number, number, number]> }>;
}
Fallback path — if --explain is not shipped on the installed neural-trader build (older versions; the flag was scoped for a follow-up upstream PR), the skill degrades to a deterministic feature-importance heuristic over the signal's input vector: contribution_i = |input_i - μ_i| / σ_i (z-score magnitude). This is a known proxy — not as faithful as attention/SHAP — and the resulting artifact is tagged attribution_method: "input-zscore-fallback" so downstream consumers can filter it out for regulator filings. Document the fallback path in the resulting markdown summary so the agent surfaces it to the user.
-
Build the feature-contribution graph:
- Nodes: one node per feature + one source node
__signal_output__ for the prediction.
- Edges: outgoing edges from
__signal_output__ to each feature node, weighted by contribution_i. When attention co-occurrence data is available, also add edges between feature nodes weighted by cooccur — this is what makes the PageRank single-entry rather than degenerating to plain top-K.
- Source:
__signal_output__ (index 0 by convention so the smoke can assert reproducibility).
-
Run single-entry PageRank — preferred path when mcp__ruflo-sublinear__page-rank-entry is registered:
mcp__ruflo-sublinear__page-rank-entry({
nodes: GRAPH_NODES,
edges: GRAPH_EDGES,
sourceIndex: 0,
damping: 0.85,
maxIterations: 100,
tolerance: 1e-8,
seed: 42
})
The local fallback (localSingleEntryPageRank in plugins/ruflo-neural-trader/src/signed-attribution.mjs) runs ~30 LOC of seeded power-iteration when the MCP tool is not available — same math, same result up to floating-point tolerance, same ordering for the same seed (the Phase 6 smoke asserts this).
-
Build the top-K AttributionFeature[] via topKFeatures(graph, scores, k=10, excludeIndex=0) — excludes the source node from the ranked output. Ties broken by node index (lower index wins) so the ranking is deterministic.
-
Sign the artifact (reuses the Phase 4 signing primitives — same Ed25519 + canonicalization):
- Build the
SignedAttributionArtifact body:
{
signalId: SIGNAL_ID,
modelId: SIGNAL.modelId,
features: TOP_K_FEATURES,
graphMetadata: {
nodeCount: GRAPH.nodes.length,
edgeCount: COUNT_EDGES,
pageRankIterations: PR_RESULT.iterations,
seed: SEED
},
generatedAt: NEW_DATE_ISO
}
- Resolve the witness signing key — same lookup order as Phase 4:
RUFLO_WITNESS_KEY_PATH env var — JSON file with { "privateKey": "<hex>" }.
verification/witness-key.json (the ADR-103 default path).
- If a key resolves:
signAttributionArtifact(body, privateKeyHex) from plugins/ruflo-neural-trader/src/signed-attribution.mjs.
- If NEITHER path resolves: log
"[WARN] ruflo-neural-trader: no witness signing key found — storing attribution artifact in UNSIGNED degraded mode. Regulator filings will reject UNSIGNED artifacts." and store the body unsigned. NEVER silently fall back.
-
Store the (possibly signed) artifact to the canonical trading-analysis namespace (ADR-126 Phase 1):
mcp__ruflo__memory_store({
key: "attribution-SIGNAL_ID-TIMESTAMP",
namespace: "trading-analysis",
value: JSON.stringify(signedArtifact)
})
The trading-analysis namespace is the canonical home for model-analysis output (regime classifications, technical-indicator summaries, model-training results — and now attribution rankings). Long-lived — no TTL — because the audit trail is the deliverable.
-
Return the markdown summary to the agent. Suggested format:
## Feature attribution for signal `SIGNAL_ID` (model: MODEL_ID)
| Rank | Feature | Score |
|------|---------|-------|
| 1 | NAME | 0.42 |
| 2 | NAME | 0.18 |
| … | … | … |
- PageRank iterations: N
- Graph: nodeCount nodes, edgeCount edges
- Seed: 42 (reproducible — same seed → same ordering)
- Path: mcp | local
- Signature: ed25519:abcd… (or UNSIGNED — degraded warning above)
Downstream consumers verify the artifact before any regulator-facing report or paper→live promotion: