Overall Statistics |
Total Orders
590
Average Win
0.02%
Average Loss
-0.02%
Compounding Annual Return
-0.035%
Drawdown
2.700%
Expectancy
-0.143
Start Equity
100000
End Equity
99131.53
Net Profit
-0.868%
Sharpe Ratio
-5.514
Sortino Ratio
-0.418
Probabilistic Sharpe Ratio
0.000%
Loss Rate
59%
Win Rate
41%
Profit-Loss Ratio
1.07
Alpha
-0.022
Beta
0.001
Annual Standard Deviation
0.004
Annual Variance
0
Information Ratio
-0.405
Tracking Error
0.159
Treynor Ratio
-16.956
Total Fees
$9.54
Estimated Strategy Capacity
$7000.00
Lowest Capacity Asset
PEBK R735QTJ8XC9X
Portfolio Turnover
0.02%
|
# 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.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"))