- ago
Hello,

What would the best implementation approach be for a rotational strategy to not just select the top X symbols of a particular dataset but also to then filter those picks to one stock symbol per stock sector that is the highest weight per the strategy?

My guess would be to manually split the dataset into each sector, and code the strategy accordingly, but I am curious if there is an easier approach as this seems difficult.

Thanks,
Eric
0
1,227
Solved
28 Replies

Reply

Bookmark

Sort
- ago
#1
Hi Eric,

Since this goes beyond the capability of Rotation strategy, manually coding your sector rotation logic is the way to go. You can take this code as example:

Converting a WL6 rotation script to WL7
0
Cone8
 ( 24.56% )
- ago
#2
I made a Version 6 strategy called, "Best stocks in best performing sectors" but it relied on GICS data that was available in Wealth-Lab Pro. However, if you've got the symbols organized by sector in DataSets, we could probably work with that to convert the script. For the sector strength proxy it used ETFs - XLB, XLE, XLF, etc.
0
- ago
#3
QUOTE:
a Version 6 strategy called, "Best stocks in best performing sectors" but it relied on GICS data that was available in Wealth-Lab Pro.

You could convert that WL6 strategy to use NAICS sector/industry codes (instead of GICS codes), which are available from IQFeed. That might be best.
0
Cone8
 ( 24.56% )
- ago
#4
Sounds reasonable. If someone can come up with ETF proxies for the NAICS like these for the GICS, I'll translate the script. https://www.naics.com/search/#naics

CODE:
         //GICS          sectors.Add("XLE", "10"); // Energy          sectors.Add("XLB", "15"); // Materials          sectors.Add("XLI", "20"); // Industrials          sectors.Add("XLY", "25"); // Consumer Discretionary          sectors.Add("XLP", "30"); // Consumer Staples          sectors.Add("XLV", "35"); // Health Care          sectors.Add("XLF", "40"); // Financials          sectors.Add("XLK", "45"); // I.T.          sectors.Add("IXP", "50"); // Telecomm Services          sectors.Add("XLU", "55"); // Utilities          //NAICS ???
The description says the script was based off an idea from Stan Weinstein's "Secrets for Profiting in Bull and Bear Markets".

I wish I knew if the current market was a bull or a bear - most of my account positions say bear!
0
- ago
#5
QUOTE:
If someone can come up with ETF proxies for the NAICS like these for the GICS,...

I didn't think ETFs had either NAICS or GICS codes associated with them. I thought these classification codes were only defined for stocks.

I thought this problem was only about stocks. I didn't know ETFs were involved.
0
Cone8
 ( 24.56% )
- ago
#7
GICS doesn't have associated ETFs either. The strategy uses ETFs as proxies to measure sector strength vs. a broader market benchmark, SPY. Then, the strongest stocks from those sectors are selected for the rotation.
0
- ago
#8
QUOTE:
The strategy uses ETFs as proxies to measure sector strength ...

In that case, I wouldn't be using ETFs for that. I would use the Fidelity Select Sector funds as proxies instead to measure sector strength because they are "actively managed".

Could I ask, why not scrape Fidelity's website to determine which sector is strongest? They have ETF pseudo funds broken down by GICS code that gauge sector strength. https://eresearch.fidelity.com/eresearch/markets_sectors/sectors/si_performance.jhtml?tab=siperformance

So is the problem more about how to convert GICS classifications to NAICS classifications? I don't think there's a one-to-one conversion, but one can take the NAICS classifications on https://www.naics.com/search/ and "kind of" make an association with the two code classification methods. Hasn't someone already done an "approximate" code conversion like this (say at the sector level)?
0
- ago
#9
If you guys agree on something robust and simple I could look into the parser side of things?
0
Cone8
 ( 24.56% )
- ago
#10
QUOTE:
Could I ask, why not scrape Fidelity's website to determine which sector is strongest?
Works for today, but not for a backtest.

Re: Fidelity Select
It doesn't matter to me what's used as proxies. I just wanted someone else to tell me what they are and I'll translate the code. If the topic starter is interested, that's a good task for them. Of course, if you don't have IQFeed, you'll need to find a source for NAICS too.
0
- ago
#11
QUOTE:
Works for today, but not for a backtest.

Are you saying the backtester needs historical sector performance data in order to do the simulation? And WL6 Pro had the Standard and Poors historical ETF, pseudo-funds performance data to perform the backtest simulation. Is there another option without buying an S&P subscription to the historical pseudo-fund performance data? Are we back to using the Fidelity Select Sector funds for historical sector performance without the S&P data subscription?

I agree the poster could help with how to assign the number system conversion from GICS to NAICS. Maybe a web search might reveal a conversion implementation. And the poster never said if he's using IQFeed or plans to. He needs to respond.

I suppose you could have IndexLab convert the historical Fidelity Select Sector funds into NAICS-numbered indices to be used for the sector-oriented backtest simulation. Sounds scary. Could Wealth-Data create and manage the NAICS-numbered indices instead of IndexLab? (Sounds like a feature request.) This will only work with Daily bars. One is still going to need a IQFeed subscription.
0
Cone8
 ( 24.56% )
- ago
#12
QUOTE:
Are you saying the backtester needs historical sector performance data in order to do the simulation?
I'm saying that the strategy we're talking about needs it (explained strategy rules in Post #7).

QUOTE:
And WL6 Pro had the Standard and Poors historical ETF...
I don't know what you're talking about!

QUOTE:
I suppose you could have IndexLab convert the historical Fidelity Select Sector funds into NAICS-numbered indices to be used for the sector-oriented backtest simulation.
Sure, that would work. But it'd be a lot easier just to use sector ETFs.

QUOTE:
Could Wealth-Data create and manage the NAICS-numbered indices instead of IndexLab? (Sounds like a feature request.)
I think not; it's way out of scope for Wealth-Data.
0
- ago
#13
QUOTE:
But it'd be a lot easier just to use sector ETFs.

I like easy. I think getting IndexLab involved if we don't need to is over complicating the problem. So let's go with the sector ETFs. Would these sector ETFs be available in WealthData or Q-Data as "NAICS-coded" (or "named") indices?

And the poster never said if he's using IQFeed or plans to so he has access to NAICS classification codes for each stock. He needs to respond.

--
Off topic I know, but can the IQFeed provider cache the NAICS code for each stock so our sector strategy can work in off-line mode? (not really a requirement though)
0
Cone8
 ( 24.56% )
- ago
#14
QUOTE:
can the IQFeed provider cache the NAICS code for each stock so our sector strategy can work in off-line mode? (not really a requirement though)
Sure, it does. It's part of the fundamental string and it works precisely like it did for WL6. https://www2.wealth-lab.com/wl5wiki/IQFeedProvider.ashx

(I know, this documentation didn't make it to WL7 yet, but no one has missed it until now either.)

CODE:
public override void Initialize(BarHistory bars) {          int code = WealthLab.IQFeed.IQFundamental.ValueInt(bars.Symbol, "NAICS");          WriteToDebugLog(String.Format("{0} NAICS Code:\t{1}", bars.Symbol, code)); }


1
- ago
#15
Sorry for the late response, I do utilize IQFeed.
0
- ago
#16
As for the strategy, I wouldn't want to rank the sectors by performance, merely rank the top stocks by the strategy and only take 1 stock per sector (highest weight in each sector).
0
- ago
#17
QUOTE:
I wouldn't want to rank the sectors by performance, merely rank the top stocks by the strategy and only take 1 stock per sector ...

So even if a sector is falling, you would still want to buy a stock out of that sector? That doesn't sound like a very popular strategy to me. I'm not interested anymore.

One of the greatest new features of WL7 is PreExecute{} where, in this case, one would be ranking the best sectors to pick stocks from. But you're not interested in this new feature. And it's this new WL7 feature that interests me the most.
0
Cone8
 ( 24.56% )
- ago
#18
I don't mind creating an example strategy for this, but I don't want to get in a back-and-forth about requirements. eralbanese, I need some specific requirements, like how many or which sectors (there are no fewer than 20 NAICS sectors), how are the stocks ranked in a sector?, how often is the rotation, and anything else you can think of.
0
- ago
#19
QUOTE:
So even if a sector is falling, you would still want to buy a stock out of that sector? That doesn't sound like a very popular strategy to me. I'm not interested anymore.


Let me further elaborate. The rotational strategy I currently utilize doesn't include any sector performance metrics in its weighting as it is a quick turnaround (1-2 buy/sell) strategy. The first iteration I want to test is picking the highest weighted stocks (1 per sector) to smooth out the equity/dd curve and to not overload myself with 2-3 stocks in each buy period that *could* be in the same sector.

After I test that, I am completely open to your idea to test for market strength and then pick my top X stocks (1 per sector) over the top X sectors that have the highest weight accordingly.

@cone

Sample Strategy for ease of creation:

Number of Stocks: 10
Weighting: Highest Volume
Rotation: Daily
All Primary NAICS Sectors (20).

For this sample strategy, we would buy the top 10 stocks (10% equity) with the highest volume but filtered by NAICS Sectors (The top 1 stock in each sector with the highest volume) and then sell the following day. If that stock still has the highest volume in that sector, we do not sell and hold until it isn't the highest weight in that sector.


0
- ago
#20
Cone, does this give you everything you need?
0
Cone8
 ( 24.56% )
- ago
#21
Sorry! I didn't see your reply since our notfications weren't working until just this week :/

QUOTE:
The first iteration I want...
That's already sounding like more than one strategy.

Okay, the highest volume per sector should be easy enough. Let's start there.
1
Cone8
 ( 24.56% )
- ago
#22
Here's the daily rotation by highest volume - like any rotation strategy, it's easy to modify the indicator used.

The debug output will indicate if a symbol is missing a code and therefore won't be included. I tested this with the current Nasdaq 100 list, which seems to cover 16 of the 20 sectors.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.IQFeed; using System.Linq; namespace WealthScript123 {    public class HighestIndicatorInSectorRotation : UserStrategyBase    {       public override void Initialize(BarHistory bars)       {          StartIndex = 1;          string naics = Convert.ToString(WealthLab.IQFeed.IQFundamental.ValueInt(bars.Symbol, "NAICS"));          string sector = "";          if (naics.Length < 2)          {             WriteToDebugLog(bars.Symbol + " - no NAICS code", false);             sector = "0";          }          else             sector = naics.Substring(0, 2);                    bars.UserData = Convert.ToInt32(sector);                }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          base.PreExecute(dt, participants);          _sectorData.Clear();          foreach (BarHistory bh in participants)          {             int idx = GetCurrentIndex(bh);             SectorData sData = new SectorData()             {                Bars = bh,                NaicsSector = bh.UserDataAsInt,                IndicatorValue = bh.Volume[idx]             };             //WriteToDebugLog(dt.ToString("yyyyMMdd") + "\t" + bh.Symbol + "\t" + sData.NaicsSector, false);             if (sData.NaicsSector > 0)                _sectorData.Add(sData);          }          var results = _sectorData.GroupBy(x => x.NaicsSector)              .SelectMany(y => y                 .Where(z => z.IndicatorValue == y.Max(i => i.IndicatorValue)))              .Distinct();                    //WriteToDebugLog(dt.ToString("yyyyMMdd") + "\t" + results.Count() + "\t" + participants.Count, false);          _buys.Clear();          foreach (SectorData sd in results)             _buys.Add(sd.Bars);          _keepers.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                _keepers.Add(p.Symbol);          }                    WriteToDebugLog(dt.ToString("yyyyMMdd") + "\tBuys:" + _buys.Count + "\tKeepers:" + _keepers.Count);                    foreach (SectorData sd in results)          {             if (!_keepers.Contains(sd.Bars.Symbol))             {                               Transaction t = PlaceTrade(sd.Bars, TransactionType.Buy, OrderType.Market, 0, sd.NaicsSector.ToString());             }          }       }       public override void Execute(BarHistory bars, int idx)       {                }              List<string> _keepers = new List<string>();       List<BarHistory> _buys = new List<BarHistory>();       static List<SectorData> _sectorData = new List<SectorData>();    }    class SectorData    {       public BarHistory Bars;       public int NaicsSector;       public double IndicatorValue;    } }
1
- ago
#23
This seems to randomly pick 10 stocks in individual sectors and doesn't weight the stocks first by volume, then filter by sector. How would we add weight to this to make sure that the top volume stocks are looked at from top down and filled into individual sectors?
0
Cone8
 ( 24.56% )
- ago
#24
I'm sure (99.5%) that the code groups by the sector first, and then picks the highest volume stock in each sector. Wasn't that the idea?

QUOTE:
For this sample strategy, we would buy the top 10 stocks (10% equity) with the highest volume but filtered by NAICS Sectors (The top 1 stock in each sector with the highest volume) and then sell the following day. If that stock still has the highest volume in that sector, we do not sell and hold until it isn't the highest weight in that sector.
Yes, should be precisely what this code does.

If you weigh by volume first, and then pick the one with the highest volume in each sector, you'll get the same result anyway.

QUOTE:
weight the stocks first by volume, then filter by sector.
What does this mean exactly? Filter what by sector?
0
- ago
#25
I guess what I am trying to do is to find the top 10 stocks by volume (or any indicator) first and then go down the list 1 by 1 to filter similar sectors out.

For example: Look at the #1 stock by volume and purchase it. If the #2 stock by volume is in the same sector as #1, skip it. If #3 is in the same sector, skip it. Finally, #4 is in a different sector so we purchase it... and so on. Meaning, we may only end up with 3 buys for the days out of 10, if all 10 top volume stocks fell in 3 sectors only for that day.
0
- ago
#26
@Cone. Does this make sense?
0
Cone8
 ( 24.56% )
- ago
#27
Edit:
Here was my response without really having read your detailed requirements. Another version on the way when I get a chance..

Previously ...
Sure, I just got busy with other things and forgot about this one.

Here's a modification that just sorts the previous results by the indicator (volume), descending, then we just pick off the top 10, or _maxCandidates. You can change the number of _maxCandidates using the slider/parameter, and, this will automatically adjust the sizing for to use 100% of the equity. You should use a little margin to avoid NSF Positions.

Check out the debug log, showing the stop 10 symbols and their volume for each bar/date.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.IQFeed; using System.Linq; using System.Text; namespace WealthScript1 {    public class HighestIndicatorInSectorRotation : UserStrategyBase    {       public HighestIndicatorInSectorRotation()       {          AddParameter("Candidates", ParameterTypes.Int32, 10, 1, 20, 1);       }       public override void Initialize(BarHistory bars)       {          StartIndex = 1;          _maxCandidates = Parameters[0].AsInt;          string naics = Convert.ToString(WealthLab.IQFeed.IQFundamental.ValueInt(bars.Symbol, "NAICS"));          string sector = "";          if (naics.Length < 2)          {             WriteToDebugLog(bars.Symbol + " - no NAICS code", false);             sector = "0";          }          else             sector = naics.Substring(0, 2);                    bars.UserData = Convert.ToInt32(sector);                }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          base.PreExecute(dt, participants);          _sectorData.Clear();          foreach (BarHistory bh in participants)          {             int idx = GetCurrentIndex(bh);             SectorData sData = new SectorData()             {                Bars = bh,                NaicsSector = bh.UserDataAsInt,                IndicatorValue = bh.Volume[idx]             };             //WriteToDebugLog(dt.ToString("yyyyMMdd") + "\t" + bh.Symbol + "\t" + sData.NaicsSector, false);             if (sData.NaicsSector > 0)                _sectorData.Add(sData);          }          var results = _sectorData.GroupBy(x => x.NaicsSector)              .SelectMany(y => y                 .Where(z => z.IndicatorValue == y.Max(i => i.IndicatorValue)))                 .Distinct();          //WriteToDebugLog(dt.ToString("yyyyMMdd") + "\t" + results.Count() + "\t" + participants.Count, false);                    // pick the top 10          List<SectorData> list = results.OrderByDescending(x => x.IndicatorValue).ToList();                   _buys.Clear();          _sb.Clear();          for (int n = 0; n < _maxCandidates; n++)          {                         SectorData sd = list[n];             _buys.Add(sd.Bars);             _sb.AppendJoin(':', sd.Bars.Symbol, sd.IndicatorValue.ToString()).Append("; ");          }          WriteToDebugLog(dt.ToShortDateString() + " - " + _sb.ToString(), false);                    _keepers.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                _keepers.Add(p.Symbol);          }                    WriteToDebugLog(dt.ToString("yyyyMMdd") + "\tBuys:" + _buys.Count + "\tKeepers:" + _keepers.Count);                    for (int n = 0; n < _maxCandidates; n++)          {             SectorData sd = list[n];             if (!_keepers.Contains(sd.Bars.Symbol))             {                               Transaction t = PlaceTrade(sd.Bars, TransactionType.Buy, OrderType.Market, 0, sd.NaicsSector.ToString());                //adjust sizing to use all equity for the number of candidates in rotation                t.Quantity = Math.Floor(CurrentEquity / _maxCandidates / sd.Bars.Close[GetCurrentIndex(sd.Bars)]);             }          }       }       public override void Execute(BarHistory bars, int idx)       {                }              List<string> _keepers = new List<string>();       List<BarHistory> _buys = new List<BarHistory>();       static List<SectorData> _sectorData = new List<SectorData>();       int _maxCandidates = 0;       StringBuilder _sb = new StringBuilder();    }    class SectorData    {       public BarHistory Bars;       public int NaicsSector;       public double IndicatorValue;    } }
0
Cone8
 ( 24.56% )
- ago
#28
Final cut!
Debug log displays the symbol-sector-volume selections from the top-10 volume stocks in the DataSet for each date.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.IQFeed; using System.Linq; using System.Text; namespace WealthScript7 {    public class HighestIndicatorInSectorRotation : UserStrategyBase    {       public HighestIndicatorInSectorRotation()       {          AddParameter("Candidates", ParameterTypes.Int32, 10, 1, 20, 1);       }       public override void Initialize(BarHistory bars)       {          StartIndex = 1;          _maxCandidates = Parameters[0].AsInt;          string naics = Convert.ToString(WealthLab.IQFeed.IQFundamental.ValueInt(bars.Symbol, "NAICS"));          string sector = "";          if (naics.Length < 2)          {             WriteToDebugLog(bars.Symbol + " - no NAICS code", false);             sector = "0";          }          else             sector = naics.Substring(0, 2);                    bars.UserData = Convert.ToInt32(sector);                }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          base.PreExecute(dt, participants);          _sectorData.Clear();          foreach (BarHistory bh in participants)          {             int idx = GetCurrentIndex(bh);             SectorData sData = new SectorData()             {                Bars = bh,                NaicsSector = bh.UserDataAsInt,                IndicatorValue = bh.Volume[idx]             };             //WriteToDebugLog(dt.ToString("yyyyMMdd") + "\t" + bh.Symbol + "\t" + sData.NaicsSector, false);             if (sData.NaicsSector > 0)                _sectorData.Add(sData);          }          List<SectorData> results = _sectorData.OrderByDescending(i => i.IndicatorValue).ToList();          results.RemoveRange(10, results.Count - 10);                    // use the top 10 in volume, pick 1 from each sector          _buys.Clear();          _sb.Clear();          _sectorCodes.Clear();          for (int n = 0; n < 10; n++)          {                         SectorData sd = results[n];             if (!_sectorCodes.Contains(sd.NaicsSector))             {                _buys.Add(sd.Bars);                _sectorCodes.Add(sd.NaicsSector);                _sb.AppendJoin('-', sd.Bars.Symbol, sd.NaicsSector, sd.IndicatorValue.ToString()).Append("; ");             }          }          WriteToDebugLog(dt.ToShortDateString() + "\t" + _sb.ToString(), false);                    _keepers.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                _keepers.Add(p.Symbol);          }                    WriteToDebugLog(dt.ToString("yyyyMMdd") + "\tBuys:" + _buys.Count + "\tKeepers:" + _keepers.Count);                    for (int n = 0; n < _maxCandidates; n++)          {             SectorData sd = results[n];             if (!_keepers.Contains(sd.Bars.Symbol))             {                               Transaction t = PlaceTrade(sd.Bars, TransactionType.Buy, OrderType.Market, 0, sd.NaicsSector.ToString());             }          }                }       public override void Execute(BarHistory bars, int idx)       {                }              List<string> _keepers = new List<string>();       List<BarHistory> _buys = new List<BarHistory>();       static List<SectorData> _sectorData = new List<SectorData>();       int _maxCandidates = 0;       StringBuilder _sb = new StringBuilder();       List<int> _sectorCodes = new List<int>();    }    class SectorData    {       public BarHistory Bars;       public int NaicsSector;       public double IndicatorValue;    } }


0
Best Answer

Reply

Bookmark

Sort