| Overall Statistics |
|
Total Orders 3365 Average Win 0.38% Average Loss -0.34% Compounding Annual Return 7.611% Drawdown 34.600% Expectancy 0.201 Start Equity 100000 End Equity 296914.64 Net Profit 196.915% Sharpe Ratio 0.322 Sortino Ratio 0.262 Probabilistic Sharpe Ratio 0.420% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.14 Alpha -0.013 Beta 0.674 Annual Standard Deviation 0.143 Annual Variance 0.02 Information Ratio -0.364 Tracking Error 0.116 Treynor Ratio 0.068 Total Fees $4698.04 Estimated Strategy Capacity $320000000.00 Lowest Capacity Asset VMC R735QTJ8XC9X Portfolio Turnover 3.17% |
from AlgorithmImports import *
import numpy as np
from datetime import timedelta
class SharpeWeightedStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1) # Set start date
self.SetCash(100000) # Set strategy cash
self.UniverseSettings.Resolution = Resolution.Daily
# Define other settings
self.minimum_market_cap = 20e9 # Minimum market cap
self.number_of_stocks = 50 # Number of stocks to select
self.risk_free_rate = 0.04/252 # Daily risk-free rate
self.lookback = 252 # Trading days lookback
self.next_rebalance = None
self.symbols = []
# Add universe
self.AddUniverse(self.CoarseSelectionFunction)
def CoarseSelectionFunction(self, coarse):
if self.Time.day != 1: # Only run on first day of month
return Universe.Unchanged
# Filter for stocks meeting our criteria
selected = [x for x in coarse if x.HasFundamentalData and
x.Market == "usa" and
x.Price > 0 and
x.MarketCap > self.minimum_market_cap]
# Sort by dollar volume (for liquidity)
selected = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in selected[:200]] # Take top 200 by volume for detailed analysis
def OnData(self, data):
if not self.symbols: # Skip if we haven't selected symbols yet
return
if self.next_rebalance is None:
self.next_rebalance = self.Time + timedelta(days=1)
return
# Check if it's time to rebalance
if self.Time >= self.next_rebalance:
# Calculate next rebalance date (first day of next month)
self.next_rebalance = self.Time.replace(day=1) + timedelta(days=32)
self.next_rebalance = self.next_rebalance.replace(day=1)
self.Rebalance()
def Rebalance(self):
sharpe_ratios = {}
# Calculate Sharpe ratio for each symbol
for symbol in self.symbols:
history = self.History(symbol, self.lookback, Resolution.Daily)
if len(history) < self.lookback: # Skip if not enough data
continue
# Calculate daily returns
returns = history['close'].pct_change().dropna()
# Calculate Sharpe ratio
excess_returns = returns - self.risk_free_rate
expected_return = excess_returns.mean()
volatility = returns.std()
if volatility == 0: # Skip if volatility is zero
continue
sharpe = expected_return / volatility * np.sqrt(252) # Annualized Sharpe
sharpe_ratios[symbol] = sharpe
if not sharpe_ratios: # If no valid Sharpe ratios, skip rebalance
return
# Select top stocks by Sharpe ratio
sorted_symbols = sorted(sharpe_ratios.items(), key=lambda x: x[1], reverse=True)
top_symbols = sorted_symbols[:self.number_of_stocks]
# Calculate weights based on Sharpe ratios
total_sharpe = sum(sharpe for _, sharpe in top_symbols)
weights = {symbol: max(0, sharpe/total_sharpe) for symbol, sharpe in top_symbols}
# Normalize weights to sum to 1
weight_sum = sum(weights.values())
weights = {symbol: weight/weight_sum for symbol, weight in weights.items()}
# Liquidate positions we don't want anymore
for holding in self.Portfolio.Values:
if holding.Symbol not in weights and holding.Invested:
self.Liquidate(holding.Symbol)
# Rebalance to target weights
for symbol, weight in weights.items():
self.SetHoldings(symbol, weight)
def OnSecuritiesChanged(self, changes):
self.symbols = [x.Symbol for x in changes.AddedSecurities]
# Clean up any removed symbols
for removed in changes.RemovedSecurities:
if removed.Symbol in self.symbols:
self.symbols.remove(removed.Symbol)