Overall Statistics |
Total Orders 3820 Average Win 0.15% Average Loss -0.11% Compounding Annual Return 14.430% Drawdown 42.400% Expectancy 0.588 Start Equity 1000000 End Equity 1963413.20 Net Profit 96.341% Sharpe Ratio 0.438 Sortino Ratio 0.458 Probabilistic Sharpe Ratio 10.193% Loss Rate 33% Win Rate 67% Profit-Loss Ratio 1.36 Alpha -0.004 Beta 1.186 Annual Standard Deviation 0.228 Annual Variance 0.052 Information Ratio 0.12 Tracking Error 0.101 Treynor Ratio 0.084 Total Fees $2831.13 Estimated Strategy Capacity $48000000.00 Lowest Capacity Asset ERIE R735QTJ8XC9X Portfolio Turnover 0.69% |
#region imports from AlgorithmImports import * #endregion def CalculateTrendIndicators(self): """ Calculates momentum-based trading signals using historical price data. Trading Logic: 1. Signal Generation: - Analyzes price history over specified lookback period - Calculates compound returns for each security - Filters for only positive-returning securities 2. Security Selection: - Ranks securities by compound return performance - Selects top 10% of performers - Implements momentum factor strategy 3. Performance Monitoring: - Tracks average, minimum, and maximum returns - Monitors selection pool size and characteristics - Provides detailed logging of selection metrics Risk Management: - Only considers securities with positive returns - Uses compound returns to capture consistent performers - Implements percentage-based selection for diversification Returns: list: Top performing securities selected for potential investment, filtered by positive returns and ranked by performance. """ # Define the percentage of top performers to select top_pct = 0.1 # Select top 10% of securities - momentum concentration control # Calculate compounded returns for each security compounded_returns = {} for symbol, prices in self.historical_data.items(): if len(prices) >= self.lookback: # Ensure sufficient data daily_returns = prices.pct_change().dropna() compounded_return = (1 + daily_returns).prod() - 1 # Risk management: Only include positive returns if compounded_return > 0: compounded_returns[symbol] = compounded_return # Calculate and log performance statistics if compounded_returns: returns_values = list(compounded_returns.values()) avg_return = sum(returns_values) / len(returns_values) min_return = min(returns_values) max_return = max(returns_values) # Performance monitoring logs self.Debug(f"**Compounded Returns Statistics:**") self.Debug(f"Number of symbols analyzed (positive returns only): {len(compounded_returns)}") self.Debug(f"Population average return: {avg_return:.2%}") self.Debug(f"Population min return: {min_return:.2%}") self.Debug(f"Population max return: {max_return:.2%}") # Select top performing symbols using momentum strategy top_symbols = sorted(compounded_returns, key=compounded_returns.get, reverse=True) top_symbols = top_symbols[:int(len(compounded_returns) * top_pct)] # Log selection statistics self.Debug(f"**Selected {len(top_symbols)} top symbols out of {len(compounded_returns)} total symbols (all positive returns)**") if top_symbols: top_returns = [compounded_returns[symbol] for symbol in top_symbols] self.Debug(f"Selected symbols average return: {sum(top_returns) / len(top_returns):.2%}") self.Debug(f"Selected symbols return range: {min(top_returns):.2%} to {max(top_returns):.2%}") return top_symbols
#region imports from AlgorithmImports import * from pypfopt import BlackLittermanModel import pandas as pd import numpy as np #endregion def OptimizePortfolio(self, total_returns, volatilities): """ Implements a sophisticated portfolio optimization strategy using risk-adjusted returns. Trading Logic: 1. Weight Calculation: - Uses return-to-volatility ratios for position sizing - Implements risk-adjusted allocation strategy - Normalizes weights to respect leverage constraints 2. Risk Management: - Monitors for negative returns/volatilities - Implements position size limits - Ensures portfolio leverage constraints 3. Portfolio Construction: - Calculates optimal position sizes - Applies leverage and concentration limits - Normalizes allocations to meet constraints 4. Performance Monitoring: - Tracks allocation statistics - Monitors position concentration - Logs portfolio characteristics Args: total_returns (pd.Series): Historical returns for each asset volatilities (pd.Series): Calculated volatilities for each asset Returns: dict: Optimized portfolio weights for each symbol, respecting all risk and leverage constraints """ # Monitor for data quality issues for symbol in total_returns.index: if total_returns[symbol] < 0: self.Debug(f"Warning: Negative return detected for {symbol}: {total_returns[symbol]:.4f}") if volatilities[symbol] < 0: self.Debug(f"Warning: Negative volatility detected for {symbol}: {volatilities[symbol]:.4f}") # Calculate risk-adjusted return scores epsilon = 1e-8 # Numerical stability factor return_to_vol = total_returns / (volatilities + epsilon) # Calculate initial weights using risk-adjusted scores total_score = return_to_vol.sum() weights = return_to_vol / total_score if total_score != 0 else pd.Series(0, index=return_to_vol.index) # Apply leverage constraints for risk management max_allocation = self.universe_settings.leverage # Scale weights to meet leverage constraints total_weight = sum(abs(weight) for weight in weights) scaling_factor = max_allocation / total_weight if total_weight > max_allocation else 1.0 # Create final weight dictionary with constraints applied normalized_weights = {symbol: weight * scaling_factor for symbol, weight in weights.items()} # Log portfolio construction metrics self.Debug(f"**Portfolio Allocation Statistics:**") self.Debug(f"Number of positions: {len(normalized_weights)}") self.Debug(f"Average position size: {np.mean(list(normalized_weights.values())):.2%}") self.Debug(f"Position size range: {min(normalized_weights.values()):.2%} to {max(normalized_weights.values()):.2%}") self.Debug(f"Total allocation: {sum(normalized_weights.values()):.2%}") return normalized_weights
#region imports from pypfopt import risk_models, expected_returns from AlgorithmImports import * import numpy as np import pandas as pd #endregion def CalculateRiskParameters(self, top_symbols): """ Calculates comprehensive risk metrics for portfolio management. Trading Logic: 1. Risk Assessment: - Calculates individual security volatilities - Computes historical returns for risk-adjusted metrics - Analyzes return distributions 2. Data Validation: - Ensures data availability for each symbol - Handles missing data scenarios - Provides warning logs for data issues 3. Risk Metrics: - Calculates daily returns and volatilities - Annualizes volatility metrics (252 trading days) - Computes total period returns 4. Risk Monitoring: - Tracks average volatility levels - Monitors volatility extremes - Reports data quality metrics Args: top_symbols (list): List of symbols to analyze for risk metrics Returns: tuple: (returns, volatilities) containing: - returns: pandas Series of total period returns - volatilities: pandas Series of annualized volatilities Used for portfolio optimization and position sizing """ # Filter and validate historical data selected_data = {} for symbol in top_symbols: if symbol in self.historical_data: selected_data[symbol] = self.historical_data[symbol] else: self.Debug(f"Warning: No historical data found for {symbol}") # Data validation check if not selected_data: self.Debug("Error: No valid historical data found for any symbols") return pd.Series(), pd.Series() # Convert to DataFrame for calculations selected_history = pd.DataFrame(selected_data) # Calculate risk metrics daily_returns = selected_history.pct_change().dropna() total_returns = (1 + daily_returns).prod() - 1 # Annualize volatility for risk scaling volatilities = daily_returns.std() * np.sqrt(252) # Annualization factor # Risk monitoring logs self.Debug(f"**Volatility Statistics:**") self.Debug(f"Average Annualized Volatility: {volatilities.mean():.2%}") self.Debug(f"Min Volatility: {volatilities.min():.2%}") self.Debug(f"Max Volatility: {volatilities.max():.2%}") # Data quality metrics self.Debug(f"Number of symbols analyzed: {len(selected_data)}") self.Debug(f"Data points per symbol: {len(daily_returns)}") return total_returns, volatilities
#region imports from AlgorithmImports import * #endregion def Execute_Trades(self, position_list): """ Implements an intelligent trade execution strategy with capital efficiency. Trading Logic: 1. Trade Sequencing: - Prioritizes position reductions before increases - Orders increases by size (smallest first) - Optimizes capital utilization 2. Position Management: - Separates increase/decrease trades - Tracks current vs target positions - Manages position adjustments efficiently 3. Risk Management: - Executes reductions first to free capital - Controls position sizing - Implements smart order routing 4. Execution Strategy: - Uses SetHoldings for position management - Implements size-based execution ordering - Optimizes trade sequence Args: position_list (dict): Target portfolio weights for each symbol Format: {'AAPL': 0.25, 'GOOGL': 0.25} """ self.Debug("***Placing Trades***") # Separate trades by direction for optimal execution reduce_trades = {} increase_trades = {} # Analyze current vs target positions for symbol, target_weight in position_list.items(): holding = self.Portfolio[symbol] current_weight = holding.HoldingsValue / self.Portfolio.TotalPortfolioValue if holding.Invested else 0 # Categorize trades by direction if current_weight > target_weight: reduce_trades[symbol] = target_weight elif current_weight < target_weight: increase_trades[symbol] = target_weight # Execute reductions first to free up capital for symbol, weight in reduce_trades.items(): self.SetHoldings(symbol, weight) # Execute increases in order of size (smallest first) sorted_increases = sorted(increase_trades.items(), key=lambda x: x[1]) for symbol, weight in sorted_increases: self.SetHoldings(symbol, weight) def Exit_Positions(self, position_list): """ Manages complete position exits and portfolio cleanup. Trading Logic: 1. Position Analysis: - Compares current holdings to target portfolio - Identifies positions for complete exit - Maintains portfolio alignment 2. Exit Management: - Executes complete liquidations - Cleans up legacy positions - Ensures portfolio accuracy 3. Risk Management: - Removes unwanted exposure - Maintains clean portfolio structure - Implements efficient liquidation Args: position_list (dict): Dictionary of target positions Any holding not in this list will be liquidated """ # Review current holdings against target portfolio for holding in self.Portfolio.Values: # Liquidate positions not in target portfolio if holding.Symbol not in position_list and holding.Invested: self.Liquidate(holding.Symbol)
# region imports from AlgorithmImports import * from Alpha_Models import CalculateTrendIndicators from Risk_Models import CalculateRiskParameters from Portfolio_Construction import OptimizePortfolio from Trade_Execution import Execute_Trades, Exit_Positions # endregion class NCSU_Strategy_2024_Q3(QCAlgorithm): """ A sophisticated quantitative trading strategy implementing a trend-following approach with comprehensive risk management. Trading Logic Overview: 1. Universe Selection: Uses SPY ETF constituents to focus on liquid, large-cap stocks 2. Signal Generation: Implements trend following by analyzing compound returns over a 63-day lookback period 3. Risk Management: - Employs position sizing limits - Implements drawdown protection - Uses volatility-adjusted position sizing 4. Portfolio Construction: - Optimizes weights using return-to-volatility ratios - Applies leverage constraints (max 2.0x) - Manages concentration risk 5. Trade Execution: - Prioritizes reducing positions before increasing - Implements transaction cost consideration - Uses smart order routing with SetHoldings Key Parameters: - Lookback Period: 63 days - Rebalance Threshold: 4% - Maximum Leverage: 2.0x - Initial Capital: $1,000,000 """ def Initialize(self): """ Initializes the strategy with core settings and parameters. This method: 1. Sets the backtest period and initial capital 2. Configures the investment universe using SPY constituents 3. Establishes risk parameters and trading thresholds 4. Initializes tracking variables for portfolio management The initialization ensures all components are properly set up before trading begins. """ # Set basic algorithm parameters self.SetStartDate(2020, 1, 1) self.SetEndDate(2024, 12, 31) self.SetCash(1000000) # Define the universe using SPY ETF constituents self.etf = "SPY" # Set maximum leverage for the strategy self.universe_settings.leverage = 2.0 # Add ETF and set up universe selection self.AddEquity(self.etf, Resolution.Daily) self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverseSelection(ETFConstituentsUniverseSelectionModel(self.etf)) # Initialize strategy parameters self.historical_data = {} # Store historical prices self.lookback = 63 # Lookback period for calculations # Risk management parameters self.equity_high_water_mark = self.Portfolio.TotalPortfolioValue self.rebalance_threshold = 0.04 # 4% rebalance threshold self.rebalance = True self.current_equity = self.Portfolio.TotalPortfolioValue def OnSecuritiesChanged(self, changes): """ Manages the dynamic universe of tradeable securities. Trading Logic: 1. For new securities: - Retrieves historical data for signal generation - Adds to tracking system for ongoing monitoring 2. For removed securities: - Liquidates any existing positions - Triggers rebalance to reallocate capital - Cleans up historical data tracking Args: changes (SecurityChanges): Contains lists of added and removed securities """ # Add new securities to historical data tracking for security in changes.AddedSecurities: try: history = self.History(security.Symbol, self.lookback, Resolution.Daily) if not history.empty and 'close' in history: self.historical_data[security.Symbol] = history['close'] else: self.Debug(f"No historical data available for {security.Symbol}") continue except Exception as e: self.Debug(f"Error getting historical data for {security.Symbol}: {str(e)}") continue # Clean up removed securities for security in changes.RemovedSecurities: if security.Symbol in self.historical_data: del self.historical_data[security.Symbol] # Liquidate positions in removed securities if self.Portfolio[security.Symbol].Invested: self.Liquidate(security.Symbol) self.Debug(f"Liquidating {security.Symbol} as it is removed from the ETF") # Rebalance portfolio when positions are removed self.Debug(f"Reblancing due to postion liquidation") self.Rebalance() def OnData(self, data): """ Primary market data handler and portfolio monitoring system. Trading Logic: 1. Monitors portfolio value against high-water mark 2. Triggers rebalancing when: - Portfolio value deviates by more than 4% from high-water mark - Manual rebalance flag is set 3. Updates portfolio tracking metrics 4. Implements drawdown protection by monitoring equity ratios Args: data: Current market data slice """ # Update current equity value self.current_equity = self.Portfolio.TotalPortfolioValue # Calculate portfolio rebalance percentage abs_equity_ratio = abs(self.current_equity / self.equity_high_water_mark) # Log equity values and ratios self.Debug(f"**Portfolio Status:**") self.Debug(f"Current Equity: ${self.current_equity:,.2f}") self.Debug(f"High Water Mark: ${self.equity_high_water_mark:,.2f}") self.Debug(f"Absolute Equity Ratio: {abs_equity_ratio:.4f}") # Check for rebalance condition if abs(abs_equity_ratio - 1) >= self.rebalance_threshold or self.rebalance==True: self.Debug(f"**ALERT: Rebalance Threshold {self.rebalance_threshold:.2%}. Rebalancing...**") self.rebalance=True self.Rebalance() self.equity_high_water_mark = self.current_equity # Reset high water mark def Rebalance(self): """ Executes the complete portfolio rebalancing process. Trading Logic Flow: 1. Alpha Signal Generation: - Calculates trend indicators using compound returns - Identifies top-performing securities 2. Risk Assessment: - Calculates volatility metrics - Evaluates return patterns 3. Portfolio Optimization: - Adjusts returns for transaction costs - Optimizes weights based on risk-adjusted returns 4. Trade Execution: - Exits unnecessary positions - Executes new position targets efficiently """ self.Debug(f"Rebalancing on {self.Time}") # Get alpha signals sorted_symbols = CalculateTrendIndicators(self) # Calculate risk parameters #mu, S = CalculateRiskParameters(self, top_symbols=sorted_symbols) total_returns, volatilities = CalculateRiskParameters(self, top_symbols=sorted_symbols) # Adjust expected returns for transaction costs transaction_cost = 0.001 # 0.1% per trade for symbol in sorted_symbols: if symbol in total_returns: total_returns[symbol] -= transaction_cost else: self.Debug(f"Symbol {symbol} not found in total_returns") # Optimize portfolio target_positions = OptimizePortfolio(self, total_returns=total_returns, volatilities=volatilities) # Execute trades Exit_Positions(self, position_list=target_positions) Execute_Trades(self, position_list=target_positions) def OnOrderEvent(self, orderEvent): """ Handles order execution feedback and portfolio state management. Trading Logic: 1. Monitors order execution status 2. Resets rebalancing flag after trades complete 3. Enables tracking of portfolio changes Args: orderEvent (OrderEvent): Details of the executed order including fill price, quantity, and execution time """ self.rebalance = False # Reset rebalanced flag after trades execute