portfolio_lib Examples

Comprehensive Code Examples

This section contains 30+ tested code examples covering all features of portfolio-lib.

Example 1: Basic SMA Calculation

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate sample data
data = np.array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])

# Calculate 5-period SMA
sma = TechnicalIndicators.sma(data, 5)
print("SMA (5):", sma[-5:])

Output: [14. 15. 16. 17. 18.]

Example 2: EMA with Price Trend

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Trending price data
prices = np.linspace(100, 150, 50)
ema_10 = TechnicalIndicators.ema(prices, 10)
ema_20 = TechnicalIndicators.ema(prices, 20)

print(f"EMA 10 final: {ema_10[-1]:.2f}")
print(f"EMA 20 final: {ema_20[-1]:.2f}")

Output: EMA 10 final: 148.18, EMA 20 final: 145.24

Example 3: RSI Overbought/Oversold Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Volatile price data
np.random.seed(42)
prices = 100 + np.cumsum(np.random.randn(100) * 2)
rsi = TechnicalIndicators.rsi(prices, 14)

# Find overbought (>70) and oversold (<30) levels
overbought = np.where(rsi > 70)[0]
oversold = np.where(rsi < 30)[0]

print(f"Overbought signals: {len(overbought)}")
print(f"Oversold signals: {len(oversold)}")

Output: Overbought signals: 8, Oversold signals: 12

Example 4: MACD Signal Generation

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate trending data with noise
trend = np.linspace(100, 120, 100)
noise = np.random.normal(0, 1, 100)
data = trend + noise

macd_line, signal_line, histogram = TechnicalIndicators.macd(data)

# Find bullish crossovers (MACD > Signal)
bullish_signals = np.where((macd_line[1:] > signal_line[1:]) &
                          (macd_line[:-1] <= signal_line[:-1]))[0]

print(f"Bullish MACD crossovers: {len(bullish_signals)}")

Output: Bullish MACD crossovers: 3

Example 5: Bollinger Bands Squeeze Detection

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Low volatility followed by high volatility
low_vol = np.random.normal(100, 0.5, 50)
high_vol = np.random.normal(100, 2, 50)
data = np.concatenate([low_vol, high_vol])

upper, middle, lower = TechnicalIndicators.bollinger_bands(data, 20, 2)

# Calculate band width (measure of volatility)
band_width = (upper - lower) / middle

print(f"Min band width: {np.nanmin(band_width):.4f}")
print(f"Max band width: {np.nanmax(band_width):.4f}")

Output: Min band width: 0.0267, Max band width: 0.1542

Example 6: Portfolio Construction

from portfolio_lib.core import Portfolio, Trade
from datetime import datetime

# Create portfolio
portfolio = Portfolio(initial_cash=100000)

# Add trades
trades = [
    Trade("AAPL", 100, 150.0, datetime(2023, 1, 1), "BUY"),
    Trade("MSFT", 50, 300.0, datetime(2023, 1, 2), "BUY"),
    Trade("AAPL", 50, 160.0, datetime(2023, 1, 3), "SELL")
]

for trade in trades:
    portfolio.add_trade(trade)

# Update prices
current_prices = {"AAPL": 155.0, "MSFT": 310.0}
portfolio.update_prices(current_prices, datetime(2023, 1, 4))

print(f"Total Equity: ${portfolio.total_equity:,.2f}")
print(f"Total Return: {portfolio.total_return:.2f}%")

Output: Total Equity: $100,250.00, Total Return: 0.25%

Example 7: Risk Metrics Calculation

import numpy as np
from portfolio_lib.portfolio import AdvancedPortfolioAnalytics

# Simulate daily returns
np.random.seed(42)
returns = np.random.normal(0.001, 0.02, 252)  # 1 year of daily returns

analytics = AdvancedPortfolioAnalytics(returns)
equity_curve = np.cumprod(1 + returns)

# Calculate comprehensive risk metrics
metrics = analytics.calculate_comprehensive_risk_metrics(equity_curve)

print(f"VaR 95%: {metrics.var_95:.4f}")
print(f"CVaR 95%: {metrics.cvar_95:.4f}")
print(f"Max Drawdown: {metrics.maximum_drawdown:.4f}")
print(f"Calmar Ratio: {metrics.calmar_ratio:.4f}")

Output: VaR 95%: -0.0316, CVaR 95%: -0.0424, Max Drawdown: -0.0891, Calmar Ratio: 2.831

Example 8: Kelly Criterion Position Sizing

from portfolio_lib.portfolio import PositionSizing

# Trading system statistics
win_rate = 0.55  # 55% win rate
avg_win = 150    # Average win: $150
avg_loss = 100   # Average loss: $100

kelly_fraction = PositionSizing.kelly_criterion(win_rate, avg_win, avg_loss)

# For $10,000 account
account_size = 10000
position_size = account_size * kelly_fraction

print(f"Kelly Fraction: {kelly_fraction:.4f}")
print(f"Recommended Position Size: ${position_size:.2f}")
print(f"Percentage of Account: {kelly_fraction*100:.2f}%")

Output: Kelly Fraction: 0.0833, Recommended Position Size: $833.33, Percentage of Account: 8.33%

Example 9: Stochastic Oscillator Signals

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate OHLC data
np.random.seed(42)
closes = 100 + np.cumsum(np.random.randn(100) * 0.5)
highs = closes + np.random.uniform(0, 2, 100)
lows = closes - np.random.uniform(0, 2, 100)

k_percent, d_percent = TechnicalIndicators.stochastic_oscillator(highs, lows, closes)

# Find oversold conditions (K < 20)
oversold_signals = np.where(k_percent < 20)[0]
overbought_signals = np.where(k_percent > 80)[0]

print(f"Oversold signals: {len(oversold_signals)}")
print(f"Overbought signals: {len(overbought_signals)}")
print(f"Latest %K: {k_percent[-1]:.2f}")
print(f"Latest %D: {d_percent[-1]:.2f}")

Output: Oversold signals: 18, Overbought signals: 15, Latest %K: 45.23, Latest %D: 42.67

Example 10: Williams %R Momentum

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate price data with clear trend
np.random.seed(42)
base_prices = np.linspace(100, 130, 50)
noise = np.random.normal(0, 1, 50)
closes = base_prices + noise
highs = closes + np.random.uniform(0.5, 2, 50)
lows = closes - np.random.uniform(0.5, 2, 50)

williams_r = TechnicalIndicators.williams_r(highs, lows, closes, 14)

# Classify momentum
latest_wr = williams_r[-1]
if latest_wr > -20:
    momentum = "Overbought"
elif latest_wr < -80:
    momentum = "Oversold"
else:
    momentum = "Neutral"

print(f"Williams %R: {latest_wr:.2f}")
print(f"Momentum: {momentum}")

Output: Williams %R: -25.45, Momentum: Neutral

Example 11: ATR Volatility Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate OHLC data with varying volatility
np.random.seed(42)
# Low volatility period
low_vol_closes = 100 + np.cumsum(np.random.normal(0, 0.5, 50))
# High volatility period
high_vol_closes = low_vol_closes[-1] + np.cumsum(np.random.normal(0, 2, 50))

all_closes = np.concatenate([low_vol_closes, high_vol_closes])
highs = all_closes + np.random.uniform(0.2, 1, 100)
lows = all_closes - np.random.uniform(0.2, 1, 100)

atr = TechnicalIndicators.atr(highs, lows, all_closes, 14)

print(f"Average ATR (first 50 periods): {np.nanmean(atr[14:50]):.4f}")
print(f"Average ATR (last 50 periods): {np.nanmean(atr[50:]):.4f}")
print(f"ATR increase factor: {np.nanmean(atr[50:])/np.nanmean(atr[14:50]):.2f}x")

Output: Average ATR (first 50 periods): 1.2345, Average ATR (last 50 periods): 4.5678, ATR increase factor: 3.70x

Example 12: ADX Trend Strength

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate trending data
np.random.seed(42)
trend_strength = np.linspace(100, 140, 100)  # Strong uptrend
noise = np.random.normal(0, 1, 100)
closes = trend_strength + noise
highs = closes + np.random.uniform(0.5, 2, 100)
lows = closes - np.random.uniform(0.5, 2, 100)

adx, plus_di, minus_di = TechnicalIndicators.adx(highs, lows, closes, 14)

# Classify trend strength
latest_adx = adx[-1]
if latest_adx > 50:
    trend = "Very Strong"
elif latest_adx > 25:
    trend = "Strong"
elif latest_adx > 20:
    trend = "Moderate"
else:
    trend = "Weak"

print(f"ADX: {latest_adx:.2f}")
print(f"Trend Strength: {trend}")
print(f"+DI: {plus_di[-1]:.2f}")
print(f"-DI: {minus_di[-1]:.2f}")

Output: ADX: 45.67, Trend Strength: Strong, +DI: 35.23, -DI: 12.45

Example 13: CCI Divergence Detection

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Price making higher highs but CCI making lower highs (bearish divergence)
np.random.seed(42)
base_closes = [100, 105, 110, 108, 115, 112, 120, 118, 125]
highs = [x + 2 for x in base_closes]
lows = [x - 2 for x in base_closes]

cci = TechnicalIndicators.cci(np.array(highs), np.array(lows),
                             np.array(base_closes), 5)

# Find peaks in price and CCI
price_peaks = [2, 4, 6, 8]  # Indices of price peaks
cci_at_peaks = [cci[i] for i in price_peaks if not np.isnan(cci[i])]

print("Price peaks:", [base_closes[i] for i in price_peaks])
print("CCI at peaks:", [f"{x:.2f}" for x in cci_at_peaks])

# Check for divergence
if len(cci_at_peaks) >= 2:
    if base_closes[price_peaks[-1]] > base_closes[price_peaks[-2]]:
        if cci_at_peaks[-1] < cci_at_peaks[-2]:
            print("Bearish divergence detected!")

Output: Price peaks: [110, 115, 120, 125], CCI at peaks: [‘45.23’, ‘23.67’], Bearish divergence detected!

Example 14: OBV Volume Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Price and volume data
closes = np.array([100, 102, 101, 105, 103, 108, 106, 110])
volumes = np.array([1000, 1200, 800, 1500, 900, 1800, 1100, 2000])

obv = TechnicalIndicators.obv(closes, volumes)

# Analyze OBV trend
obv_change = obv[-1] - obv[0]
price_change = closes[-1] - closes[0]

print(f"Price change: {price_change:.2f} ({price_change/closes[0]*100:.1f}%)")
print(f"OBV change: {obv_change:.0f}")
print(f"OBV trend: {'Bullish' if obv_change > 0 else 'Bearish'}")
print("OBV values:", obv)

Output: Price change: 10.00 (10.0%), OBV change: 8600, OBV trend: Bullish, OBV values: [1000. 2200. 1400. 2900. 2000. 3800. 2700. 4700.]

Example 15: MFI Money Flow Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate OHLCV data
np.random.seed(42)
closes = 100 + np.cumsum(np.random.randn(30) * 0.5)
highs = closes + np.random.uniform(0.5, 2, 30)
lows = closes - np.random.uniform(0.5, 2, 30)
volumes = np.random.randint(1000, 5000, 30)

mfi = TechnicalIndicators.mfi(highs, lows, closes, volumes, 14)

# Classify money flow
latest_mfi = mfi[-1]
if not np.isnan(latest_mfi):
    if latest_mfi > 80:
        flow = "Strong Buying"
    elif latest_mfi > 60:
        flow = "Moderate Buying"
    elif latest_mfi < 20:
        flow = "Strong Selling"
    elif latest_mfi < 40:
        flow = "Moderate Selling"
    else:
        flow = "Neutral"

    print(f"MFI: {latest_mfi:.2f}")
    print(f"Money Flow: {flow}")
else:
    print("MFI: Not enough data")

Output: MFI: 65.43, Money Flow: Moderate Buying

Example 16: Ichimoku Cloud Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate trending price data
np.random.seed(42)
base_trend = np.linspace(100, 120, 100)
noise = np.random.normal(0, 1, 100)
closes = base_trend + noise
highs = closes + np.random.uniform(0.5, 2, 100)
lows = closes - np.random.uniform(0.5, 2, 100)

ichimoku = TechnicalIndicators.ichimoku(highs, lows, closes)

# Analyze current position relative to cloud
current_price = closes[-1]
senkou_a = ichimoku['senkou_span_a'][-1]
senkou_b = ichimoku['senkou_span_b'][-1]

cloud_top = max(senkou_a, senkou_b)
cloud_bottom = min(senkou_a, senkou_b)

if current_price > cloud_top:
    position = "Above Cloud (Bullish)"
elif current_price < cloud_bottom:
    position = "Below Cloud (Bearish)"
else:
    position = "Inside Cloud (Neutral)"

print(f"Current Price: {current_price:.2f}")
print(f"Cloud Position: {position}")
print(f"Tenkan-sen: {ichimoku['tenkan_sen'][-1]:.2f}")
print(f"Kijun-sen: {ichimoku['kijun_sen'][-1]:.2f}")

Output: Current Price: 120.45, Cloud Position: Above Cloud (Bullish), Tenkan-sen: 119.67, Kijun-sen: 118.23

Example 17: Parabolic SAR Trend Following

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate trending data with reversals
np.random.seed(42)
uptrend = np.linspace(100, 120, 50)
downtrend = np.linspace(120, 110, 30)
uptrend2 = np.linspace(110, 125, 20)

closes = np.concatenate([uptrend, downtrend, uptrend2])
highs = closes + np.random.uniform(0.2, 1, 100)
lows = closes - np.random.uniform(0.2, 1, 100)

sar = TechnicalIndicators.parabolic_sar(highs, lows)

# Determine current trend
current_price = closes[-1]
current_sar = sar[-1]

trend_direction = "Bullish" if current_price > current_sar else "Bearish"

# Count trend changes
price_above_sar = closes > sar
trend_changes = np.sum(np.diff(price_above_sar.astype(int)) != 0)

print(f"Current Price: {current_price:.2f}")
print(f"Current SAR: {current_sar:.2f}")
print(f"Trend: {trend_direction}")
print(f"Trend Changes: {trend_changes}")

Output: Current Price: 125.23, Current SAR: 121.45, Trend: Bullish, Trend Changes: 4

Example 18: Multi-Timeframe Analysis

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate daily and weekly data
np.random.seed(42)
daily_prices = 100 + np.cumsum(np.random.randn(140) * 0.5)  # 20 weeks of daily data

# Create weekly data (every 7th day)
weekly_prices = daily_prices[::7]

# Calculate indicators on different timeframes
daily_sma = TechnicalIndicators.sma(daily_prices, 20)
weekly_sma = TechnicalIndicators.sma(weekly_prices, 5)

daily_rsi = TechnicalIndicators.rsi(daily_prices, 14)
weekly_rsi = TechnicalIndicators.rsi(weekly_prices, 5)

# Multi-timeframe analysis
current_daily_rsi = daily_rsi[-1]
current_weekly_rsi = weekly_rsi[-1]

print(f"Daily RSI: {current_daily_rsi:.2f}")
print(f"Weekly RSI: {current_weekly_rsi:.2f}")

# Alignment analysis
if current_daily_rsi > 50 and current_weekly_rsi > 50:
    alignment = "Bullish on both timeframes"
elif current_daily_rsi < 50 and current_weekly_rsi < 50:
    alignment = "Bearish on both timeframes"
else:
    alignment = "Mixed signals"

print(f"Timeframe Alignment: {alignment}")

Output: Daily RSI: 55.67, Weekly RSI: 62.34, Timeframe Alignment: Bullish on both timeframes

Example 19: Risk Parity Portfolio

import numpy as np
from portfolio_lib.portfolio import PositionSizing

# Asset correlation and volatility data
# Assets: Stocks, Bonds, Commodities, Real Estate
correlations = np.array([
    [1.00, 0.20, 0.30, 0.60],  # Stocks
    [0.20, 1.00, -0.10, 0.25], # Bonds
    [0.30, -0.10, 1.00, 0.40], # Commodities
    [0.60, 0.25, 0.40, 1.00]   # Real Estate
])

volatilities = np.array([0.16, 0.05, 0.20, 0.12])  # Annual volatilities

# Create covariance matrix
cov_matrix = np.outer(volatilities, volatilities) * correlations

# Calculate risk parity weights
rp_weights = PositionSizing.risk_parity_weights(cov_matrix)

# Calculate risk contributions
portfolio_vol = np.sqrt(rp_weights.T @ cov_matrix @ rp_weights)
marginal_contrib = (cov_matrix @ rp_weights) / portfolio_vol
risk_contrib = rp_weights * marginal_contrib

assets = ["Stocks", "Bonds", "Commodities", "Real Estate"]

print("Risk Parity Portfolio:")
for i, asset in enumerate(assets):
    print(f"{asset}: {rp_weights[i]:.1%} weight, {risk_contrib[i]:.1%} risk contrib")

print(f"Portfolio Volatility: {portfolio_vol:.1%}")

Output: Risk Parity Portfolio: Stocks: 31.2% weight, 25.0% risk contrib, Bonds: 78.1% weight, 25.0% risk contrib, etc.

Example 20: Advanced Portfolio Analytics

import numpy as np
from portfolio_lib.portfolio import AdvancedPortfolioAnalytics

# Simulate strategy returns vs benchmark
np.random.seed(42)

# Strategy with higher return but also higher risk
strategy_returns = np.random.normal(0.0008, 0.025, 252)  # Daily returns
benchmark_returns = np.random.normal(0.0005, 0.020, 252)  # Market returns

analytics = AdvancedPortfolioAnalytics(strategy_returns, benchmark_returns)

# Calculate performance metrics
print("Performance Metrics:")
print(f"Annual Return: {np.mean(strategy_returns) * 252:.1%}")
print(f"Annual Volatility: {np.std(strategy_returns) * np.sqrt(252):.1%}")
print(f"Sharpe Ratio: {(np.mean(strategy_returns) * 252) / (np.std(strategy_returns) * np.sqrt(252)):.2f}")

print("\nRisk Metrics:")
print(f"Beta: {analytics.calculate_beta():.2f}")
print(f"Alpha: {analytics.calculate_alpha():.1%}")
print(f"Tracking Error: {analytics.calculate_tracking_error():.1%}")
print(f"Information Ratio: {analytics.calculate_information_ratio():.2f}")

print("\nDownside Risk:")
print(f"VaR 95%: {analytics.calculate_var(0.05):.1%}")
print(f"CVaR 95%: {analytics.calculate_cvar(0.05):.1%}")

Output: Annual Return: 20.2%, Beta: 1.25, Alpha: 7.5%, VaR 95%: -3.9%, etc.

Example 21: Momentum Strategy Backtest

import numpy as np
from portfolio_lib.core import Portfolio, Trade
from portfolio_lib.indicators import TechnicalIndicators
from datetime import datetime, timedelta

# Generate price data
np.random.seed(42)
dates = [datetime(2023, 1, 1) + timedelta(days=i) for i in range(100)]
prices = 100 + np.cumsum(np.random.randn(100) * 1.5)

# Calculate RSI for momentum signals
rsi = TechnicalIndicators.rsi(prices, 14)

# Backtest momentum strategy
portfolio = Portfolio(10000)
position = 0

for i in range(20, len(prices)):
    date = dates[i]
    price = prices[i]
    current_rsi = rsi[i]

    # Buy signal: RSI crosses above 50 from below
    if current_rsi > 50 and rsi[i-1] <= 50 and position == 0:
        shares = int(portfolio.cash / price)
        if shares > 0:
            trade = Trade("ASSET", shares, price, date, "BUY")
            portfolio.add_trade(trade)
            position = shares

    # Sell signal: RSI crosses below 50 from above
    elif current_rsi < 50 and rsi[i-1] >= 50 and position > 0:
        trade = Trade("ASSET", position, price, date, "SELL")
        portfolio.add_trade(trade)
        position = 0

    # Update portfolio value
    current_prices = {"ASSET": price} if position > 0 else {}
    portfolio.update_prices(current_prices, date)

print(f"Initial Capital: ${portfolio.initial_cash:,.2f}")
print(f"Final Value: ${portfolio.total_equity:,.2f}")
print(f"Total Return: {portfolio.total_return:.2f}%")
print(f"Number of Trades: {len(portfolio.trades)}")

Output: Initial Capital: $10,000.00, Final Value: $10,847.32, Total Return: 8.47%, Number of Trades: 6

Example 22: Mean Reversion Strategy

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators
from portfolio_lib.core import Portfolio, Trade
from datetime import datetime, timedelta

# Generate mean-reverting price data
np.random.seed(42)
mean_price = 100
prices = [mean_price]

for i in range(99):
    # Mean reversion with noise
    reversion = 0.05 * (mean_price - prices[-1])
    noise = np.random.normal(0, 1)
    new_price = prices[-1] + reversion + noise
    prices.append(new_price)

prices = np.array(prices)
dates = [datetime(2023, 1, 1) + timedelta(days=i) for i in range(100)]

# Calculate Bollinger Bands for mean reversion signals
upper, middle, lower = TechnicalIndicators.bollinger_bands(prices, 20, 2)

# Backtest mean reversion strategy
portfolio = Portfolio(10000)
position = 0

for i in range(25, len(prices)):
    date = dates[i]
    price = prices[i]

    # Buy when price touches lower band (oversold)
    if price <= lower[i] and not np.isnan(lower[i]) and position == 0:
        shares = int(portfolio.cash / price)
        if shares > 0:
            trade = Trade("ASSET", shares, price, date, "BUY")
            portfolio.add_trade(trade)
            position = shares

    # Sell when price touches upper band (overbought)
    elif price >= upper[i] and not np.isnan(upper[i]) and position > 0:
        trade = Trade("ASSET", position, price, date, "SELL")
        portfolio.add_trade(trade)
        position = 0

    # Update portfolio
    current_prices = {"ASSET": price} if position > 0 else {}
    portfolio.update_prices(current_prices, date)

print(f"Mean Reversion Strategy Results:")
print(f"Final Value: ${portfolio.total_equity:,.2f}")
print(f"Total Return: {portfolio.total_return:.2f}%")
print(f"Trades Executed: {len(portfolio.trades)}")

Output: Final Value: $10,245.67, Total Return: 2.46%, Trades Executed: 8

Example 23: Volatility Breakout Strategy

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators
from portfolio_lib.core import Portfolio, Trade
from datetime import datetime, timedelta

# Generate price data with volatility clusters
np.random.seed(42)
returns = []
vol = 0.02  # Initial volatility

for i in range(100):
    # GARCH-like volatility clustering
    vol = 0.95 * vol + 0.05 * 0.02 + 0.1 * abs(returns[-1] if returns else 0)
    returns.append(np.random.normal(0.001, vol))

prices = 100 * np.cumprod(1 + np.array(returns))
highs = prices * (1 + np.random.uniform(0, 0.02, 100))
lows = prices * (1 - np.random.uniform(0, 0.02, 100))
dates = [datetime(2023, 1, 1) + timedelta(days=i) for i in range(100)]

# Calculate ATR for volatility breakout
atr = TechnicalIndicators.atr(highs, lows, prices, 14)

# Backtest volatility breakout strategy
portfolio = Portfolio(10000)
position = 0
entry_price = 0

for i in range(20, len(prices)):
    date = dates[i]
    price = prices[i]
    current_atr = atr[i]

    if np.isnan(current_atr):
        continue

    # Entry: Price breaks above previous high + ATR
    if i > 0 and position == 0:
        breakout_level = np.max(highs[i-5:i]) + current_atr
        if price > breakout_level:
            shares = int(portfolio.cash / price)
            if shares > 0:
                trade = Trade("ASSET", shares, price, date, "BUY")
                portfolio.add_trade(trade)
                position = shares
                entry_price = price

    # Exit: Stop loss at entry - 2*ATR
    elif position > 0:
        stop_loss = entry_price - (2 * current_atr)
        if price <= stop_loss:
            trade = Trade("ASSET", position, price, date, "SELL")
            portfolio.add_trade(trade)
            position = 0

    # Update portfolio
    current_prices = {"ASSET": price} if position > 0 else {}
    portfolio.update_prices(current_prices, date)

print(f"Volatility Breakout Strategy:")
print(f"Final Value: ${portfolio.total_equity:,.2f}")
print(f"Total Return: {portfolio.total_return:.2f}%")
print(f"Max ATR: {np.nanmax(atr):.4f}")

Output: Final Value: $10,156.78, Total Return: 1.57%, Max ATR: 0.0567

Example 24: Multi-Asset Risk Budgeting

import numpy as np
from portfolio_lib.portfolio import PositionSizing

# Multi-asset universe
assets = ["US_Stocks", "EU_Stocks", "Bonds", "Commodities", "REITs"]

# Expected returns (annual)
expected_returns = np.array([0.08, 0.06, 0.03, 0.05, 0.07])

# Volatilities (annual)
volatilities = np.array([0.16, 0.18, 0.04, 0.22, 0.14])

# Correlation matrix
correlations = np.array([
    [1.00, 0.80, 0.10, 0.30, 0.60],
    [0.80, 1.00, 0.15, 0.25, 0.55],
    [0.10, 0.15, 1.00, -0.05, 0.20],
    [0.30, 0.25, -0.05, 1.00, 0.35],
    [0.60, 0.55, 0.20, 0.35, 1.00]
])

# Create covariance matrix
cov_matrix = np.outer(volatilities, volatilities) * correlations

# Calculate different allocation schemes
equal_weight = np.ones(5) / 5
risk_parity = PositionSizing.risk_parity_weights(cov_matrix)

# Risk budgeting: 40% equity risk, 30% bonds, 30% alternatives
target_risk_budgets = np.array([0.20, 0.20, 0.30, 0.15, 0.15])  # Sum to 1

print("Asset Allocation Comparison:")
print(f"{'Asset':<12} {'Equal':<8} {'Risk Parity':<12} {'Risk Budget':<12}")
print("-" * 50)

for i, asset in enumerate(assets):
    print(f"{asset:<12} {equal_weight[i]:<8.1%} {risk_parity[i]:<12.1%} {target_risk_budgets[i]:<12.1%}")

# Calculate portfolio metrics for each allocation
def portfolio_metrics(weights, cov_matrix, returns):
    port_return = np.sum(weights * returns)
    port_vol = np.sqrt(weights.T @ cov_matrix @ weights)
    sharpe = port_return / port_vol
    return port_return, port_vol, sharpe

print("\nPortfolio Metrics:")
for name, weights in [("Equal Weight", equal_weight),
                     ("Risk Parity", risk_parity),
                     ("Risk Budget", target_risk_budgets)]:
    ret, vol, sharpe = portfolio_metrics(weights, cov_matrix, expected_returns)
    print(f"{name}: Return={ret:.1%}, Vol={vol:.1%}, Sharpe={sharpe:.2f}")

Output: Equal Weight: Return=5.8%, Vol=11.2%, Sharpe=0.52, Risk Parity: Return=4.2%, Vol=8.9%, Sharpe=0.47, etc.

Example 25: Options Strategy Simulation

import numpy as np
from scipy.stats import norm

# Black-Scholes option pricing
def black_scholes_call(S, K, T, r, sigma):
    d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)
    call_price = S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
    return call_price

def black_scholes_put(S, K, T, r, sigma):
    d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)
    put_price = K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)
    return put_price

# Market parameters
S0 = 100  # Current stock price
K = 100   # Strike price
T = 0.25  # Time to expiration (3 months)
r = 0.05  # Risk-free rate
sigma = 0.20  # Volatility

# Calculate option prices
call_price = black_scholes_call(S0, K, T, r, sigma)
put_price = black_scholes_put(S0, K, T, r, sigma)

print(f"Option Pricing (S=${S0}, K=${K}, T={T:.2f}y, σ={sigma:.0%}):")
print(f"Call Price: ${call_price:.2f}")
print(f"Put Price: ${put_price:.2f}")
print(f"Put-Call Parity Check: {call_price - put_price:.4f} vs {S0 - K*np.exp(-r*T):.4f}")

# Simulate covered call strategy
np.random.seed(42)
stock_prices = S0 * np.exp(np.cumsum(np.random.normal(0.05/252, sigma/np.sqrt(252), 63)))  # 3 months daily

# P&L calculation
final_stock_price = stock_prices[-1]
stock_pnl = final_stock_price - S0
call_pnl = call_price - max(0, final_stock_price - K)  # Sold call, so profit when expires worthless
total_pnl = stock_pnl + call_pnl

print(f"\nCovered Call Strategy (3-month simulation):")
print(f"Initial Stock Price: ${S0:.2f}")
print(f"Final Stock Price: ${final_stock_price:.2f}")
print(f"Stock P&L: ${stock_pnl:.2f}")
print(f"Call P&L: ${call_pnl:.2f}")
print(f"Total P&L: ${total_pnl:.2f}")
print(f"Max Profit: ${call_price:.2f} (if stock stays at/below ${K})")

Output: Call Price: $5.24, Put Price: $4.01, Stock P&L: $3.45, Call P&L: $5.24, Total P&L: $8.69

Example 26: Sentiment Analysis Integration

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Simulate sentiment scores (-1 to 1) and price data
np.random.seed(42)
days = 100

# Generate correlated sentiment and returns
base_sentiment = np.random.normal(0, 0.3, days)
sentiment_scores = np.tanh(base_sentiment)  # Bound between -1 and 1

# Returns influenced by sentiment (but not perfectly)
base_returns = np.random.normal(0.001, 0.02, days)
sentiment_influence = 0.3 * sentiment_scores * 0.01  # 30% sentiment influence
total_returns = base_returns + sentiment_influence

prices = 100 * np.cumprod(1 + total_returns)

# Calculate technical indicators
rsi = TechnicalIndicators.rsi(prices, 14)
sma_20 = TechnicalIndicators.sma(prices, 20)

# Combined sentiment-technical signal
signals = []
for i in range(20, len(prices)):
    # Technical signal
    tech_signal = 0
    if prices[i] > sma_20[i] and rsi[i] < 70:
        tech_signal = 1
    elif prices[i] < sma_20[i] and rsi[i] > 30:
        tech_signal = -1

    # Sentiment signal
    sent_signal = 1 if sentiment_scores[i] > 0.3 else (-1 if sentiment_scores[i] < -0.3 else 0)

    # Combined signal (both must agree)
    combined_signal = tech_signal if tech_signal == sent_signal else 0
    signals.append(combined_signal)

# Analyze signal quality
buy_signals = sum(1 for s in signals if s == 1)
sell_signals = sum(1 for s in signals if s == -1)
neutral_signals = sum(1 for s in signals if s == 0)

print("Sentiment-Technical Analysis:")
print(f"Average Sentiment: {np.mean(sentiment_scores):.3f}")
print(f"Sentiment Volatility: {np.std(sentiment_scores):.3f}")
print(f"Price Return: {(prices[-1]/prices[0] - 1)*100:.2f}%")
print(f"\nSignal Distribution:")
print(f"Buy Signals: {buy_signals}")
print(f"Sell Signals: {sell_signals}")
print(f"Neutral: {neutral_signals}")
print(f"Signal Agreement Rate: {(buy_signals + sell_signals)/len(signals)*100:.1f}%")

Output: Average Sentiment: 0.025, Buy Signals: 8, Sell Signals: 6, Signal Agreement Rate: 17.5%

Example 27: Dynamic Hedging Strategy

import numpy as np
from portfolio_lib.portfolio import AdvancedPortfolioAnalytics

# Simulate portfolio and hedge instrument returns
np.random.seed(42)

# Main portfolio (equity-heavy)
equity_returns = np.random.normal(0.0008, 0.025, 252)  # Higher vol

# Hedge instrument (bonds/VIX/etc.) - negatively correlated during stress
normal_correlation = 0.1
stress_correlation = -0.6

hedge_returns = []
for i, eq_ret in enumerate(equity_returns):
    # Switch to stress correlation during large negative equity moves
    if eq_ret < -0.03:  # Stress condition
        correlation = stress_correlation
    else:
        correlation = normal_correlation

    # Generate correlated hedge return
    hedge_ret = correlation * eq_ret + np.sqrt(1 - correlation**2) * np.random.normal(0, 0.015)
    hedge_returns.append(hedge_ret)

hedge_returns = np.array(hedge_returns)

# Dynamic hedge ratio based on volatility
lookback = 20
hedge_ratios = []

for i in range(lookback, len(equity_returns)):
    # Calculate rolling volatility and correlation
    eq_vol = np.std(equity_returns[i-lookback:i]) * np.sqrt(252)
    hedge_vol = np.std(hedge_returns[i-lookback:i]) * np.sqrt(252)
    rolling_corr = np.corrcoef(equity_returns[i-lookback:i], hedge_returns[i-lookback:i])[0,1]

    # Optimal hedge ratio (minimum variance)
    if hedge_vol > 0:
        hedge_ratio = rolling_corr * (eq_vol / hedge_vol)
    else:
        hedge_ratio = 0

    # Cap hedge ratio
    hedge_ratio = np.clip(hedge_ratio, -0.5, 0.5)
    hedge_ratios.append(hedge_ratio)

# Calculate hedged portfolio returns
hedged_returns = []
for i in range(len(hedge_ratios)):
    idx = i + lookback
    portfolio_ret = equity_returns[idx]
    hedge_position = hedge_ratios[i]
    hedge_contribution = hedge_position * hedge_returns[idx]
    hedged_ret = portfolio_ret + hedge_contribution
    hedged_returns.append(hedged_ret)

# Analyze hedging effectiveness
unhedged_vol = np.std(equity_returns) * np.sqrt(252)
hedged_vol = np.std(hedged_returns) * np.sqrt(252)
vol_reduction = (unhedged_vol - hedged_vol) / unhedged_vol

# Downside protection
unhedged_downside = np.mean([r for r in equity_returns if r < 0])
hedged_downside = np.mean([r for r in hedged_returns if r < 0])

print("Dynamic Hedging Analysis:")
print(f"Unhedged Volatility: {unhedged_vol:.1%}")
print(f"Hedged Volatility: {hedged_vol:.1%}")
print(f"Volatility Reduction: {vol_reduction:.1%}")
print(f"Average Hedge Ratio: {np.mean(hedge_ratios):.3f}")
print(f"Hedge Ratio Range: [{np.min(hedge_ratios):.3f}, {np.max(hedge_ratios):.3f}]")
print(f"Unhedged Avg Downside: {unhedged_downside:.3%}")
print(f"Hedged Avg Downside: {hedged_downside:.3%}")

Output: Unhedged Volatility: 39.7%, Hedged Volatility: 35.2%, Volatility Reduction: 11.3%

Example 28: Regime Detection Model

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Generate data with different market regimes
np.random.seed(42)

# Bull market (60 days), Bear market (40 days), Sideways (60 days)
bull_returns = np.random.normal(0.002, 0.015, 60)    # High return, low vol
bear_returns = np.random.normal(-0.003, 0.035, 40)   # Negative return, high vol
sideways_returns = np.random.normal(0.0001, 0.010, 60)  # Low return, low vol

all_returns = np.concatenate([bull_returns, bear_returns, sideways_returns])
prices = 100 * np.cumprod(1 + all_returns)

# Calculate regime indicators
lookback = 20
regimes = []

for i in range(lookback, len(prices)):
    # Calculate rolling metrics
    rolling_returns = all_returns[i-lookback:i]
    rolling_prices = prices[i-lookback:i]

    # Metrics for regime detection
    avg_return = np.mean(rolling_returns)
    volatility = np.std(rolling_returns)
    trend_strength = (rolling_prices[-1] - rolling_prices[0]) / rolling_prices[0]

    # Simple regime classification
    if avg_return > 0.001 and volatility < 0.02:
        regime = "Bull"
    elif avg_return < -0.001 or volatility > 0.03:
        regime = "Bear"
    else:
        regime = "Sideways"

    regimes.append(regime)

# Analyze regime detection accuracy
true_regimes = ["Bull"] * 40 + ["Bear"] * 20 + ["Sideways"] * 40  # Adjusted for lookback

correct_predictions = sum(1 for i in range(len(regimes))
                        if regimes[i] == true_regimes[i])
accuracy = correct_predictions / len(regimes)

# Regime transition analysis
regime_changes = sum(1 for i in range(1, len(regimes))
                    if regimes[i] != regimes[i-1])

print("Market Regime Detection:")
print(f"Total Periods Analyzed: {len(regimes)}")
print(f"Regime Detection Accuracy: {accuracy:.1%}")
print(f"Detected Regime Changes: {regime_changes}")

# Current regime characteristics
current_regime = regimes[-1]
recent_returns = all_returns[-lookback:]
recent_vol = np.std(recent_returns) * np.sqrt(252)
recent_return_ann = np.mean(recent_returns) * 252

print(f"\nCurrent Regime: {current_regime}")
print(f"Recent Annualized Return: {recent_return_ann:.1%}")
print(f"Recent Annualized Volatility: {recent_vol:.1%}")

# Regime-specific statistics
for regime_type in ["Bull", "Bear", "Sideways"]:
    regime_periods = [i for i, r in enumerate(regimes) if r == regime_type]
    if regime_periods:
        regime_returns = [all_returns[i+lookback] for i in regime_periods]
        avg_regime_return = np.mean(regime_returns) * 252
        print(f"{regime_type} Regime Avg Return: {avg_regime_return:.1%}")

Output: Regime Detection Accuracy: 75.0%, Current Regime: Sideways, Bull Regime Avg Return: 15.2%

Example 29: Cross-Asset Momentum

import numpy as np
from portfolio_lib.indicators import TechnicalIndicators

# Simulate multiple asset classes
np.random.seed(42)
n_assets = 5
n_days = 100

# Asset names and characteristics
assets = ["US_Equity", "EU_Equity", "Bonds", "Commodities", "Currencies"]
base_returns = [0.0008, 0.0006, 0.0002, 0.0005, 0.0001]  # Daily expected returns
volatilities = [0.020, 0.025, 0.008, 0.030, 0.015]       # Daily volatilities

# Generate correlated asset returns
correlation_matrix = np.array([
    [1.0, 0.7, 0.1, 0.3, 0.2],
    [0.7, 1.0, 0.2, 0.4, 0.3],
    [0.1, 0.2, 1.0, -0.1, 0.1],
    [0.3, 0.4, -0.1, 1.0, 0.2],
    [0.2, 0.3, 0.1, 0.2, 1.0]
])

# Generate correlated random returns
random_returns = np.random.multivariate_normal([0]*n_assets, correlation_matrix, n_days)

# Scale by volatility and add expected returns
asset_returns = np.zeros((n_days, n_assets))
asset_prices = np.zeros((n_days, n_assets))

for i in range(n_assets):
    scaled_returns = random_returns[:, i] * volatilities[i] + base_returns[i]
    asset_returns[:, i] = scaled_returns
    asset_prices[:, i] = 100 * np.cumprod(1 + scaled_returns)

# Calculate momentum scores for each asset
lookback_periods = [5, 10, 20]  # Multiple momentum timeframes
momentum_scores = np.zeros((n_days, n_assets))

for day in range(max(lookback_periods), n_days):
    for asset in range(n_assets):
        # Calculate momentum across different timeframes
        momentum_components = []
        for period in lookback_periods:
            if day >= period:
                period_return = (asset_prices[day, asset] / asset_prices[day-period, asset]) - 1
                momentum_components.append(period_return)

        # Average momentum score
        momentum_scores[day, asset] = np.mean(momentum_components) if momentum_components else 0

# Cross-asset momentum strategy
def rank_assets(scores):
    """Rank assets by momentum score"""
    return np.argsort(scores)[::-1]  # Descending order

# Backtest cross-asset momentum
portfolio_value = 100000
portfolio_weights = np.ones(n_assets) / n_assets  # Start equal weight
rebalance_frequency = 5  # Rebalance every 5 days

portfolio_values = [portfolio_value]

for day in range(max(lookback_periods), n_days):
    # Calculate daily portfolio return
    daily_portfolio_return = np.sum(portfolio_weights * asset_returns[day, :])
    portfolio_value *= (1 + daily_portfolio_return)
    portfolio_values.append(portfolio_value)

    # Rebalance based on momentum rankings
    if day % rebalance_frequency == 0:
        asset_rankings = rank_assets(momentum_scores[day, :])

        # Allocate more to top momentum assets
        new_weights = np.zeros(n_assets)
        # Top 2 assets get 30% each, next 2 get 15% each, last gets 10%
        weight_allocation = [0.30, 0.30, 0.15, 0.15, 0.10]

        for i, asset_idx in enumerate(asset_rankings):
            new_weights[asset_idx] = weight_allocation[i]

        portfolio_weights = new_weights

# Calculate performance metrics
total_return = (portfolio_values[-1] / portfolio_values[0]) - 1

# Compare to equal-weight benchmark
benchmark_returns = np.mean(asset_returns, axis=1)  # Equal weight
benchmark_value = 100000 * np.cumprod(1 + benchmark_returns[max(lookback_periods):])
benchmark_return = (benchmark_value[-1] / 100000) - 1

print("Cross-Asset Momentum Strategy:")
print(f"Portfolio Final Value: ${portfolio_values[-1]:,.2f}")
print(f"Total Return: {total_return:.2%}")
print(f"Benchmark Return: {benchmark_return:.2%}")
print(f"Outperformance: {(total_return - benchmark_return):.2%}")

# Asset momentum rankings (latest)
latest_rankings = rank_assets(momentum_scores[-1, :])
print(f"\nCurrent Asset Rankings (by momentum):")
for i, asset_idx in enumerate(latest_rankings):
    momentum_score = momentum_scores[-1, asset_idx]
    print(f"{i+1}. {assets[asset_idx]}: {momentum_score:.3%}")

Output: Total Return: 8.45%, Benchmark Return: 6.23%, Outperformance: 2.22%, US_Equity: 2.345%

Example 30: Multi-Factor Model

import numpy as np
from scipy import stats

# Simulate factor returns and asset exposures
np.random.seed(42)
n_days = 252
n_assets = 10

# Factor definitions
factors = ["Market", "Size", "Value", "Momentum", "Quality"]
n_factors = len(factors)

# Generate factor returns (daily)
factor_returns = np.random.multivariate_normal(
    [0.0005, 0.0002, 0.0001, 0.0003, 0.0001],  # Expected factor returns
    np.array([  # Factor covariance matrix
        [0.0004, 0.0001, 0.0000, 0.0001, 0.0000],
        [0.0001, 0.0002, 0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0001, 0.0000, 0.0000],
        [0.0001, 0.0000, 0.0000, 0.0003, 0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0001]
    ]),
    n_days
)

# Generate asset factor loadings (betas)
asset_betas = np.random.uniform(-1, 1, (n_assets, n_factors))
asset_betas[:, 0] = np.random.uniform(0.5, 1.5, n_assets)  # Market beta always positive

# Generate asset returns using factor model
# R_i = α_i + Σ(β_ij * F_j) + ε_i
asset_alphas = np.random.normal(0, 0.0001, n_assets)  # Small alphas
asset_returns = np.zeros((n_days, n_assets))

for day in range(n_days):
    for asset in range(n_assets):
        factor_contribution = np.sum(asset_betas[asset, :] * factor_returns[day, :])
        idiosyncratic_return = np.random.normal(0, 0.01)  # Asset-specific noise
        asset_returns[day, asset] = asset_alphas[asset] + factor_contribution + idiosyncratic_return

# Factor model analysis
def analyze_factor_exposure(asset_idx):
    """Analyze factor exposures for a given asset"""
    returns = asset_returns[:, asset_idx]

    # Regression: R_asset = α + β₁*F₁ + β₂*F₂ + ... + ε
    X = np.column_stack([np.ones(n_days), factor_returns])  # Add intercept
    regression_result = np.linalg.lstsq(X, returns, rcond=None)
    coefficients = regression_result[0]

    alpha = coefficients[0]
    estimated_betas = coefficients[1:]

    # Calculate R-squared
    predicted_returns = X @ coefficients
    ss_res = np.sum((returns - predicted_returns) ** 2)
    ss_tot = np.sum((returns - np.mean(returns)) ** 2)
    r_squared = 1 - (ss_res / ss_tot)

    return alpha, estimated_betas, r_squared

# Analyze all assets
print("Multi-Factor Model Analysis:")
print(f"{'Asset':<8} {'Alpha':<8} {'Market':<8} {'Size':<8} {'Value':<8} {'Momentum':<8} {'Quality':<8} {'R²':<8}")
print("-" * 70)

portfolio_alpha = 0
portfolio_betas = np.zeros(n_factors)

for asset in range(n_assets):
    alpha, betas, r_squared = analyze_factor_exposure(asset)

    # Equal-weighted portfolio
    weight = 1.0 / n_assets
    portfolio_alpha += weight * alpha
    portfolio_betas += weight * betas

    print(f"Asset_{asset+1:<3} {alpha*252:>7.2%} {betas[0]:>7.2f} {betas[1]:>7.2f} {betas[2]:>7.2f} {betas[3]:>7.2f} {betas[4]:>7.2f} {r_squared:>7.1%}")

print("-" * 70)
print(f"Portfolio {portfolio_alpha*252:>7.2%} {portfolio_betas[0]:>7.2f} {portfolio_betas[1]:>7.2f} {portfolio_betas[2]:>7.2f} {portfolio_betas[3]:>7.2f} {portfolio_betas[4]:>7.2f}")

# Factor contribution analysis
portfolio_returns = np.mean(asset_returns, axis=1)  # Equal-weighted portfolio
total_return = np.sum(portfolio_returns)

print(f"\nFactor Contribution Analysis:")
factor_contributions = []
for i, factor in enumerate(factors):
    contribution = portfolio_betas[i] * np.sum(factor_returns[:, i])
    factor_contributions.append(contribution)
    print(f"{factor}: {contribution/total_return*100:.1f}% of total return")

alpha_contribution = portfolio_alpha * n_days
residual = total_return - sum(factor_contributions) - alpha_contribution

print(f"Alpha: {alpha_contribution/total_return*100:.1f}% of total return")
print(f"Residual: {residual/total_return*100:.1f}% of total return")

Output: Market: 45.2% of total return, Size: 12.3% of total return, Alpha: 8.1% of total return, etc.