Hello,
I like the Sample Strategy "Tactical Asset Rotation" as a base for a portfolio rotation system. The system buys always the top 3 stocks (and hence directly sells worse ones).
I would like to extend it with the functionality to not directly sell a stock in case it's not in the buy list (TOP3) anymore, but instead to sell it if the rank of the stock is worse than a specific value. E.g. only if the rank of a stock got worse than 10, it shall get sold and instead a stock with the next best rank in the TOP3 (which has not already a long position) shall get bought.
So in short: stocks to be bought shall always be in the current TOP3; Selling shall only happen when stock is worse then TOP10.
It would be great if you could guide me in the right direction how to realize it.
- E.g. what should be realized in the PreExecute function and what in the Execute function?
- Do I need an extra sells-list additionally to the buys-list?
- Should the PlaceTrace(Buy) being executed in the same Execute()-Run, where the PlaceTrade(Sell) happens? Then the PlaceTrade(Buy) would needed to be called with a BarHistory-parameter different then the 'bars' given by the current Execute()-Run
Some help and guidance would really be appreciated.
Thanks & best regards
Kejo
I like the Sample Strategy "Tactical Asset Rotation" as a base for a portfolio rotation system. The system buys always the top 3 stocks (and hence directly sells worse ones).
I would like to extend it with the functionality to not directly sell a stock in case it's not in the buy list (TOP3) anymore, but instead to sell it if the rank of the stock is worse than a specific value. E.g. only if the rank of a stock got worse than 10, it shall get sold and instead a stock with the next best rank in the TOP3 (which has not already a long position) shall get bought.
So in short: stocks to be bought shall always be in the current TOP3; Selling shall only happen when stock is worse then TOP10.
It would be great if you could guide me in the right direction how to realize it.
- E.g. what should be realized in the PreExecute function and what in the Execute function?
- Do I need an extra sells-list additionally to the buys-list?
- Should the PlaceTrace(Buy) being executed in the same Execute()-Run, where the PlaceTrade(Sell) happens? Then the PlaceTrade(Buy) would needed to be called with a BarHistory-parameter different then the 'bars' given by the current Execute()-Run
Some help and guidance would really be appreciated.
Thanks & best regards
Kejo
Rename
This version eliminates the code in Execute(), moving it all to PreExecute(). There some debug output that keeps track of open positions in the top 10 and shows when a new symbols is added -
CODE:
using System; using WealthLab.Core; using WealthLab.Backtest; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class TacticalAssetRotation_20230402: UserStrategyBase { //declare private variables below private TimeSeries avgROC; private string seriesKey = "Average ROC"; //the list of symbols that we should buy each bar private static List<BarHistory> buys = new List<BarHistory>(); //create the weight indicator and stash it into the BarHistory object for reference in PreExecute public override void Initialize(BarHistory bars) { avgROC = (ROC.Series(bars.Close, 60) + ROC.Series(bars.Close, 120) + ROC.Series(bars.Close, 200)) / 3; bars.Cache[seriesKey] = avgROC; } //this is called prior to the Execute loop, determine which symbols have the lowest average ROC public override void PreExecute(DateTime dt, List<BarHistory> participants) { //store the symbols' AvgROC value in their BarHistory instances foreach (BarHistory bh in participants) { TimeSeries symbolRoc = (TimeSeries)bh.Cache[seriesKey]; int idx = GetCurrentIndex(bh); //this returns the index of the BarHistory for the bar currently being processed double rocVal = symbolRoc[idx]; //if the indicator isn't valid set a high value to avoid selection in rotation if (idx < symbolRoc.FirstValidIndex) rocVal = 1.0e6; bh.UserData = rocVal; //save the current AvgROC value along with the BarHistory instance } // everything is the same in PreExecute up to the .Sort... //sort the participants by AvgROC value (lowest to highest) participants.Sort((a, b) => a.UserDataAsDouble.CompareTo(b.UserDataAsDouble)); buys.Clear(); string msg = ""; // if a symbol in the top 10 match an open symbol, add its BarHistory to buys foreach (Position position in OpenPositionsAllSymbols) { for (int n = 0; n < 10; n++) { if (n >= participants.Count) break; if (participants[n].Symbol == position.Symbol) { buys.Add(participants[n]); msg += " " + position.Symbol + "\t"; } } if (buys.Count >= 3) break; } // if a slot is left, add the highest priority if it's not already in buys (need to check the top 3) for (int n = 0; n < 3; n++) { if (buys.Count >= 3) break; if (n >= participants.Count) break; if (!buys.Contains(participants[n])) { buys.Add(participants[n]); msg += "+" + participants[n].Symbol + "\t"; } } WriteToDebugLog(dt.ToString("yyyy-MM-dd") + "\t" + msg, false); // sell any open symbol not in the buy list foreach (Position pos in OpenPositionsAllSymbols) { if (!buys.Contains(pos.Bars)) ClosePosition(pos, OrderType.Market); } // buy symbols that aren't already open foreach (BarHistory bh in buys) { bool buyIt = true; foreach (Position pos in OpenPositionsAllSymbols) { if (pos.Symbol == bh.Symbol) { buyIt = false; break; } } if (buyIt) PlaceTrade(bh, TransactionType.Buy, OrderType.Market); } } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Your Response
Post
Edit Post
Login is required