- ago
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;                }             }          }       }    }
0
244
5 Replies

Reply

Bookmark

Sort
Cone8
 ( 2.73% )
- ago
#1
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.
0
- ago
#2
Not sure, but isn't
CODE:
OpenPositions.Where(p => p.Symbol != "SQQQ" && p.IsOpen).ToList()
the right choice to check for any open positions?

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;       }
0
Cone8
 ( 2.73% )
- ago
#3
OpenPositions returns NSF positions too. If you only want to consider what's part of the backtest, remove the NSFs.
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;
0
- ago
#4
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);    } }
0
- ago
#5
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;          }       }    } }
0

Reply

Bookmark

Sort