Overall Statistics |
Total Orders
142674
Average Win
0.01%
Average Loss
-0.01%
Compounding Annual Return
11.216%
Drawdown
45.800%
Expectancy
0.279
Start Equity
100000
End Equity
1404212.48
Net Profit
1304.212%
Sharpe Ratio
0.52
Sortino Ratio
0.511
Probabilistic Sharpe Ratio
3.039%
Loss Rate
22%
Win Rate
78%
Profit-Loss Ratio
0.64
Alpha
0.033
Beta
0.66
Annual Standard Deviation
0.119
Annual Variance
0.014
Information Ratio
0.246
Tracking Error
0.077
Treynor Ratio
0.093
Total Fees
$2695.88
Estimated Strategy Capacity
$22000.00
Lowest Capacity Asset
JWSM XN4UG0ZA25WL
Portfolio Turnover
1.10%
|
# https://quantpedia.com/strategies/low-volatility-factor-effect-in-stocks-long-only-version/ # # The investment universe consists of global large-cap stocks (or US large-cap stocks). At the end of each month, the investor constructs # equally weighted decile portfolios by ranking the stocks on the past three-year volatility of weekly returns. The investor goes long # stocks in the top decile (stocks with the lowest volatility). # # QC implementation changes: # - Top quartile (stocks with the lowest volatility) is fundamental instead of decile. #region imports from AlgorithmImports import * import numpy as np from typing import List, Dict #endregion class LowVolatilityFactorEffectStocks(QCAlgorithm): def Initialize(self) -> None: self.SetStartDate(2000, 1, 1) self.SetCash(100_000) self.symbol: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.period: int = 12 * 21 self.fundamental_count: int = 3000 self.quantile: int = 4 self.leverage: int = 10 self.data: Dict[Symbol, SymbolData] = {} self.long: List[Symbol] = [] self.selection_flag: bool = True self.UniverseSettings.Resolution = Resolution.Daily self.Settings.MinimumOrderMarginPortfolioPercentage = 0. self.settings.daily_precise_end_time = False self.AddUniverse(self.FundamentalSelectionFunction) self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection) def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(self.leverage) def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]: # Update the rolling window every day. for stock in fundamental: symbol: Symbol = stock.Symbol # Store daily price. if symbol in self.data: self.data[symbol].update(stock.AdjustedPrice) if not self.selection_flag: return Universe.Unchanged fundamental: List[Fundamental] = [ x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.MarketCap != 0 ] if len(fundamental) > self.fundamental_count: fundamental = sorted(fundamental, key = lambda x: x.MarketCap, reverse=True)[:self.fundamental_count] # Warmup price rolling windows. weekly_vol: Dict[Symbol, float] = {} for stock in fundamental: symbol: Symbol = stock.Symbol if symbol not in self.data: self.data[symbol] = SymbolData(self.period) history: DataFrame = self.History(symbol, self.period, Resolution.Daily) if history.empty: self.Log(f"Not enough data for {symbol} yet.") continue closes: pd.Series = history.loc[symbol].close for time, close in closes.items(): self.data[symbol].update(close) if self.data[symbol].is_ready(): weekly_vol[symbol] = self.data[symbol].volatility() if len(weekly_vol) >= self.quantile: # volatility sorting sorted_by_vol: List[Tuple] = sorted(weekly_vol.items(), key = lambda x: x[1], reverse = True) quantile: int = int(len(sorted_by_vol) / self.quantile) self.long = [x[0] for x in sorted_by_vol[-quantile:]] return self.long def OnData(self, data: Slice) -> None: if not self.selection_flag: return self.selection_flag = False # trade execution invested: List[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in invested: if symbol not in self.long: self.Liquidate(symbol) for symbol in self.long: if symbol in data and data[symbol]: self.SetHoldings(symbol, 1. / len(self.long)) self.long.clear() def Selection(self) -> None: self.selection_flag = True class SymbolData(): def __init__(self, period: int) -> None: self.price: RollingWindow = RollingWindow[float](period) def update(self, value: float) -> None: self.price.Add(value) def is_ready(self) -> bool: return self.price.IsReady def volatility(self) -> float: closes: List[float] = [x for x in self.price] # Weekly volatility calc. separete_weeks: List[float] = [closes[x:x+5] for x in range(0, len(closes), 5)] weekly_returns: List[float] = [(x[0] - x[-1]) / x[-1] for x in separete_weeks] return np.std(weekly_returns) # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee: fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))