- ago
I have a c# strategy that trades external symbols. When I run the strategy with "Run backtest" from strategy window, it generate correct orders. However when the strategy is run in SM by using "Run Now", I always miss limit profit taking exits. The screenshot gives an example: the profit taking exit for CHRO and BCTX are not present when running the strategy in SM. Any idea what's wrong here?

Run backtest in strategy window:


Run now in SM:
0
697
Solved
7 Replies

Reply

Bookmark

Sort
Cone8
 ( 3.70% )
- ago
#1
It's hard to say why you're getting that result. Why does NVNI only have a Limit exit in both cases? The logic would tell, so we'd need to see the code and your Trading Preferences.
0
- ago
#2
Here is the code for the strategy. It's not a normal strategy. It reads a csv file with signals which are generated from other methods and place orders or backtests. The reason why NVNI has only profit taking is that the stop loss from the csv is <0 so in the code i ignored it.

CODE:
public class CsvStrategy2 : UserStrategyBase    {       private List<SignalData> _signals = new List<SignalData>();       private Dictionary<object, ExitConditions> _positionExits = new Dictionary<object, ExitConditions>();       private Dictionary<string, BarHistory> _symbolHistories = new Dictionary<string, BarHistory>();       private string _signalFilePath;       public class SignalData       {          public DateTime Date { get; set; }          public string Ticker { get; set; }          public string Action { get; set; } // BTO or STO          public int Shares { get; set; } // renamed from Quantity          public string OrderType { get; set; } // LO or MKT          public double? LimitPrice { get; set; }          public double? ProfitTarget { get; set; } // stored as decimal (1.04 etc)          public double? StopAmount { get; set; } // stored as absolute amount          public DateTime? TimeExit { get; set; } // specific date          public string StrategyName { get; set; } // renamed from Strat       }       public class ExitConditions       {          public double? TakeProfitPct { get; set; }          public double? StopAmount { get; set; } // Changed from StopLossPct          public DateTime? TimeExitDate { get; set; } // Changed from TimeExitDays       }       public CsvStrategy2() : base()       {          StartIndex = 0;          _signalFilePath = Path.Combine(Environment.CurrentDirectory, "csv_strategy.csv");          LoadSignalsFromCsv(_signalFilePath);       }       public override void Initialize(BarHistory bars)       {          PlotStopsAndLimits(3);          // Load histories for all unique symbols in signals          var uniqueSymbols = _signals.Select(s => s.Ticker).Distinct();          foreach (var symbol in uniqueSymbols)          {             if (symbol != bars.Symbol) // Don't get history for the primary symbol             {                var history = GetHistory(bars, symbol);                if (history != null)                {                   _symbolHistories[symbol] = history;                   WriteToDebugLog($"Loaded history for {symbol}");                }                else                {                   WriteToDebugLog($"Failed to load history for {symbol}");                }             }          }       }       public override void Execute(BarHistory bars, int idx)       {          var currentDate = bars.DateTimes[idx];          // Process all signals for the current date          var todaySignals = _signals.Where(s => s.Date.Date == currentDate.Date).ToList();          foreach (var signal in todaySignals)          {             // Get the correct BarHistory for this signal             BarHistory targetBars = signal.Ticker == bars.Symbol ?                bars : _symbolHistories.GetValueOrDefault(signal.Ticker);             if (targetBars == null)             {                WriteToDebugLog($"No history found for {signal.Ticker}");                continue;             }             // Check for existing position             Position openPosition = findOpenPositionsForSymbol(signal.Ticker);             if (openPosition == null && (signal.Action == "buy" || signal.Action == "short"))             {                // Entry logic                Backtester.CancelationCode = 1;                var transType = signal.Action == "buy" ? TransactionType.Buy : TransactionType.Short;                var orderType = signal.OrderType.ToLower() == "limit" ? OrderType.Limit : OrderType.Market;                var limitPrice = signal.LimitPrice ?? 0;                Transaction currentTrade = PlaceTrade(targetBars, transType, orderType, limitPrice,                   $"{signal.Action} {signal.OrderType} ({signal.StrategyName})");                currentTrade.Quantity = signal.Shares;                currentTrade.Tag = $"{signal.StrategyName} {signal.Ticker} {currentDate.Date} {currentTrade.TransactionID}";                WriteToDebugLog($"Placed trade for {signal.Ticker} with ID {currentTrade.TransactionID}");                // Store exit conditions                if (currentTrade != null)                {                   // Convert profit target from decimal to percentage                   double? takeProfitPct = signal.ProfitTarget.HasValue ?                      (signal.ProfitTarget.Value - 1) * 100 : null;                   _positionExits[currentTrade.Tag] = new ExitConditions                   {                      TakeProfitPct = takeProfitPct,                      StopAmount = signal.StopAmount,                      TimeExitDate = signal.TimeExit                   };                   WriteToDebugLog($"Stored exit conditions for {signal.Ticker} with ID {currentTrade.TransactionID}, exit conditions: {takeProfitPct}, {signal.StopAmount}, {signal.TimeExit}");                }             }          }          // Check exit conditions for all open positions          foreach (var position in OpenPositionsAllSymbols)          {             if (_positionExits.TryGetValue(position.Tag, out var exitConditions))             {                bool isLong = position.PositionType == PositionType.Long;                var entryPrice = position.EntryPrice;                WriteToDebugLog($"Entry price for {position.Symbol}: {entryPrice}");                // Take profit exit (percentage-based)                if (exitConditions.TakeProfitPct.HasValue)                {                   var takeProfitPrice = entryPrice * (1 + (exitConditions.TakeProfitPct.Value / 100.0) * (isLong ? 1 : -1));                   Backtester.CancelationCode = 1;                   ClosePosition(position, OrderType.Limit, takeProfitPrice, "Take Profit Exit");                }                // Stop loss exit (absolute amount-based)                if (exitConditions.StopAmount.HasValue)                {                   var stopPrice = isLong ?                      entryPrice - exitConditions.StopAmount.Value :                      entryPrice + exitConditions.StopAmount.Value;                   if (stopPrice > 0)                   {                      Backtester.CancelationCode = 1;                      ClosePosition(position, OrderType.Stop, stopPrice, "Stop Loss Exit");                   }                }                // Time-based exit (specific date)                if (exitConditions.TimeExitDate.HasValue &&                   currentDate.Date >= exitConditions.TimeExitDate.Value.Date)                {                   Backtester.CancelationCode = 1;                   ClosePosition(position, OrderType.Market, 0, "Time Exit (Date)");                   _positionExits.Remove(position.Tag);                }             }          }       }       public override void AssignAutoStopTargetPrices(Transaction t, double basisPrice, double executionPrice)       {          // Take profit, get the percentage from the signal          double? takeProfitPct = _positionExits[t.Tag].TakeProfitPct;          if (takeProfitPct.HasValue)          {             double price;             if (t.PositionType == PositionType.Short)             {                price = executionPrice * (1.0 - takeProfitPct.Value / 100.0);             }             else             {                price = executionPrice * (1.0 + takeProfitPct.Value / 100.0);             }             t.AssignAutoProfitTargetPrice(price);          }          // Stop loss          double? stopLossPct = _positionExits[t.Tag].StopAmount;          if (stopLossPct.HasValue)          {             double price;             if (t.PositionType == PositionType.Short)             {                price = executionPrice * (1.0 - stopLossPct.Value / 100.0);             }             else             {                price = executionPrice * (1.0 + stopLossPct.Value / 100.0);             }             t.AssignAutoStopLossPrice(price);          }       }       private void LoadSignalsFromCsv(string filePath)       {          if (!File.Exists(filePath))          {             WriteToDebugLog($"Signal file not found: {filePath}");             return;          }          using (var reader = new StreamReader(filePath))          {             string line;             bool isHeader = true;             while ((line = reader.ReadLine()) != null)             {                if (isHeader) { isHeader = false; continue; }                var parts = line.Split(',');                try                {                   // Debug log to see the actual values                   WriteToDebugLog($"Parsing line with {parts.Length} columns: {line}");                   // Column indices based on CSV format:                   // 0: Date                   // 1: Ticker                   // 2: Action (BTO/STO)                   // 3: Shares                   // 4: Type (LO/MKT)                   // 5: Limit or Stop price                   // 6: Profit Target                   // 7: Stop Loss                   // 8: Time Exit                   // 9: Strategy                   var action = parts[2].Trim().ToUpper();                   var orderType = parts[4].Trim().ToUpper();                   // Parse profit target (now in column 6)                   double? profitTarget = string.IsNullOrEmpty(parts[6].Trim()) ?                      null : double.Parse(parts[6].Trim());                   // Parse stop loss (now in column 7)                   double? stopAmount = null;                   if (!string.IsNullOrEmpty(parts[7].Trim()))                   {                      var stopStr = parts[7].Trim();                      if (stopStr.StartsWith("(") && stopStr.EndsWith(")"))                      {                         stopStr = stopStr.Trim('(', ')');                         stopAmount = double.Parse(stopStr);                      }                   }                   // Parse time exit (now in column 8)                   DateTime? timeExit = null;                   if (!string.IsNullOrEmpty(parts[8].Trim()))                   {                      timeExit = DateTime.Parse(parts[8].Trim());                   }                   var signal = new SignalData                   {                      Date = DateTime.Parse(parts[0].Trim()),                      Ticker = parts[1].Trim(),                      Action = action == "BTO" ? "buy" : "short",                      Shares = int.Parse(parts[3].Trim()),                      OrderType = orderType == "LO" ? "limit" : "market",                      LimitPrice = string.IsNullOrEmpty(parts[5].Trim()) ?                         null : double.Parse(parts[5].Trim()),                      ProfitTarget = profitTarget,                      StopAmount = stopAmount,                      TimeExit = timeExit,                      StrategyName = parts[9].Trim()                   };                   _signals.Add(signal);                   // Debug log for successful parsing                   WriteToDebugLog($"Successfully parsed signal for {signal.Ticker} with action {action}");                }                catch (Exception ex)                {                   WriteToDebugLog($"Error parsing line: {line}");                   WriteToDebugLog($"Detailed error: {ex.Message}");                   WriteToDebugLog($"Stack trace: {ex.StackTrace}");                }             }          }          WriteToDebugLog($"Loaded {_signals.Count} signals from {filePath}");       }       public Position findOpenPositionsForSymbol(string symbol)       {          return OpenPositionsAllSymbols.FirstOrDefault(p => p.Bars.Symbol == symbol);       }    }


Here is a csv to test:

CODE:
Date,Ticker,Action,Shares,Type,Limit or Stop,Profit Target,Stop Loss,Time Exit,Strategy,Notes 2025-02-04,CHRO,BTO,331,LO,1.60,1.04,(1.33),2025-02-12,Strat_6,mr entry 2025-02-04,BCTX,BTO,847,LO,3.81,1.04,(3.08),2025-02-12,Strat_6,mr entry 2025-02-04,CAN,BTO,9966,LO,1.69,1.04,(0.55),2025-02-12,Strat_6,mr entry 2025-02-04,VITL,BTO,775,LO,35.35,1.04,(6.00),2025-02-12,Strat_6,mr entry 2025-02-04,IDAI,BTO,4608,LO,2.93,1.04,(1.18),2025-02-12,Strat_6,mr entry 2025-02-04,INTZ,BTO,2215,LO,1.48,1.04,(0.53),2025-02-12,Strat_6,mr entry 2025-02-04,SNTI,BTO,585,LO,3.59,1.04,(0.88),2025-02-12,Strat_6,mr entry 2025-02-05,SMX,BTO,2800,LO,2.89,1.04,(1.95),2025-02-12,Strat_6,mr entry 2025-02-05,FCUV,BTO,1428,LO,4.63,1.04,(3.58),2025-02-12,Strat_6,mr entry 2025-02-05,NUKK,BTO,705,LO,19.36,1.04,(7.75),2025-02-12,Strat_6,mr entry 2025-02-05,SPCB,BTO,473,LO,9.02,1.04,(5.15),2025-02-12,Strat_6,mr entry 2025-02-05,VITL,BTO,779,LO,35.05,1.04,(5.95),2025-02-12,Strat_6,mr entry 2025-02-05,CAN,BTO,8652,LO,1.68,1.04,(0.53),2025-02-12,Strat_6,mr entry 2025-02-06,NVNI,BTO,1107,LO,4.85,1.04,(4.93),2025-02-14,Strat_6,mr entry 2025-02-06,CYCN,BTO,2479,LO,3.95,1.04,(2.20),2025-02-14,Strat_6,mr entry 2025-02-06,ATCH,BTO,2567,LO,3.34,1.04,(2.13),2025-02-14,Strat_6,mr entry 2025-02-06,BCTX,BTO,1604,LO,3.77,1.04,(3.40),2025-02-14,Strat_6,mr entry 2025-02-06,SPCB,BTO,1355,LO,8.71,1.04,(4.03),2025-02-14,Strat_6,mr entry 2025-02-06,SOPA,BTO,4742,LO,1.93,1.04,(1.15),2025-02-14,Strat_6,mr entry 2025-02-07,NOTV,BTO,2709,LO,3.26,1.04,(1.08),2025-02-17,Strat_6,mr entry 2025-02-07,KAPA,BTO,2217,LO,1.28,1.04,(0.40),2025-02-17,Strat_6,mr entry 2025-02-07,SPCB,BTO,1458,LO,8.15,1.04,(3.75),2025-02-17,Strat_6,mr entry 2025-02-07,SMTC,BTO,336,LO,50.69,1.04,(16.28),2025-02-17,Strat_6,mr entry 2025-02-07,BNRG,BTO,1841,LO,1.03,1.04,(0.50),2025-02-17,Strat_6,mr entry 2025-02-07,ABAT,BTO,7316,LO,1.22,1.04,(0.43),2025-02-17,Strat_6,mr entry 2025-02-07,LUNR,BTO,1042,LO,17.11,1.04,(5.25),2025-02-17,Strat_6,mr entry 2025-02-07,FUBO,BTO,6250,LO,3.67,1.04,(0.88),2025-02-17,Strat_6,mr entry
0
Cone8
 ( 3.70% )
- ago
#3
This one's a mystery for now.
Is there a reason you need to run it in the S. Monitor?
0
- ago
#4
Were you able to reproduce it? I think the logic here is no different than other strategies with stop loss and profit target. I want to be able to auto place the orders at given time each day, there’s another process to to update the csv before that every day.
0
Cone8
 ( 3.70% )
- ago
#5
Yes, I saw it. But that strategy is a lot different than most strategies for which WealthLab manages the BarHistories of participants. Nonetheless, I don't see why it doesn't work and reached a dead end. Glitch said he'd take a look.
0
Cone8
 ( 3.70% )
- ago
#6
Glitch found the bug. Fixed for Build 111.
Thanks for the report!
1
Best Answer
- ago
#7
Great! Thanks
0

Reply

Bookmark

Sort