APC Alpha Portfolio Construction
The final bridge from signals to actual positions
Learning Objectives
- •Map the complete signal-to-position pipeline
- •Implement position sizing that accounts for signal strength and regime
- •Apply portfolio-level constraints for risk management
- •Build a correlation-aware position construction system
Explain Like I'm 5
Having good signals is necessary but not sufficient. You also need to convert those signals into properly sized positions that respect risk limits, account for correlation, and don't blow up during tail events.
Think of It This Way
Signals are ingredients; portfolio construction is the recipe. Great ingredients cooked badly produce a mediocre meal. The recipe — how much of each, in what order, under what conditions — matters as much as the raw materials.
1The Signal-to-Position Pipeline
2Constraints That Matter
3Position Sizing Hierarchy
4Correlation-Aware Construction
5The Complete Pipeline in Practice
Key Formulas
Portfolio Expected Return
Weighted sum of individual expected returns across all N positions
Portfolio Variance
Total portfolio risk including cross-correlations between positions. This is why correlated positions amplify risk.
Hands-On Code
Signal-to-Position Pipeline
import numpy as np
def signal_to_position(signal_strength, atr, account_equity,
base_risk_pct=0.30, dd_zone='NORMAL',
pip_value=10.0, existing_cluster_exposure=0.0,
max_cluster_pct=1.5):
"""Convert model signal to actual position size in lots."""
# DD zone risk scaling
risk_scale = {
'NORMAL': 1.0,
'CAUTION': 0.833, # 0.25 / 0.30
'DANGER': 0.667, # 0.20 / 0.30
'CRITICAL': 0.50 # 0.15 / 0.30
}
# Adjusted risk percentage
risk_pct = base_risk_pct * risk_scale.get(dd_zone, 1.0)
# Signal strength modifier (sigmoid mapping)
# signal_strength: 0.5 (minimum) to 1.0 (maximum confidence)
strength_mult = 0.5 + (signal_strength - 0.5) * 2.0 # Maps [0.5, 1.0] -> [0.5, 1.5]
strength_mult = np.clip(strength_mult, 0.5, 1.5)
# Risk in dollars
risk_dollars = account_equity * (risk_pct / 100) * strength_mult
# Position size
if atr > 0 and pip_value > 0:
lots = risk_dollars / (atr * pip_value)
else:
lots = 0.01 # Minimum
# Cluster exposure check
if existing_cluster_exposure + (lots * pip_value * atr) > account_equity * max_cluster_pct / 100:
max_additional = (account_equity * max_cluster_pct / 100 - existing_cluster_exposure)
lots = max(0.01, max_additional / (atr * pip_value))
# Round to nearest 0.01 lots
lots = round(max(0.01, lots), 2)
return {
'lots': lots,
'risk_pct': round(risk_pct * strength_mult, 4),
'risk_dollars': round(risk_dollars, 2),
'dd_zone': dd_zone,
'strength_multiplier': round(strength_mult, 2)
}Implements a simplified signal-to-position conversion pipeline with volatility normalization, signal strength scaling, and correlation-based position adjustment.
Knowledge Check
Q1.In the constraint hierarchy, which takes absolute priority?
Q2.Why is Level 5 (portfolio-optimized) position sizing rarely better than Level 3 in practice?
Q3.Three long USD positions (short EUR/USD, GBP/USD, AUD/USD) primarily create risk because:
Assignment
Build a position sizing function that takes signal strength (0.5-1.0), ATR, account equity, and DD zone as inputs. Implement the risk scaling table from the V7 system. Test it with 10 hypothetical signals and verify that the resulting portfolio respects per-trade, per-cluster, and total risk constraints.