← Back to Learn
IV ExpertWeek 17 • Lesson 52Duration: 50 min

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

Converting a raw signal value into an actual position involves eight steps. Each one can lose money if done carelessly: Step 1: Signal generation. L1 model produces a directional forecast (probability of up/down move). This is your raw input. Step 2: Signal filtering. L2 gate decides ENTER or SKIP. Weak signals are filtered out to reduce noise trades. Step 3: Signal standardization. Convert to a z-score using rolling statistics. A signal of 2.5 standard deviations means something very different from a signal of 0.3. Step 4: Position sizing. Determine how much capital to allocate based on: - Signal strength (stronger signal = larger position) - Current volatility (higher vol = smaller position for same risk) - Regime (DD zone determines risk budget) - Conviction level (L2 confidence) Step 5: Constraint application. Apply hard limits: - Maximum position size per symbol - Maximum sector/cluster exposure - Maximum total portfolio risk - FTMO daily and total DD limits Step 6: Correlation adjustment. Reduce positions in correlated pairs to avoid concentrated risk. Step 7: Execution. Convert target position to actual orders (market, limit, etc.) Step 8: Post-trade validation. Verify the executed position matches the intended position within tolerance.

2Constraints That Matter

Six constraints that every production system needs: 1. Per-position risk limit. Maximum risk on any single trade. Typical: 0.15% - 0.30% of account (varies by DD zone). 2. Cluster concentration limit. Maximum exposure to any single cluster (FOREX, METALS, etc.). Prevents sector blowups. 3. Correlation exposure limit. If EUR/USD and GBP/USD are both long, their combined risk shouldn't exceed what you'd allocate to uncorrelated positions. 4. Daily stop-loss. Maximum loss allowed in a single day. FTMO: 5% (we use 4.5% with 0.5% buffer). 5. Total drawdown limit. Maximum cumulative loss from peak equity. FTMO: 10% (we use 9.5% with 0.5% buffer). 6. Regime-based exposure. In high-volatility regimes, reduce total exposure even if individual signal strength is high. The hierarchy is critical: FTMO limits override everything else, followed by cluster limits, then position limits. No signal, no matter how strong, gets to override risk constraints.

3Position Sizing Hierarchy

Five levels of position sizing sophistication, from simple to advanced: Level 1: Fixed fractional. Risk a fixed percentage on every trade regardless of signal quality. Simple, robust, but ignores valuable information. Level 2: Volatility-adjusted. Scale position size inversely with ATR. Higher volatility = smaller position. This keeps risk per trade roughly constant.
Lots=Risk</mi></msub><mrow><mtext>ATR</mtext><mo>×</mo><mtext>Pip Value</mtext></mrow></mfrac></mrow><annotation encoding="application/x-tex">\text{Lots} = \frac{\text{Risk}_\}{\text{ATR} \times \text{Pip Value}}
Level 3: Signal-weighted. Larger signals get larger positions. Combine with volatility adjustment:
Lots=Risk</mi></msub><mo>×</mo><mi>f</mi><mo stretchy="false">(</mo><mtext>signal strength</mtext><mo stretchy="false">)</mo></mrow><mrow><mtext>ATR</mtext><mo>×</mo><mtext>Pip Value</mtext></mrow></mfrac></mrow><annotation encoding="application/x-tex">\text{Lots} = \frac{\text{Risk}_\ \times f(\text{signal strength})}{\text{ATR} \times \text{Pip Value}}
Where ff is a sigmoid or linear mapping from signal strength to a multiplier in [0.5, 1.5]. Level 4: Kelly-adjusted. Use Kelly criterion to determine optimal fraction, then fractional Kelly (typically 25-50% Kelly) for real trading. Level 5: Portfolio-optimized. Use the full covariance matrix to determine positions that maximize Sharpe subject to constraints. Most theoretically optimal but requires very accurate covariance estimates. Our production system uses Level 3 (signal-weighted, volatility-adjusted) with Level 2 as fallback. Level 5 sounds great but the covariance estimation error typically exceeds the optimization benefit.

4Correlation-Aware Construction

The naive approach to portfolio construction treats each position independently. This is a mistake. If you're long EUR/USD, GBP/USD, and AUD/USD simultaneously, you're effectively making one big USD-short bet, not three independent trades. Correlation-aware construction adjusts for this: Step 1: Compute rolling pairwise correlations for all active positions (60-day rolling window is standard). Step 2: Estimate portfolio variance:
\sigma_p^2 = \sum_i w_i^2 \sigma_i^2 + 2 \sum_{i&lt;j} w_i w_j \sigma_i \sigma_j \rho_{ij}
Step 3: Scale down positions when portfolio variance exceeds target. If the target portfolio risk is 1% daily, but adding an EUR/USD long to your existing GBP/USD and AUD/USD longs would push it to 1.5%, you either reduce the new position or reduce existing ones. Step 4: Prefer uncorrelated additions. When two signals have equal strength, prioritize the one that adds diversification rather than the one that doubles down on existing exposure. In practice, we group symbols by cluster and apply intra-cluster concentration limits. This captures the dominant correlation structure (most of the high correlation is within-cluster) without requiring precise covariance estimation.

5The Complete Pipeline in Practice

Here is the actual decision flow every 15 minutes in a production system: 1. Data update: Receive latest bar for all symbols 2. Feature computation: Calculate 38 features per symbol 3. L1 inference: XGBoost models produce directional probability per symbol per cluster 4. Signal ranking: Sort all signals by strength, filter by cluster threshold 5. L2 gating: Run L2 model on top candidates — ENTER or SKIP 6. Position sizing: For ENTER signals, compute lot size based on ATR, risk budget, and signal confidence 7. Portfolio check: Verify new position + existing portfolio stays within risk limits 8. Correlation check: Ensure intra-cluster exposure stays within bounds 9. FTMO check: Final guardrail — confirm daily R and total R limits are respected 10. Execute: Submit orders via MT5 The entire pipeline runs in under 2 seconds per bar. Speed matters not for HFT reasons but because you want decisions made and orders submitted before the next bar opens. Every step has a default fallback: - L2 threshold uncertain? Use rule-based minimum confidence - Correlation data stale? Use cluster-based approximation - ATR missing? Use 14-period estimate or reduce size to minimum

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

python
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.