code/fusion/ — Hybrid Fusion EngineThe code/fusion/ directory contains the final synthesis layer for An Explainable Multimodal Neural Framework for Financial Risk Management. Its job is to combine the two high-level analysis branches produced by the system:
The Fusion Engine converts these branches into a final risk-aware decision object:
Buy / Hold / Sell
├── final fused signal
├── final fused risk score
├── final confidence score
├── final position size
├── learned quantitative/qualitative branch weights
├── rule-barrier override reasons
└── system-level XAI explanation
This module is intentionally hybrid. It is not a pure neural black box. It uses a learned fusion layer to estimate branch weights and final signal components, then applies a user-controlled rule barrier as the final line of defence.
code/fusion/
├── fusion_layer.py
└── final_fusion.py
fusion_layer.pyCore implementation file. It contains:
Important classes and functions include:
| Object | Role |
|---|---|
UserRuleBarrierConfig |
Stores user-controlled safety rules and exposure caps. |
FusionConfig |
Stores paths, training settings, HPO settings, schema behaviour, and rule settings. |
HybridFusionModel |
Neural fusion model that learns branch weights, signal, risk, confidence, position multiplier, and Buy/Hold/Sell logits. |
FusionScaler |
Train-fitted feature normaliser saved with the model. |
merge_branches() |
Joins quantitative and qualitative branch outputs by ticker and date. |
prepare_fusion_dataframe() |
Cleans and engineers fusion features. |
construct_fusion_targets() |
Builds self-supervised risk-aware training targets. |
apply_user_rule_barrier() |
Applies hard safety constraints after learned prediction. |
predict_fusion() |
Produces final fused decisions and XAI outputs. |
validate_predictions() |
Verifies output range, branch weights, and required schema. |
final_fusion.pyThin CLI wrapper around fusion_layer.py. It exposes the runnable interface:
inspect
smoke
hpo
train-best
predict
predict-all
validate
run
run-all
The file is intentionally small. All substantive modelling logic remains in fusion_layer.py, while final_fusion.py provides a clean command-line entry point for experiments, validation, and production-style execution.
The Fusion Engine sits after the two analysis branches:
Risk Engine + Technical Analyst
│
▼
Quantitative Analyst
│
├──────────────┐
│ ▼
│ Fusion Engine ─────► Final Decision / Trade Approver
│ ▲
└──────────────┤
│
Sentiment + News + FinBERT
│
▼
Qualitative Analyst
The Fusion Engine is the final synthesis stage before final trade approval. It does not replace the risk engine. Instead, it respects the risk engine by enforcing the principle:
Fusion may reduce exposure, but it must not exceed the Position Sizing recommendation.
The learned layer estimates how to combine quantitative and qualitative evidence. It learns:
learned_quantitative_weight,learned_qualitative_weight,learned_fusion_signal,learned_fusion_risk_score,learned_fusion_confidence,learned_position_multiplier,learned_sell_prob,learned_hold_prob,learned_buy_prob,learned_recommendation.The model is intentionally compact. It uses an MLP backbone with tanh activations and separate heads for branch weights, action logits, signal, risk, confidence, and position scaling. This keeps the layer trainable, inspectable, and defensible.
The user rule barrier is not learned. It is a deterministic final safety layer. It applies constraints such as:
tradable == False,The final position is computed using the conservative rule:
final_position = min(
position_sizing_recommendation,
learned_position_suggestion,
user_rule_cap
)
This design preserves transparency and ensures the learned model cannot override explicit user/risk constraints.
Expected path:
outputs/results/QuantitativeAnalyst/quantitative_analysis_chunk{chunk}_{split}.csv
Required attention-schema columns include:
ticker
date
quantitative_recommendation
risk_adjusted_quantitative_signal
technical_direction_score
quantitative_risk_score
quantitative_confidence
quantitative_action_strength
recommended_capital_fraction
recommended_capital_pct
position_fraction_of_max
max_single_stock_exposure
attention_pooled_risk_score
top_attention_risk_driver
risk_attention_volatility
risk_attention_drawdown
risk_attention_var_cvar
risk_attention_contagion
risk_attention_liquidity
risk_attention_regime
The Fusion Engine deliberately fails if it detects the older Quantitative Analyst schema containing top_risk_driver without top_attention_risk_driver. This prevents stale rule-only quantitative outputs from silently corrupting fusion training.
Expected path:
outputs/results/QualitativeAnalyst/qualitative_daily_chunk{chunk}_{split}.csv
Important columns include:
ticker
date
event_count
sentiment_event_count
news_event_count
qualitative_score
qualitative_risk_score
qualitative_confidence
qualitative_recommendation
mean_sentiment_score
mean_news_impact_score
dominant_qualitative_driver
xai_summary
Qualitative data is sparse. Most ticker-date rows do not have text events. When a qualitative row is missing for a quantitative ticker-date, the Fusion Engine uses a neutral qualitative state:
qualitative_score = 0.0
qualitative_risk_score = 0.5
qualitative_confidence = 0.0
event_count = 0
dominant_qualitative_driver = no_text_event
This prevents missing text from being interpreted as bullish or bearish.
Predictions are written to:
outputs/results/FusionEngine/fused_decisions_chunk{chunk}_{split}.csv
XAI summaries are written to:
outputs/results/FusionEngine/xai/fused_decisions_chunk{chunk}_{split}_xai_summary.json
Model artefacts are written to:
outputs/models/FusionEngine/chunk{chunk}/
├── best_model.pt
├── final_model.pt
├── scaler.npz
├── training_history.csv
├── training_summary.json
├── model_freezed/model.pt
└── model_unfreezed/model.pt
HPO artefacts are written to:
outputs/codeResults/FusionEngine/
├── hpo_chunk{chunk}.db
└── best_params_chunk{chunk}.json
The fused CSV contains both final outputs and audit traces. Important columns include:
| Column | Meaning |
|---|---|
final_recommendation |
Final post-rule Buy/Hold/Sell decision. |
final_fusion_signal |
Final fused directional signal after learned model. |
final_fusion_risk_score |
Final fused risk score. |
final_fusion_confidence |
Final confidence score. |
final_position_fraction |
Final capital allocation as a fraction. |
final_position_pct |
Final capital allocation as a percentage. |
learned_recommendation |
Learned model recommendation before rule barrier. |
learned_quantitative_weight |
Learned trust weight assigned to quantitative branch. |
learned_qualitative_weight |
Learned trust weight assigned to qualitative branch. |
branch_weight_dominance |
Which branch dominated the fusion decision. |
rule_changed_action |
Whether the rule barrier changed the learned action. |
rule_barrier_reasons |
Human-readable reason for safety caps/vetoes. |
fusion_xai_summary |
Compact final explanation. |
Fusion-level XAI has three layers:
The model reports how much it trusted each branch:
learned_quantitative_weight
learned_qualitative_weight
branch_weight_dominance
This answers: Was the decision primarily market/risk-driven or text/event-driven?
The quantitative branch carries risk attention weights:
risk_attention_volatility
risk_attention_drawdown
risk_attention_var_cvar
risk_attention_contagion
risk_attention_liquidity
risk_attention_regime
top_attention_risk_driver
This answers: Which risk type most influenced the quantitative branch?
The rule layer records why the final output was capped or modified:
rule_changed_action
user_rule_cap_fraction
rule_barrier_reasons
This answers: Did the safety layer intervene, and why?
cd ~/fin-glassbox && python -m py_compile code/fusion/fusion_layer.py code/fusion/final_fusion.py
cd ~/fin-glassbox && python code/fusion/final_fusion.py inspect --repo-root .
cd ~/fin-glassbox && python code/fusion/final_fusion.py smoke --repo-root . --device cuda
cd ~/fin-glassbox && python code/fusion/final_fusion.py hpo --repo-root . --chunk 1 --trials 30 --device cuda --fresh
cd ~/fin-glassbox && python code/fusion/final_fusion.py train-best --repo-root . --chunk 1 --device cuda --fresh
cd ~/fin-glassbox && python code/fusion/final_fusion.py predict --repo-root . --chunk 1 --split test --device cuda
cd ~/fin-glassbox && python code/fusion/final_fusion.py predict-all --repo-root . --chunks 1 --splits val test --device cuda
cd ~/fin-glassbox && python code/fusion/final_fusion.py validate --repo-root . --chunk 1 --split test
cd ~/fin-glassbox && python code/fusion/final_fusion.py run --repo-root . --chunk 1 --trials 30 --device cuda --fresh --predict-splits val test
Only run this after all chunks have the trained Quantitative attention schema:
cd ~/fin-glassbox && python code/fusion/final_fusion.py run-all --repo-root . --chunks 1 2 3 --trials 30 --device cuda --fresh --predict-splits val test
Before training Fusion on a chunk, verify:
ticker and date columns are present in both branches.Recommended schema audit:
cd ~/fin-glassbox && python - <<'PY'
import pandas as pd
from pathlib import Path
for p in sorted(Path("outputs/results/QuantitativeAnalyst").glob("quantitative_analysis_chunk*_*.csv")):
df = pd.read_csv(p, nrows=5)
has_attention = "top_attention_risk_driver" in df.columns
has_old = "top_risk_driver" in df.columns
print(f"{p}: attention_schema={has_attention}, old_schema={has_old}, rows~", sum(1 for _ in open(p))-1)
PY
Default user-facing exposure profile:
conservative: 5% max per stock
moderate: 10% max per stock
aggressive: 15% max per stock
Crisis-specific caps:
short-horizon crisis cap: 5%
long-horizon crisis cap: 3%
Important default constraints:
not tradable -> HOLD, 0% position
low liquidity -> HOLD, 0% position
high contagion -> BUY veto / severe cap
high drawdown -> cap exposure
high quantitative risk -> BUY veto / cap exposure
position sizing output -> upper bound on final exposure
A pure learned fusion model would be difficult to explain and could override risk constraints. A pure rule-based fusion model would be transparent but less adaptive. This module combines both:
This is aligned with the project philosophy:
specialisation + multimodality + explainability + modular integration + risk-aware decision-making
Cause: chunk output came from the old Quantitative Analyst version.
Fix: rerun the trained attention-based Quantitative Analyst for that chunk/split.
Cause: only val/test predictions were generated.
Fix: generate train predictions for both Quantitative and Qualitative branches before Fusion HPO/training.
Possible causes:
Check:
learned_recommendation
final_recommendation
rule_changed_action
rule_barrier_reasons
learned_fusion_signal
final_position_pct
This may be correct when no text event exists for most ticker-date rows. Check:
text_available
event_count
qualitative_confidence
learned_qualitative_weight
code/fusion/ contains the final hybrid synthesis layer of the system. It is deliberately designed as a compact but explainable module:
learned branch weighting
+ learned signal/risk/confidence estimation
+ user-controlled rule barrier
+ final position cap
+ module-level and system-level XAI
It converts the project from a collection of specialised modules into a coherent final decision system while preserving the central role of risk management and explainability.