| Overall Statistics |
|
Total Orders 1797 Average Win 1.07% Average Loss -0.28% Compounding Annual Return 73.165% Drawdown 37.400% Expectancy 1.706 Start Equity 10000 End Equity 207204.93 Net Profit 1972.049% Sharpe Ratio 1.538 Sortino Ratio 2.088 Probabilistic Sharpe Ratio 81.370% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 3.78 Alpha 0.435 Beta 0.914 Annual Standard Deviation 0.334 Annual Variance 0.112 Information Ratio 1.463 Tracking Error 0.292 Treynor Ratio 0.562 Total Fees $3998.92 Estimated Strategy Capacity $8000.00 Lowest Capacity Asset LFAC WVP9292ZBFXH Portfolio Turnover 1.14% |
# Import all the required libraries and functionalities from QuantConnect
from AlgorithmImports import *
import numpy as np
# Define the main algorithm class that extends QCAlgorithm
class NetCurrentAssetValueEffect(QCAlgorithm):
def Initialize(self) -> None:
# Initialization and configuration of the algorithm
self.SetStartDate(2020, 1, 1) # Set the backtest start date
#self.SetEndDate(2010, 1, 1) # Optionally set an end date for the backtest
self.SetCash(10000) # Set the initial capital for the algorithm
# Set universe settings
self.UniverseSettings.Leverage = 1.5 # Define the leverage for the securities
self.UniverseSettings.Resolution = Resolution.Daily # Use daily data resolution
# Configure universe selection based on a custom fundamental function
self.AddUniverse(self.FundamentalFunction)
# Set some portfolio and algorithm settings
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.Settings.daily_precise_end_time = False
# Variables for filtering and selecting securities
self.fundamental_count = 3000 # Maximum number of securities to consider
self.market = 'usa' # Market filter for USA
self.country_id = 'USA' # Country ID filter for USA
self.fin_sector_code = 103 # Exclude financial sector based on sector code
self.ncav_threshold = 1.75 # Threshold for Net Current Asset Value
self.illiquid_market_cap_threshold = 0.5e9 # Market cap threshold for liquidity
# List to store selected long symbols
self.long_symbols = []
# Define the months when rebalancing should occur
self.rebalance_months = [1, 4, 7, 10] # Rebalance in January, April, July, and October
# A flag to trigger universe selection
self.selection_flag = True
# Add benchmark securities (SPY and GLD)
self.spy = self.AddEquity('SPY', Resolution.Daily).Symbol # S&P 500 ETF
self.gld = self.AddEquity('GLD', Resolution.Daily).Symbol # Gold
# Calculate 100-day and 200-day SMAs for SPY
self.spy_sma_100 = self.SMA(self.spy, 100, Resolution.Daily)
self.spy_sma_200 = self.SMA(self.spy, 200, Resolution.Daily)
self.previous_crossover = None # Variable to track the previous crossover state
# Warm up the data to initialize indicators
self.SetWarmUp(200) # Warm-up period of 200 days to ensure SMAs are ready
# Schedule events for rebalancing
self.Schedule.On(
self.DateRules.MonthStart(self.spy),
self.TimeRules.AfterMarketOpen(self.spy),
self.Selection
) # Schedule rebalancing at the start of the defined months
# Perform an initial selection at the start of the algorithm
self.Schedule.On(
self.DateRules.Today,
self.TimeRules.AfterMarketOpen(self.spy),
self.Selection
)
def FundamentalFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
# Return an unchanged universe if selection is not needed
if not self.selection_flag:
return Universe.Unchanged
# Filter out securities based on various fundamental criteria
filtered = [
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 <= self.illiquid_market_cap_threshold
and f.ValuationRatios.WorkingCapitalPerShare != 0
and f.Volume > 0 # Ensure stocks have volume data
]
# Sort the filtered securities by market cap in descending order and take the top fundamental_count
sorted_by_market_cap = sorted(filtered, key=lambda f: f.MarketCap, reverse=True)[:self.fundamental_count]
# Select securities with a Net Current Asset Value (NCAV) ratio above the threshold
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:
# Ensure SMAs are ready
if self.IsWarmingUp:
return
# Check if SMAs are ready
if not (self.spy_sma_100.IsReady and self.spy_sma_200.IsReady):
return
# Check for SMA crossovers
current_crossover = self.spy_sma_100.Current.Value < self.spy_sma_200.Current.Value
# If the 100 SMA crosses below the 200 SMA, invest fully in GLD
if current_crossover and (self.previous_crossover is None or not self.previous_crossover):
self.SetHoldings(self.gld, 1.0)
# If the 100 SMA crosses above the 200 SMA, exit GLD
elif not current_crossover and (self.previous_crossover is None or self.previous_crossover):
self.Liquidate(self.gld)
# Update the previous crossover state
self.previous_crossover = current_crossover
# If selection is not needed, exit the method
if not self.selection_flag:
return
# Set the flag to False after selection is processed
self.selection_flag = False
# Create portfolio targets for each selected symbol and set holdings
portfolio = [
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) # Adjust holdings in the portfolio
self.long_symbols.clear() # Clear the list of long symbols
def Selection(self) -> None:
# Rebalance if it's one of the rebalance months or at the start date
if self.Time.month in self.rebalance_months or self.Time == self.StartDate:
self.selection_flag = True
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
# Set leverage and fee model for newly added securities
for security in changes.AddedSecurities:
security.SetLeverage(1.5)
security.SetFeeModel(InteractiveBrokersFeeModel())
# Custom fee model for Interactive Brokers
class InteractiveBrokersFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
# Calculate commission based on Interactive Brokers' fee structure
commission_per_share = 0.005 # $0.005 per share
min_commission = 1.00 # Minimum commission per trade
max_commission = 0.5 / 100 * parameters.Order.AbsoluteQuantity * parameters.Security.Price # 0.5% of trade value
# Determine the final commission amount
commission = max(min_commission, min(commission_per_share * parameters.Order.AbsoluteQuantity, max_commission))
return OrderFee(CashAmount(commission, "USD"))