How can I make that a backtesting system only chooses the stocks with the 50 highest market capitalisation in the Nasdaq 100 ?
Rename
It's a bit tricky but doable. You should install the Fundamental extension first: https://www.wealth-lab.com/extension/detail/Fundamental
Then check YCharts or Morningstar on the Event Providers tab in the Data Manager, and there also check the items "shares_outstanding" or "Shares" (respectively). We will use this to build the market capitalization as time series (price * outstanding shares).
Leveraging @Cone's example, we can rank the DataSet to only buy symbols if they're in Top-50 by their market cap.
Here's quick and untested strategy. Its first run will take some time to collect the data from the fundamental feed but subsequents runs will be faster due to caching.
Then check YCharts or Morningstar on the Event Providers tab in the Data Manager, and there also check the items "shares_outstanding" or "Shares" (respectively). We will use this to build the market capitalization as time series (price * outstanding shares).
Leveraging @Cone's example, we can rank the DataSet to only buy symbols if they're in Top-50 by their market cap.
Here's quick and untested strategy. Its first run will take some time to collect the data from the fundamental feed but subsequents runs will be faster due to caching.
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Linq; using System.Collections.Generic; namespace WealthScript1 { public class RankByMarketCap : UserStrategyBase { private static List<BarHistory> _buys = new List<BarHistory>(); private int _topNStocks = 50; public RankByMarketCap() { AddParameter("Top-N Stocks", ParameterType.Int32, 50, 5, 95, 5); } public override void Initialize(BarHistory bars) { _topNStocks = Parameters[0].AsInt; } public override void PreExecute(DateTime dt, List<BarHistory> participants) { int candidates = (int)Math.Truncate(participants.Count * _topNStocks / 100d); //loop through the Nasdaq 100 DataSet and save the market cap value to bars.UserData for sorting foreach (BarHistory bars in participants) { //build market capitalization time series TimeSeries marketCap = null, fEPS = null; if (bars.EventDataPoints.Count > 0) { List<EventDataPoint> fList, fList2; // Earnings Per Share (quarterly) - YCharts fList = bars.EventDataPoints.Where(i => i.Value > 0 && i.Name == "shares_outstanding" && i.ProviderName == "YCharts").ToList(); // Earnings Per Share (annual) - Morningstar fList2 = bars.EventDataPoints.Where(i => i.Value > 0 && i.Name == "Shares" && i.ProviderName == "Morningstar").ToList(); if (fList.Count > 0) fEPS = Fundamental.Series(bars, "shares_outstanding", true); else if (fList2.Count > 0) fEPS = Fundamental.Series(bars, "Shares", true); marketCap = fEPS * bars.Close; } int idx = GetCurrentIndex(bars); if(marketCap != null) bars.UserData = -marketCap[idx]; } //sorts market cap from higher to lower participants.Sort((b, a) => b.UserDataAsDouble.CompareTo(a.UserDataAsDouble)); //add the stocks with the highest market cap to the list _buys.Clear(); _buys.AddRange(participants.GetRange(0, candidates)); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { // only buy this one if it's in the _buys list if (_buys.Contains(bars) && !double.IsNaN((double)bars.UserData)) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, bars.UserData.ToString()); } } else { if (!_buys.Contains(bars)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } } }
Thank You !
I get this error while running the code:
PreExecute Exception (21.01.2022) Line 50 - Object reference not set to an instance of an object.
"Line 50 = marketCap = fEPS * bars.Close;"
I get this error while running the code:
PreExecute Exception (21.01.2022) Line 50 - Object reference not set to an instance of an object.
"Line 50 = marketCap = fEPS * bars.Close;"
Looks like the fundamental series haven't populated, hence the error in fEPS. The code assumes YCharts being selected so it's important to follow this precisely:
Then check YCharts or Morningstar on the Event Providers tab in the Data Manager, and there also check the items "shares_outstanding" or "Shares" (respectively). We will use this to build the market capitalization as time series (price * outstanding shares).
And then uncomment the appropriate variables in this code block (i.e. fList/fList2...) depending on what event provider is chosen in the Data Manager:
Then check YCharts or Morningstar on the Event Providers tab in the Data Manager, and there also check the items "shares_outstanding" or "Shares" (respectively). We will use this to build the market capitalization as time series (price * outstanding shares).
And then uncomment the appropriate variables in this code block (i.e. fList/fList2...) depending on what event provider is chosen in the Data Manager:
CODE:
// Earnings Per Share (annual) - Morningstar ...
Also it's possible that when running on the W-D DataSet the fundamental data will not be found for some (delisted) symbols. I just made an update to the code above to try and protect from this.
Now I get the same error at line 54:
"double rs = marketCap[idx];"
"double rs = marketCap[idx];"
Apparently the fundamental items aren't being requested from the event provider (YCharts) for some reason?
I updated the code above again, making it print out the market cap value at the entry bar to the Entry Signal column. Just ran the code on Dow 30 (Wealth-Data) with YCharts enabled and could clearly see the values being pumped. So it works for me.
Now the code runs without errors.
But with Nasdaq 100 also stocks with Market Capitalisation lower as Top 50 are choosen.
But with Nasdaq 100 also stocks with Market Capitalisation lower as Top 50 are choosen.
FAST and CDNS are not in the Top 50.
The entry signal for all your stocks is -1 which means in this context that the outstanding shares value is missing i.e. it wasn't pulled from the event provider. So the strategy's results will not be correct.
With both event providers (and their correspoding fundamental items) enabled, try to update the DataSet in the Data Manager first. The backtest strategy should have done that automatically but for whatever reason the data isn't there. It's free data so anything could happen.
With both event providers (and their correspoding fundamental items) enabled, try to update the DataSet in the Data Manager first. The backtest strategy should have done that automatically but for whatever reason the data isn't there. It's free data so anything could happen.
With additional Morningstar it shows Entry-Signals.
But it shows only stocks in the bottom 50-100 Rank of the Market Capitalisation.
But it shows only stocks in the bottom 50-100 Rank of the Market Capitalisation.
OK the code above was sorting from the bottom. Updated the code once again, please retry.
Note that Morningstar's data is annualized, YCharts is quarterly and thus is preferred.
Note that Morningstar's data is annualized, YCharts is quarterly and thus is preferred.
Errors with the latest code.
The strategy backtest seems to have finished with signals and results. These are warnings for the delisted symbols.
Thank You !
Now it seems that the Top 50 Market Cap are choosen.
What I would like to do with the Top 50 is to make a portfolio of 10 stocks out of the Top 50.
It should symply choose the Top 10 of the ROC 3 Month at the end of the month and rebalance monthly.
Do you think it would be possible to integrate this into the code ?
Now it seems that the Top 50 Market Cap are choosen.
What I would like to do with the Top 50 is to make a portfolio of 10 stocks out of the Top 50.
It should symply choose the Top 10 of the ROC 3 Month at the end of the month and rebalance monthly.
Do you think it would be possible to integrate this into the code ?
QUOTE:
What I would like to do with the Top 50 is to make a portfolio of 10 stocks out of the Top 50.
It should symply choose the Top 10 of the ROC 3 Month at the end of the month and rebalance monthly.
I'm not seeing the connection between the market cap ranking that this code does and the 3-month ROC rotation. You can implement the latter idea simply in a Rotation strategy without coding.
This simple monthly ROC System should not choose from the Nasdaq 100 but instead choose from the Top 50.
It's clear now but it's a bit late to request such drastic change in the code when it's ready. 🤷♂️
Is it possible ?
One way or another it should be possible. It occurred to me that an elegant solution may be to create a DataSet Provider that would rank stocks based on some fundamenal item like market cap. Wealth-Lab has an API for that: https://www.wealth-lab.com/Support/ExtensionApi/DataSetProvider
The way I envision potential feature to be working is much like our automatically updatable DataSets for upcoming events and stock screening: https://www.wealth-lab.com/extension/detail/DataExtensions#screenshots
But obviously it would more involved than a Strategy and require a formal #FeatureRequest.
The way I envision potential feature to be working is much like our automatically updatable DataSets for upcoming events and stock screening: https://www.wealth-lab.com/extension/detail/DataExtensions#screenshots
But obviously it would more involved than a Strategy and require a formal #FeatureRequest.
Eugene's code just needed another tweak - see if this works. Run it on Monthly bars adjust parameters if required. Sizing is set in the script - just set the Staring Capital.
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Linq; using System.Collections.Generic; namespace WealthScript1 { public class RankByMarketCap : UserStrategyBase { private static List<BarHistory> _buys = new List<BarHistory>(); private static List<String> _openSymbols = new List<string>(); private int _topNStocks = 50; private int _holds = 10; public RankByMarketCap() { AddParameter("Top-N Stocks", ParameterType.Int32, 50, 5, 95, 5); AddParameter("Symbols to Hold", ParameterType.Int32, 10, 2, 25, 1); } public override void Initialize(BarHistory bars) { StartIndex = 2; _topNStocks = Parameters[0].AsInt; _holds = Parameters[1].AsInt; } public override void PreExecute(DateTime dt, List<BarHistory> participants) { int candidates = (int)Math.Truncate(participants.Count * _topNStocks / 100d); //loop through the Nasdaq 100 DataSet and save the market cap value to bars.UserData for sorting foreach (BarHistory bars in participants) { //build market capitalization time series TimeSeries marketCap = null, fEPS = null; if (bars.EventDataPoints.Count > 0) { List<EventDataPoint> fList, fList2; // Earnings Per Share (quarterly) - YCharts fList = bars.EventDataPoints.Where(i => i.Value > 0 && i.Name == "shares_outstanding" && i.ProviderName == "YCharts").ToList(); // Earnings Per Share (annual) - Morningstar fList2 = bars.EventDataPoints.Where(i => i.Value > 0 && i.Name == "Shares" && i.ProviderName == "Morningstar").ToList(); if (fList.Count > 0) fEPS = Fundamental.Series(bars, "shares_outstanding", true); else if (fList2.Count > 0) fEPS = Fundamental.Series(bars, "Shares", true); marketCap = fEPS * bars.Close; } int idx = GetCurrentIndex(bars); if(marketCap != null) bars.UserData = marketCap[idx]; } //sorts market cap from higher to lower participants.Sort((a, b) => b.UserDataAsDouble.CompareTo(a.UserDataAsDouble)); //add the stocks with the highest market cap to the list _buys.Clear(); _buys.AddRange(participants.GetRange(0, candidates)); foreach (BarHistory bh in participants) bh.UserData = -1e9; // Get the 3-Month ROC and sort again foreach (BarHistory bh in _buys) { int bar = GetCurrentIndex(bh); double roc = ROC.Series(bh.Close, 3)[bar]; bh.UserData = roc; } participants.Sort((a, b) => b.UserDataAsDouble.CompareTo(a.UserDataAsDouble)); _buys.Clear(); _buys.AddRange(participants.GetRange(0, _holds)); string buySymbols = string.Join(", ", _buys.Select(x => x.Symbol)); WriteToDebugLog(dt.ToShortDateString() + "\tBuys: " + buySymbols); // Assumes run on monthly data // Exits _openSymbols.Clear(); foreach (Position p in OpenPositionsAllSymbols) { //sell positions that are not in the _buys list if (!_buys.Contains(p.Bars)) PlaceTrade(p.Bars, TransactionType.Sell, OrderType.Market); else _openSymbols.Add(p.Symbol); } // Entries foreach (BarHistory bars in _buys) { if (!_openSymbols.Contains(bars.Symbol) && !double.IsNaN((double)bars.UserData)) { //using the Transaction object, perform the sizing based on the number of _holds Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, bars.UserData.ToString()); t.Quantity = Math.Floor(CurrentEquity / _holds / bars.Close[GetCurrentIndex(bars)]); } } } public override void Execute(BarHistory bars, int idx) { //no-ops } } }
*If* you are subscribed to IQFeed data, their base-subscription service publishes the "shares outstanding" for the most recent quarter. It's somewhat more reliable than the YCharts free-data version. Since this example is out of library code, an "optional" try-catch block has been include (which you can omit).
CODE:
using WealthLab.IQFeed; //* IQFeed IQFundamental class here for "shares outstanding" *// ... double sharesOutstanding = 0.0; try { sharesOutstanding = IQFundamental.ValueDouble(bars.Symbol, "Common shares outstanding") * 1000.0; } catch (Exception ex) { WLHost.Instance.AddLogItem("WLTrading", "Couldn’t fetch \"shares outstanding\" for " + bars.Symbol, WLColor.Yellow, ex); }
Your Response
Post
Edit Post
Login is required