I'm using the Nasdaq 100 stocks as my dataset, where I use four long positions as long as a condition exists. Once the four open positions have been sold, I want to use the available capital to switch to SQQQ. Conversely, if four buy signals for Nasdaq 100 stocks occur, I want SQQQ to be sold. SQQQ buy/sell signals aren't working and I wonder what where's the problem here?
CODE:
public class MyStrategy : UserStrategyBase { private BarHistory qqqBars, sqqqBars; private IndicatorBase sma200; private string hedgeSymbol = "SQQQ"; public MyStrategy() : base() { StartIndex = 200; } public override void Initialize(BarHistory bars) { qqqBars = GetHistory(bars, "QQQ"); sma200 = new SMA(qqqBars.Close, 200); PlotIndicator(sma200, WLColor.Green, default, default, "QQQPane"); var sqqqRaw = GetHistoryUnsynched(hedgeSymbol, HistoryScale.Daily); sqqqBars = BarHistorySynchronizer.Synchronize(sqqqRaw, bars); } private bool AllStockPositionsClosedToday(int idx) { var positionsToday = OpenPositionsAllSymbols.Where(p => p.Symbol != hedgeSymbol && p.IsOpen).ToList(); var positionsYesterday = Backtester.Positions.Where(p => p.Symbol != hedgeSymbol && p.EntryBar <= idx - 1 && (p.ExitBar == -1 || p.ExitBar > idx - 1)).ToList(); return positionsYesterday.Count == 4 && positionsToday.Count == 0; } public override void Execute(BarHistory bars, int idx) { if (idx >= sqqqBars.Count || idx >= qqqBars.Count) return; bool shouldHedgeToday = AllStockPositionsClosedToday(idx); var mainPosition = FindOpenPosition(0); var sqqqPosition = OpenPositionsAllSymbols.Find(p => p.Symbol == hedgeSymbol && p.IsOpen); bool priceAboveSma = qqqBars.Close[idx] > sma200[idx]; bool priceBelowSma = qqqBars.Close[idx] < sma200[idx]; if (mainPosition == null && priceAboveSma) { var t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Stocks"); t.Weight = 1; if (sqqqPosition != null) ClosePosition(sqqqPosition, OrderType.Market, 0, $"Sell {hedgeSymbol}"); } else if (mainPosition != null && priceBelowSma) { ClosePosition(mainPosition, OrderType.Market, 0, "Sell Stocks"); if (shouldHedgeToday && sqqqPosition == null) { double sqqqPrice = sqqqBars.Close[idx]; double capital = CurrentEquity; int shares = (int)(capital / sqqqPrice); if (shares > 0) { WriteToDebugLog($"{bars.DateTimes[idx]:yyyy-MM-dd} | BUY {hedgeSymbol} with {shares} shares at {sqqqPrice:F2}"); PlaceTrade(sqqqBars, TransactionType.Buy, OrderType.Market, 0, $"Buy {hedgeSymbol}").Quantity = shares; } } } } }
Rename
Start with AllStockPositionsClosedToday():
1. I don't know if it's a factor, but you're not filtering out NSF positions.
2. You shouldn't rely on the index of any symbol's backtest being the same as the index of the Backtester. Instead, use p.Entry/ExitDate instead of p.Entry/ExitBar.
1. I don't know if it's a factor, but you're not filtering out NSF positions.
2. You shouldn't rely on the index of any symbol's backtest being the same as the index of the Backtester. Instead, use p.Entry/ExitDate instead of p.Entry/ExitBar.
Not sure, but isn't
CODE:the right choice to check for any open positions?
OpenPositions.Where(p => p.Symbol != "SQQQ" && p.IsOpen).ToList()
CODE:
private bool AllMainPositionsClosedToday(DateTime currentDate) { var mainPositionsToday = OpenPositions.Where(p => p.Symbol != "SQQQ" && p.IsOpen).ToList(); currentMainPositionCount = mainPositionsToday.Count; bool shouldHedge = previousMainPositionCount == 1 && currentMainPositionCount == 0; if ((previousMainPositionCount == 1 && currentMainPositionCount == 0) || (previousMainPositionCount == 0 && currentMainPositionCount == 1)) { WriteToDebugLog($"=== Hedge Check for {currentDate:yyyy-MM-dd} ==="); WriteToDebugLog($"π Open Positions yesterday: {previousMainPositionCount}"); WriteToDebugLog($"π Open Positions today: {currentMainPositionCount}"); if (previousMainPositionCount == 1 && currentMainPositionCount == 0) WriteToDebugLog("π Buy SQQQ"); else if (previousMainPositionCount == 0 && currentMainPositionCount == 1) WriteToDebugLog("π Sell SQQQ"); } previousMainPositionCount = currentMainPositionCount; return shouldHedge; }
OpenPositions returns NSF positions too. If you only want to consider what's part of the backtest, remove the NSFs.
This isn't good. Remember what I said about indexes?
CODE:
OpenPositions.Where(p => p.Symbol != "SQQQ" && p.IsOpen && !p.NSF).ToList();
This isn't good. Remember what I said about indexes?
CODE:
if (idx >= sqqqBars.Count || idx >= qqqBars.Count) return;
I wonder if it makes sense to use the PreExecute method to evaluate the Nasdaq 100 stocks dataset for buys and sells, then generate a list that is updated historically, so that I can check at the end in the Execute method whether the state of the list has been changed from yesterday to today? Or is there a simpler way to process this mix (Nasdaq 100 stocks dataset combined with a single Symbol like SQQQ)?
CODE:
private static List<BarHistory> buys = new List<BarHistory>(); private BarHistory stockIndexBars, preciousMetalBars; private List<string> yesterdayBuys = new List<string>(); public override void PreExecute(DateTime dt, List<BarHistory> participants) { buys.Clear(); int spyIdx = GetCurrentIndex(stockIndexBars); int gldIdx = GetCurrentIndex(preciousMetalBars); (bool buyCondition, bool sellCondition) = GetBuySellConditions(spyIdx, gldIdx); foreach (BarHistory bh in participants) { int idx = GetCurrentIndex(bh); if (idx < 2) continue; SMA volSma; if (!bh.Cache.TryGetValue("VolumeWeight", out object cachedWeight)) { volSma = new SMA(bh.Volume, 20); bh.Cache["VolumeWeight"] = volSma; } else { volSma = (SMA)cachedWeight; } double volScore = volSma[idx]; bh.UserData = volScore; if (buyCondition) { buys.Add(bh); } } buys = buys.OrderByDescending(b => b.UserDataAsDouble).Take(4).ToList(); } public override void Execute(BarHistory bars, int idx) { bool isInBuyList = buys.Contains(bars); string today = bars.DateTimes[idx].ToString("yyyy-MM-dd"); if (bars.Symbol == BacktestData[0].Symbol) { List<string> currentBuySymbols = buys.Select(b => b.Symbol).ToList(); // for logging purposes only, no trading is conducted if (!currentBuySymbols.SequenceEqual(yesterdayBuys)) { if (currentBuySymbols.Count > 0) WriteToDebugLog($"π₯ {today}: Top 4 stocks to buy: {string.Join(", ", currentBuySymbols)}"); if (yesterdayBuys.Count > 0 && currentBuySymbols.Count == 0) WriteToDebugLog($"π {today}: Sold stocks yesterday: {string.Join(", ", yesterdayBuys)}"); yesterdayBuys = new List<string>(currentBuySymbols); } } if (!HasOpenPosition(bars, PositionType.Long) && isInBuyList) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market); if (bars.Cache.TryGetValue("VolumeWeight", out object cachedWeight)) { var volSma = (SMA)cachedWeight; t.Weight = volSma[idx]; } } else if (HasOpenPosition(bars, PositionType.Long) && !isInBuyList) { PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } }
The idea of ββthe PreExecute method was indeed the simplest way to determine when the entire dataset had been processed and to decide, whether to use SQQQ or not.
CODE:
// === Stock list from current trading day (used for hedge logic) === private static List<BarHistory> stocksInList = new List<BarHistory>(); public override void PreExecute(DateTime dt, List<BarHistory> participants) { stocksInList = new List<BarHistory>(participants); } // === Main Logic === public override void Execute(BarHistory bars, int idx) { var today = bars.DateTimes[idx]; // Check if current symbol is the last processed symbol today bool isLastSymbol = stocksInList.Count > 0 && bars.Symbol == stocksInList[^1].Symbol; if (isLastSymbol) WriteToDebugLog($"π {today:yyyy-MM-dd} | Last Symbol today: {bars.Symbol} (isLastSymbol = true)"); (bool buyCondition, bool sellCondition) = GetBuySellConditions(idx); var longPosition = FindOpenPosition(0); var hedgePosition = OpenPositionsAllSymbols.Find(p => p.Symbol == hedgeSymbol && p.IsOpen); // Open long position when buy condition is met and no position exists if (longPosition == null && buyCondition) { var t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 0, "Buy 4 Stocks"); t.Weight = volumeWeight[idx]; // If hedge was previously active, close it if (hedgePosition != null) ClosePosition(hedgePosition, OrderType.Market, 0, $"Sell {hedgeSymbol}"); } // Close long position and open hedge if sell condition is met else if (longPosition != null && sellCondition) { ClosePosition(longPosition, OrderType.Market, 0, "Sell 4 Stocks"); if (isLastSymbol && hedgePosition == null) { int shares = CalculateHedgePositionSize(CurrentEquity, hedgeBars.Close[idx]); if (shares > 0) { WriteToDebugLog($"β {today:yyyy-MM-dd} | BUY {hedgeSymbol} with {shares} shares at {hedgeBars.Close[idx]:F2}"); PlaceTrade(hedgeBars, TransactionType.Buy, OrderType.Market, 0, $"Buy {hedgeSymbol}").Quantity = shares; } } } }
Your Response
Post
Edit Post
Login is required