Having predictive models is just the beginning. This chapter covers how to translate model predictions into actionable trading strategies with proper signal generation, position sizing, and portfolio construction.
Alpha represents excess returns above a benchmark after adjusting for risk. In quantitative trading, alpha is the holy grail - the edge that allows you to outperform the market consistently.
Converting model predictions into trading signals requires careful design:
Raw model predictions (e.g., predicted returns) used directly for ranking and weighting
# Rank-based portfolio construction predictions = model.predict(features) ranks = predictions.rank(pct=True) # 0 to 1 ranking # Long top 20%, short bottom 20% signals = pd.Series(0, index=ranks.index) signals[ranks >= 0.8] = 1 # Long signals[ranks <= 0.2] = -1 # Short # Position sizing proportional to signal strength position_sizes = signals * ranks.abs()
Discrete buy/sell decisions based on thresholds
# Threshold-based signals
signal = 0
if predicted_return > 0.01: # 1% threshold
signal = 1 # Buy
elif predicted_return < -0.01:
signal = -1 # Sell
else:
signal = 0 # Hold
Using predicted probabilities for directional trading
# Classification model output
probs = model.predict_proba(features)
p_up = probs[:, 2] # P(price up)
p_down = probs[:, 0] # P(price down)
# Trade only high-confidence signals
if p_up > 0.7:
signal = p_up - 0.5 # Scaled by confidence
elif p_down > 0.7:
signal = -(p_down - 0.5)
else:
signal = 0 # No trade
How much capital to allocate to each trade is critical for risk management and return optimization.
Risk a fixed percentage of capital per trade
def fixed_fractional_size(capital, risk_per_trade=0.02,
entry_price, stop_loss):
"""Risk 2% of capital per trade"""
risk_amount = capital * risk_per_trade
price_risk = abs(entry_price - stop_loss)
shares = risk_amount / price_risk
return shares
Maximize long-term growth rate (use fractional Kelly for safety)
def kelly_position_size(win_rate, avg_win, avg_loss):
"""Kelly criterion for position sizing"""
if avg_loss == 0:
return 0
win_loss_ratio = avg_win / abs(avg_loss)
kelly = (win_rate * win_loss_ratio - (1 - win_rate)) / win_loss_ratio
# Use half-Kelly for safety
return max(0, min(kelly * 0.5, 0.25)) # Cap at 25%
Inverse volatility weighting for risk parity
def volatility_size(predictions, volatilities, target_vol=0.15):
"""Size positions inversely to volatility"""
# Target portfolio volatility
inverse_vol = 1 / volatilities
weights = predictions * inverse_vol
weights = weights / weights.abs().sum()
# Scale to target volatility
portfolio_vol = (weights ** 2 * volatilities ** 2).sum() ** 0.5
scale = target_vol / portfolio_vol
return weights * scale
Classic Markowitz portfolio optimization balancing return and risk
import cvxpy as cp
def optimize_portfolio(expected_returns, cov_matrix,
risk_aversion=1.0):
"""Markowitz mean-variance optimization"""
n = len(expected_returns)
weights = cp.Variable(n)
# Objective: maximize return - risk_aversion * variance
ret = expected_returns @ weights
risk = cp.quad_form(weights, cov_matrix)
objective = cp.Maximize(ret - risk_aversion * risk)
# Constraints
constraints = [
cp.sum(weights) == 1, # Fully invested
weights >= -1, # Max 100% short
weights <= 1 # Max 100% long
]
problem = cp.Problem(objective, constraints)
problem.solve()
return weights.value
Equal risk contribution from each asset
Combines market equilibrium with investor views
# Example: ML-based momentum factor
class MLMomentumFactor:
def __init__(self):
self.model = LSTMPredictor(...)
def calculate_factor(self, data):
"""Generate momentum scores using ML"""
predictions = self.model.predict(data)
# Rank and normalize
factor_scores = predictions.rank(pct=True) - 0.5
# Long top quintile, short bottom quintile
long_threshold = factor_scores.quantile(0.8)
short_threshold = factor_scores.quantile(0.2)
positions = pd.Series(0, index=factor_scores.index)
positions[factor_scores >= long_threshold] = 1
positions[factor_scores <= short_threshold] = -1
return positions
class AITradingStrategy:
def __init__(self, model, risk_per_trade=0.02):
self.model = model
self.risk_per_trade = risk_per_trade
self.positions = {}
def generate_signals(self, market_data):
"""Generate trading signals from market data"""
# Feature engineering
features = self.engineer_features(market_data)
# ML predictions
predictions = self.model.predict(features)
# Convert to signals
signals = self.predictions_to_signals(predictions)
return signals
def size_positions(self, signals, portfolio_value, volatilities):
"""Calculate position sizes"""
positions = {}
for asset, signal in signals.items():
if signal == 0:
positions[asset] = 0
continue
# Volatility-adjusted sizing
vol = volatilities[asset]
base_size = portfolio_value * self.risk_per_trade
position_size = (base_size / vol) * signal
# Apply position limits
max_position = portfolio_value * 0.10 # 10% max
positions[asset] = np.clip(position_size,
-max_position, max_position)
return positions
def rebalance(self, current_positions, target_positions):
"""Generate rebalancing orders"""
orders = []
for asset, target in target_positions.items():
current = current_positions.get(asset, 0)
diff = target - current
if abs(diff) > threshold: # Avoid small trades
orders.append({
'asset': asset,
'quantity': diff,
'type': 'market'
})
return orders