| Overall Statistics |
|
Total Orders 2370 Average Win 2.29% Average Loss -0.90% Compounding Annual Return 33.236% Drawdown 64.700% Expectancy 0.980 Start Equity 100000 End Equity 124362204.69 Net Profit 124262.205% Sharpe Ratio 0.881 Sortino Ratio 1.229 Probabilistic Sharpe Ratio 15.951% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 2.54 Alpha 0.225 Beta 0.362 Annual Standard Deviation 0.273 Annual Variance 0.074 Information Ratio 0.691 Tracking Error 0.285 Treynor Ratio 0.663 Total Fees $16329798.27 Estimated Strategy Capacity $12000.00 Lowest Capacity Asset ARTL X5IQJTEKSAJP Portfolio Turnover 0.92% |
from AlgorithmImports import *
import numpy as np
class NetCurrentAssetValueEffect(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2000, 1, 1)
#self.set_end_date(2019, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 1.5
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.settings.daily_precise_end_time = False
self.fundamental_count: int = 3_000
self.market: str = 'usa'
self.country_id: str = 'USA'
self.fin_sector_code: int = 103
self.ncav_threshold: float = 1.50
self.illiquid_market_cap_threshold: float = 0.5e9
self.long_symbols: List[Symbol] = []
self.rebalance_months: List[int] = [1, 4, 7, 10]
self.selection_flag: bool = True
self.spy = self.AddEquity('SPY', Resolution.Daily).Symbol
self.spy_sma = self.SMA(self.spy, 200, Resolution.Daily)
self.Schedule.On(self.DateRules.MonthStart(self.spy), self.TimeRules.AfterMarketOpen(self.spy), self.Selection)
def FundamentalFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag or self.IsSPYBelow200SMA():
return Universe.Unchanged
filtered: List[Fundamental] = [f for f in fundamental if f.HasFundamentalData
and f.Market == self.market
and f.CompanyReference.CountryId == self.country_id
and f.AssetClassification.MorningstarSectorCode != self.fin_sector_code
and not np.isnan(f.EarningReports.BasicAverageShares.TwelveMonths)
and f.EarningReports.BasicAverageShares.TwelveMonths != 0
and not np.isnan(f.MarketCap)
and f.MarketCap != 0
and f.MarketCap <= 0.2e9
and f.ValuationRatios.WorkingCapitalPerShare != 0
and f.Volume > 0 # Filter out stocks with no volume data
]
# No more filtering based on stock splits
sorted_by_market_cap: List[Fundamental] = sorted(filtered, key=lambda f: f.MarketCap, reverse=True)[:self.fundamental_count]
self.long_symbols = [x.Symbol for x in sorted_by_market_cap if ((x.ValuationRatios.WorkingCapitalPerShare * x.EarningReports.BasicAverageShares.TwelveMonths) / x.MarketCap) > self.ncav_threshold]
return self.long_symbols
def OnData(self, slice: Slice) -> None:
if not self.selection_flag or self.IsSPYBelow200SMA():
return
self.selection_flag = False
portfolio: List[PortfolioTarget] = [PortfolioTarget(symbol, 1 / len(self.long_symbols)) for symbol in self.long_symbols if slice.ContainsKey(symbol) and slice[symbol] is not None]
self.SetHoldings(portfolio, True)
self.long_symbols.clear()
def Selection(self) -> None:
if self.Time.month in self.rebalance_months:
self.selection_flag = True
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetLeverage(1.5)
security.SetFeeModel(RealisticFeeModel(self.illiquid_market_cap_threshold))
if self.IsSPYBelow200SMA():
self.Liquidate()
def IsSPYBelow200SMA(self) -> bool:
if not self.spy_sma.IsReady:
return False
return self.Securities[self.spy].Price < self.spy_sma.Current.Value
class RealisticFeeModel(FeeModel):
def __init__(self, illiquid_market_cap_threshold):
self.illiquid_market_cap_threshold = illiquid_market_cap_threshold
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
market_cap = parameters.Security.Fundamentals.MarketCap
quantity = parameters.Order.AbsoluteQuantity
price = parameters.Security.Price
per_share_fee = 0.005
min_fee = 1.00
illiquid_fee = 0.01
if market_cap <= self.illiquid_market_cap_threshold:
fee = price * quantity * illiquid_fee
else:
fee = max(quantity * per_share_fee, min_fee)
return OrderFee(CashAmount(fee, "USD"))