This document is the replacement documentation for the Position Sizing Engine in the fin-glassbox project:
An Explainable Multimodal Neural Framework for Financial Risk Management
The module is implemented in:
code/riskEngine/position_sizing.py
Its purpose is to convert risk-engine outputs into an interpretable recommended capital fraction for each ticker-date. It is not a neural model. It is a rule-based, user-adjustable, risk-control engine.
The Position Sizing Engine is the bridge between raw risk estimates and actionable exposure control.
Technical Analyst
Volatility Risk
Drawdown Risk
VaR / CVaR
StemGNN Contagion
Liquidity Risk
MTGNN Regime
│
▼
Position Sizing Engine
├── weighted risk score
├── risk bucket
├── technical-confidence multiplier
├── module hard caps
└── final recommended capital fraction
│
▼
Quantitative Analyst → Fusion Engine → Final Trade Approver
It ensures that optimistic signals cannot freely increase exposure when risk modules disagree. In the project’s design, the Risk Engine remains central; Position Sizing is one of the main mechanisms enforcing that principle.
Position sizing answers:
How much capital should be allocated to this asset under current risk conditions?
It is separate from direction. A model may identify a potentially positive signal, but the system still needs to decide whether that signal deserves:
This module makes that decision using interpretable risk weights and hard caps.
The Position Sizing Engine is intentionally rule-based. This is not a weakness. It is a safety and explainability feature.
A fully learned position sizing model would be harder to defend because it could silently learn aggressive behaviour. The current implementation keeps capital allocation transparent:
weighted risk → risk bucket → technical multiplier → hard caps → final size
The module’s guiding rule is:
Hard risk caps always override optimistic signals.
The module reads outputs from the following components:
outputs/results/TechnicalAnalyst/predictions_chunk{chunk}_{split}.csv
outputs/results/Volatility/predictions_chunk{chunk}_{split}.csv
outputs/results/Drawdown/predictions_chunk{chunk}_{split}.csv
outputs/results/StemGNN/contagion_scores_chunk{chunk}_{split}.csv
outputs/results/MTGNNRegime/predictions_chunk{chunk}_{split}.csv
outputs/results/risk/chunks/var_cvar_chunk{chunk}_{split}.csv
outputs/results/risk/chunks/liquidity_chunk{chunk}_{split}.csv
The module merges these inputs by ticker-date. It is designed to tolerate some missing modules during development, but final production runs should have all upstream modules available.
The approved default weights are:
| Risk component | Weight |
|---|---|
| volatility | 0.20 |
| drawdown | 0.15 |
| VaR/CVaR | 0.15 |
| contagion | 0.25 |
| liquidity | 0.15 |
| regime | 0.10 |
Contagion has the largest weight because cross-asset spillover risk can create systemic danger that single-stock models miss.
The combined risk score is computed as:
combined_risk_score =
0.20 × volatility_risk
+ 0.15 × drawdown_risk
+ 0.15 × var_cvar_risk
+ 0.25 × contagion_risk
+ 0.15 × liquidity_risk
+ 0.10 × regime_risk
The module supports user-adjustable exposure modes:
| Mode | Maximum single-stock exposure |
|---|---|
| conservative | 5% |
| moderate/default | 10% |
| aggressive | 15% |
The approved default is:
moderate = 10% maximum per stock
These are portfolio-level capital fractions, not model confidence values.
Regime controls can reduce exposure even if the weighted risk bucket is moderate.
Configured caps:
| Regime / mode | Hard cap |
|---|---|
| volatile | 6% |
| rotation | 5% |
| crisis, short horizon | 5% |
| crisis, long horizon | 3% |
The long-horizon crisis cap is stricter because holding risky exposure through crisis regimes is more dangerous than short tactical exposure.
The module applies hard caps for severe risk signals:
severe_module_risk_threshold = 0.85
high_module_risk_threshold = 0.75
severe_module_cap = 2%
high_module_cap = 5%
These caps apply to module risk scores such as volatility, drawdown, VaR/CVaR, contagion, and regime.
Liquidity has its own special logic:
low_liquidity_threshold = 0.35
severe_liquidity_threshold = 0.20
low_liquidity_cap = 5%
severe_liquidity_cap = 2%
This prevents the system from recommending large positions in assets that may be difficult or costly to trade.
The engine uses Technical Analyst confidence as a multiplier if enabled:
technical_multiplier_min = 0.75
technical_multiplier_max = 1.10
This means strong technical confidence can slightly increase the fraction of the allowed risk budget, but it cannot override hard caps. Technical confidence helps size within allowed risk boundaries; it does not create permission to ignore risk.
The module follows this pipeline:
1. Load and merge upstream module outputs by ticker-date.
2. Compute module-specific normalised risk scores.
3. Compute weighted combined_risk_score.
4. Convert combined risk to a risk bucket and bucket fraction.
5. Compute technical confidence multiplier.
6. Compute pre-cap position fraction.
7. Compute hard caps from regime and each risk module.
8. Choose the most restrictive hard cap.
9. Final recommended capital fraction = min(pre-cap size, binding hard cap).
10. Save output CSV and XAI JSON.
The final size is therefore always explainable through:
Outputs are written to:
outputs/results/PositionSizing/position_sizing_chunk{chunk}_{split}.csv
outputs/results/PositionSizing/xai/position_sizing_chunk{chunk}_{split}_xai_summary.json
Important columns:
| Column | Meaning |
|---|---|
volatility_risk_score |
normalised volatility risk |
drawdown_risk_score |
normalised drawdown risk |
var_cvar_risk_score |
normalised tail-risk score |
contagion_risk_score |
normalised contagion score |
liquidity_risk_score |
normalised liquidity risk |
regime_risk_score |
normalised regime risk |
combined_risk_score |
weighted aggregate risk |
size_bucket |
interpreted risk bucket |
risk_bucket_fraction |
base allocation fraction within max exposure |
technical_multiplier |
technical-confidence scaling factor |
pre_cap_capital_fraction |
position before hard caps |
recommended_capital_fraction |
final recommended position fraction |
recommended_capital_pct |
final recommendation in percent |
binding_cap_source |
module/rule that most restricted position |
hard_cap_applied |
whether a hard cap reduced the size |
size_reduction_reasons |
human-readable explanation string |
xai_summary |
compact row-level explanation |
python code/riskEngine/position_sizing.py inspect --repo-root .
python code/riskEngine/position_sizing.py smoke --repo-root .
python code/riskEngine/position_sizing.py run --repo-root . --chunk 1 --split test
python code/riskEngine/position_sizing.py run-all --repo-root . --chunks 1 2 3 --splits train val test
python code/riskEngine/position_sizing.py validate --repo-root . --chunk 1 --split test
The Position Sizing Engine produces one of the clearest explanation traces in the project. It explains not only the final number, but the reason for reduction.
XAI includes:
risk weight summary
module availability
position summary
risk score summary
binding cap counts
size bucket counts
regime counts
top position examples
plain-English explanation
Row-level explanation appears in:
xai_summary
size_reduction_reasons
binding_cap_source
hard_cap_applied
A typical explanation can be read as:
Top weighted risks: contagion, volatility, var_cvar; Hard cap applied: regime_hard_cap; Crisis regime constrained exposure.
This is essential for thesis defence and for a future Streamlit interface.
The Quantitative Analyst consumes Position Sizing output. It uses:
recommended_capital_fraction
recommended_capital_pct
combined_risk_score
binding_cap_source
hard_cap_applied
size_bucket
risk scores
xai_summary
The Quantitative Analyst then learns attention-weighted pooling across risk scores. Position Sizing remains the interpretable capital-allocation foundation under that learned branch.
Fusion must not increase risk beyond Position Sizing. The final Fusion rule barrier should enforce:
final_position <= recommended_capital_fraction
This keeps the Risk Engine central even after learned fusion. The learned fusion model can reduce, accept, or veto exposure, but it should not enlarge exposure beyond the Position Sizing recommendation.
A healthy Position Sizing run should show:
The validation command should pass before running Quantitative Analyst or Fusion.
The module is rule-based and therefore depends on the quality of upstream risk estimates. It cannot fix bad volatility, drawdown, contagion, or regime outputs. It also uses fixed weights, which are defensible and interpretable but not automatically optimised.
This is acceptable because Fusion later learns branch weights, while Position Sizing remains the conservative risk-control layer.
The Position Sizing Engine is a completed Risk Engine module. It is the main interpretable capital allocation layer and provides downstream modules with:
It should be run after all Risk Engine modules and before Quantitative Analyst and Fusion.