| Overall Statistics |
|
Total Orders 1709 Average Win 0.82% Average Loss -0.76% Compounding Annual Return 10.635% Drawdown 63.900% Expectancy 0.162 Start Equity 100000 End Equity 257012.57 Net Profit 157.013% Sharpe Ratio 0.323 Sortino Ratio 0.374 Probabilistic Sharpe Ratio 1.279% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.08 Alpha -0.012 Beta 1.282 Annual Standard Deviation 0.268 Annual Variance 0.072 Information Ratio 0.05 Tracking Error 0.189 Treynor Ratio 0.068 Total Fees $5261.91 Estimated Strategy Capacity $1900000.00 Lowest Capacity Asset TNXP VJ0LBTYJ4I91 Portfolio Turnover 2.50% |
from AlgorithmImports import *
import numpy as np
class ValueInvestingInsightsAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1) # Set Start Date
self.SetEndDate(2025, 5, 1) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.AddEquity("SPY", Resolution.Daily)
# Universe selection for value investing
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.value_stocks = []
self.rebalance_time = datetime.min
def CoarseSelectionFunction(self, coarse):
# Filter for stocks with fundamental data and price > 5
filtered = [x for x in coarse if x.HasFundamentalData and x.Price > 5]
# Sort by dollar volume and take top 1000
sorted_by_dollar_volume = sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in sorted_by_dollar_volume[:1000]]
def FineSelectionFunction(self, fine):
"""
Composite z-score ranking with assignment to self.value_stocks:
1. Define metrics
2. Compute mean & std for each metric over valid values
3. Calculate per-symbol z-scores and composite
4. Assign top 20 symbols to self.value_stocks
"""
# 1) Define metrics to use
metrics = {
'EarningYield': lambda x: x.ValuationRatios.EarningYield,
'BookValueYield': lambda x: x.ValuationRatios.BookValueYield,
'CFYield': lambda x: x.ValuationRatios.CFYield,
'FCFYield': lambda x: x.ValuationRatios.FCFYield
}
# 2) Compute mean & std for each metric across valid entries
stats = {}
for name, func in metrics.items():
vals = [func(x) for x in fine if func(x) is not None and func(x) > 0]
if len(vals) >= 2:
arr = np.array(vals, dtype=float)
stats[name] = (arr.mean(), arr.std(ddof=0))
else:
stats[name] = (None, None)
# 3) Build composite z-scores
composites = []
for x in fine:
z_sum = 0.0
count = 0
for name, func in metrics.items():
val = func(x)
mu, sigma = stats[name]
if mu is not None and sigma and val is not None and val > 0:
z_sum += (val - mu) / sigma
count += 1
if count > 0:
composites.append((x.Symbol, z_sum / count))
# 4) Sort by composite z-score descending and take top 20
composites.sort(key=lambda item: item[1], reverse=True)
self.value_stocks = [symbol for symbol, _ in composites[:20]]
return self.value_stocks
def OnData(self, data):
# Rebalance monthly
if self.Time < self.rebalance_time:
return
self.rebalance_time = self.Time + timedelta(30)
# Liquidate positions not in value stocks
for symbol in self.Portfolio.Keys:
if symbol not in self.value_stocks:
self.Liquidate(symbol)
# Invest equally in value stocks
for symbol in self.value_stocks:
if self.Securities[symbol].Invested:
continue
self.SetHoldings(symbol, 1 / len(self.value_stocks))