bcdxxx8
 ( 0.00% )
- ago
Hey all,
Trying to automate options trading for capital efficiency, but encountering a few problems...
Code I used will be below. I turned off entry criteria for timing, so that I could just try to see if I could enter. I'm primarily using Schwab for streaming data (I didn't try IB yet, or a combination of IB stream with Schwab historical). Was testing on NVDA, since it's a highly liquid underlying and options ticker.
1) Couldn't execute trades on a WL paper account - resulted in error, I believe because Schwab as data provider couldn't provide a live price to the option, so no orders were executed. (Broker message - Could not obtain quote for NVDA 260116C00189000)
2) So I switched to taking the plunge and trading on a live account (didn't feel like turning on the IBKR paper account). Learned at 3:59pm trying to troubleshoot this that for some reason, through this API, they don't accept all orders trying to pinch pennies (ie $7.525 was not allowable). (Broker message - Held until 4:15 PM (local). Option order price rounded to nearest 0.05 (not a penny pilot). Schwab Order Status = REJECTED)
3) Then after changing my limit order and filtering to the nearest $0.05, was able to get filled on buying options. And buying more options. And more. The system didn't recognize that it had options in the account that I now want to sell/exit. While that outcome could be handy in certain situations (price improvement and averaging down if certain signals hit, like a Turtle Trader strategy), that was not the intention here, and that behavior could be coded if desired.
4) I tried turning on "Use Live Positions" (Preferences -> Trading -> Portfolio Sync), and tried testing after hours as a manual "run now, auto-stage" (had to leave the computer for the rest of the day), and now the system wants to sell my NVDA long stock, and still can't find any of my many options positions (both manually placed and WL-placed).

Am I missing something blatantly obvious as I'm working late into the night?

Thanks for any help.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using System.Linq; using WealthLab.Schwab; using WealthLab.InteractiveBrokers; // Added for flexibility namespace WealthScript9 {    public class SchwabPutStrategy30PctProfit2 : UserStrategyBase    {       private BarHistory _obars;       private double _profitTargetPct = 0.30; // 30% Profit Target       private double _targetDelta = -0.40; // Target Delta for Puts       public override void Initialize(BarHistory bars)       {          PlotTimeSeries(bars.Close, "Price", "PricePane", WLColor.Black);       }       public override void Execute(BarHistory bars, int idx)       {          var allPositions = GetPositionsAllSymbols();          bool hasOpenPos = false;          foreach (Position pos in allPositions)          {             // Check if this position is an option for the current symbol (e.g. starts with "SPY")             // and is currently Open.             if (pos.IsOpen && (pos.Symbol == bars.Symbol || pos.Symbol.StartsWith(bars.Symbol)))             {                hasOpenPos = true;                if (pos.PositionType == PositionType.Long)                {                   // Calculate 30% Profit Target                   double profitPrice = pos.EntryPrice * (1.0 + _profitTargetPct);                   // Round the sell price to nearest 0.05 to ensure valid tick                   profitPrice = Math.Round(profitPrice / 0.05) * 0.05;                   ClosePosition(pos, OrderType.Limit, profitPrice, $"Exit @ {profitPrice:F2} (+30%)");                }             }          }          if (hasOpenPos)             return;          if (idx != bars.Count - 1)             return;          DateTime today = bars.DateTimes[idx];          double uPrc = bars.Close[idx];          // --- Expiry Search Window (36 to 44 days) ---          List<OptionGreek> chain = null;          DateTime selectedExpiry = DateTime.MinValue;          // Loop through days 36 to 44 to find the first valid expiration with data          for (int daysOut = 36; daysOut <= 44; daysOut++)          {             DateTime potentialExpiry = today.AddDays(daysOut);             // Option A: Schwab Data             chain = SchwabHistorical.Instance.GetOptionChainSnapshot(bars.Symbol, potentialExpiry, OptionType.Put, uPrc * 0.8, uPrc * 1.2);             // Option B: IBKR Data (Use this if Schwab data is failing in paper trading)             // chain = IBHistorical.Instance.GetOptionChainSnapshot(bars.Symbol, potentialExpiry, OptionType.Put, uPrc * 0.8, uPrc * 1.2);             if (chain != null && chain.Count > 0)             {                selectedExpiry = potentialExpiry;                WriteToDebugLog($"Found valid expiry at {daysOut} days out: {selectedExpiry:yyyy-MM-dd}");                break; // Stop looking, we found one             }          }          if (chain != null && chain.Count > 0)          {             // Find Put closest to -0.40 Delta             OptionGreek bestOption = chain                .OrderBy(o => Math.Abs(o.Delta - _targetDelta))                .FirstOrDefault();             if (bestOption != null)             {                // Calculate Midpoint                double midPrice = (bestOption.Bid + bestOption.Ask) / 2.0;                // Round DOWN to nearest $0.05                // Example: 7.525 -> 7.50                double limitPrice = Math.Floor(midPrice / 0.05) * 0.05;                WriteToDebugLog($"Selected: {bestOption.Symbol} | Delta: {bestOption.Delta:F2} | Mid: {midPrice:F3} | Limit: {limitPrice:F2}");                // Create Synthetic History                _obars = OptionSynthetic.GetHistory(bars, bestOption.Symbol, bestOption.IV);                _obars.Symbol = bestOption.Symbol;                // Place Trade                Transaction t = PlaceTrade(_obars, TransactionType.Buy, OrderType.Limit, limitPrice, "Buy Put Limit");                t.Quantity = 1;                // Send Email Notification                string subject = $"Trade Alert: Buy {bestOption.Symbol}";                string body = $"Placed Buy Limit for {bestOption.Symbol} at {limitPrice:C2}.\n" +                          $"Underlying: {bars.Symbol} @ {uPrc:C2}\n" +                          $"Delta: {bestOption.Delta:F2}\n" +                          $"Expiry: {selectedExpiry:yyyy-MM-dd}\n" +                          $"Target Sell Price: {(limitPrice * (1.0 + _profitTargetPct)):C2}";                try                {                   /* Fix this later                   SendEmail(_emailAddress, subject, body);                   WriteToDebugLog($"Email sent to {_emailAddress}");                   */                }                catch (Exception ex)                {                   WriteToDebugLog($"Failed to send email: {ex.Message}");                }             }             else             {                WriteToDebugLog("No option found matching Delta -0.40 criteria.");             }          }          else          {             WriteToDebugLog("No option chain found in the 36-44 day window.");          }       }    } }


And here's a purely sell program, which also can't find my options positions:
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using System.Linq; using WealthLab.Schwab; using WealthLab.InteractiveBrokers; // Added for flexibility namespace WealthScriptPutStrategy {    public class SchwabOptionSellStrategy30Pct : UserStrategyBase    {       private double _profitTargetPct = 0.30; // 30% Profit Target       public override void Initialize(BarHistory bars)       {          PlotTimeSeries(bars.Close, "Price", "PricePane", WLColor.Black);       }       public override void Execute(BarHistory bars, int idx)       {          // Only execute logic on the last bar (current live bar)          if (idx != bars.Count - 1) return;          // 1. Fetch ALL positions in the account/backtest          var allPositions = GetPositionsAllSymbols();          // DEBUGGING: Explicitly log what we see          if (allPositions.Count > 0)             WriteToDebugLog($"Scanning {allPositions.Count} total positions in strategy context...");          else             WriteToDebugLog("No positions found in GetPositionsAllSymbols(). Verify Strategy Monitor Sync.");          foreach (Position pos in allPositions)          {             // DEBUGGING: Log every symbol we encounter to see format             // WriteToDebugLog($"Found Position: {pos.Symbol} | Type: {pos.PositionType} | Open: {pos.IsOpen}");             // Filter 1: Check if the position is OPEN             if (!pos.IsOpen) continue;             // Filter 2: Check if the position is LONG (We own the option)             if (pos.PositionType != PositionType.Long) continue;             // Filter 3: Check matching Underlying using Helper (More Robust than StartsWith)             // This extracts "NVDA" from "NVDA 250117P..." reliably             string posUnderlier = OptionsHelper.SymbolUnderlier(pos.Symbol);             if (posUnderlier != bars.Symbol)                continue;             // Filter 4: Check if it is an Option (Any Type: Call or Put)             if (OptionsHelper.IsOption(pos.Symbol))             {                // --- LOGIC: Place 30% Profit Target ---                // Calculate 30% Profit Target based on Entry Price                double profitPrice = pos.EntryPrice * (1.0 + _profitTargetPct);                // Round the sell price to nearest 0.05 to ensure valid tick                profitPrice = Math.Round(profitPrice / 0.05) * 0.05;                // Place the Limit Order to Sell                ClosePosition(pos, OrderType.Limit, profitPrice, $"Exit {pos.Symbol} @ {profitPrice:F2} (+30%)");                WriteToDebugLog($"MATCH FOUND: Managing Exit for {pos.Symbol}: Entry {pos.EntryPrice:F2} -> Target {profitPrice:F2}");             }          }       }    } }

Thanks for any insight anyone can give...
0
60
4 Replies

Reply

Bookmark

Sort
Glitch8
 ( 11.27% )
- ago
#1
Let's see if WL8 is seeing all of your open positions first of all. If you open the Accounts tool in WL8, do you see the expected positions listed in your Schwab account(s)? As far as Live Positions, it should be used only if you're trading regime manages a single position per symbol.
0
bcdxxx8
 ( 0.00% )
- ago
#2
Thanks Glitch. Yes, on the account manager tool, I see all of my equity and options positions on Schwab, IBKR, and wl paper (well, I haven't gotten any options executed in wl paper yet, as I mentioned), including the WL executed put options and manually executed calls on Schwab and IBKR. Also, is there a persistent means to label executed trades that will have a sticky label on a position (like entry price) to say what strategy resulted in the entry, despite being across multiple trading days, WL closing and reopening, system reboots, etc? Or is that only a back testing function?
0
Cone8
 ( 21.02% )
- ago
#3
It probably doesn't matter for the purpose you're using it for here (a limit price based on the midPrice from the option chain), but to get the proper scale for the synthetic history, you need to divide an IV in % by 100, i.e., 15% should be entered at 0.15.
CODE:
// Create Synthetic History _obars = OptionSynthetic.GetHistory(bars, bestOption.Symbol, bestOption.IV); // SHOULD BE: _obars = OptionSynthetic.GetHistory(bars, bestOption.Symbol, bestOption.IV / 100.0);
0
bcdxxx8
 ( 0.00% )
- ago
#4
Thanks Cone for the correction. Little things like that are easy to miss, and make a huge impact (though likely not in this case, but will be diligent about that one in the future).

So I was finally able to trigger a sell on the live option contract parsing through the account. Also, given that the debuglog is not accessible in the Strategy Manager, I had to create my own debuglog as a csv file of sorts in c:\temp. Here is my code:
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using System.Linq; using WealthLab.Schwab; using WealthLab.InteractiveBrokers; using System.IO; namespace WealthScriptNVDAExitByBcdxxx {    public class Schwab_NVDA_Option_Seller : UserStrategyBase    {       private double _profitTargetPct = 0.30; // 30% Profit Target       private string _targetUnderlier = "NVDA";       public override void Initialize(BarHistory bars)       {          // Just a placeholder plot          PlotTimeSeries(bars.Close, "Price", "PricePane", WLColor.Black);       }       public override void Execute(BarHistory bars, int idx)       {          // Only run on the very last bar (Live/Current)          if (idx != bars.Count - 1) return;          // -------------------------------------------------------------          // 1. SETUP & CSV PREP          // -------------------------------------------------------------          string dir = @"C:\temp";          string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");          string filePath = Path.Combine(dir, $"{_targetUnderlier}_Active_Options_{timestamp}.csv");          try          {             if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);             using (StreamWriter sw = new StreamWriter(filePath))             {                // --- SECTION 1: DUMP ALL ACTIVE POSITIONS ---                sw.WriteLine("--- ALL ACTIVE POSITIONS ---");                sw.WriteLine("Symbol,Qty,Type,CostBasis,EntryDate,DaysHeld,DaysToExp,Underlying");                BrokerAccount liveAcct = Backtester.LiveAccount;                if (liveAcct == null)                {                   WriteToDebugLog("Error: No Live Account Attached. Running in Backtest Mode?");                   sw.WriteLine("ERROR: No Live Account Found.");                   return;                }                List<BrokerPosition> nvdaOptionsToClose = new List<BrokerPosition>();                List<BrokerPosition> shortOptionsToClose = new List<BrokerPosition>();                foreach (BrokerPosition pos in liveAcct.Positions)                {                   // Helper variables                   string sym = pos.Symbol;                   string underlier = OptionsHelper.SymbolUnderlier(sym);                   bool isOption = OptionsHelper.IsOption(sym);                   DateTime expiry = isOption ? OptionsHelper.SymbolExpiry(sym) : DateTime.MaxValue;                   double daysToExp = isOption ? (expiry - DateTime.Now).TotalDays : 0;                   // Handle Date Logic (EntryDate is nullable)                   string entryDateStr = pos.EntryDate.HasValue ? pos.EntryDate.Value.ToString("yyyy-MM-dd") : "N/A";                   double daysHeld = pos.EntryDate.HasValue ? (DateTime.Now - pos.EntryDate.Value).TotalDays : 0;                   // Write to CSV                   sw.WriteLine($"{sym},{pos.Quantity},{pos.PositionType},{pos.BasisPrice:F2},{entryDateStr},{daysHeld:F1},{daysToExp:F1},{underlier}");                   // --- SECTION 2: FILTER FOR NVDA OPTIONS ---                   if (isOption && underlier == _targetUnderlier)                   {                      // Logic: Prioritize Short positions (spreads)                      if (pos.PositionType == PositionType.Short)                      {                         shortOptionsToClose.Add(pos);                      }                      // Logic: Identify Long Puts for 30% Profit Exit                      else if (pos.PositionType == PositionType.Long)                      {                         // Specifically check if it is a PUT                         if (OptionsHelper.SymbolOptionType(sym) == OptionType.Put)                         {                            nvdaOptionsToClose.Add(pos);                         }                      }                   }                }                sw.WriteLine(); // Blank Lines                sw.WriteLine();                sw.WriteLine("--- ACTION ITEMS (ORDERS GENERATED) ---");                sw.WriteLine("Action,Symbol,LimitPrice,Reason");                // -------------------------------------------------------------                // 2. GENERATE ORDERS                // -------------------------------------------------------------                // A. Handle Short Positions First (Safety Priority)                // If you want to auto-close shorts, uncomment logic here.                // For now, just logging them as priority alerts.                foreach (var shortPos in shortOptionsToClose)                {                   sw.WriteLine($"ALERT (Short Held),{shortPos.Symbol},N/A,Close Manually or Add Logic");                   WriteToDebugLog($"ALERT: Holding Short Option {shortPos.Symbol}. Ensure spreads are managed.");                }                // B. Handle Long Puts (30% Profit Target)                foreach (var longPut in nvdaOptionsToClose)                {                   // Calculate Target                   double entryPrice = longPut.BasisPrice;                   double targetPrice = entryPrice * (1.0 + _profitTargetPct);                   // Round to nearest 0.05                   targetPrice = Math.Round(targetPrice / 0.05) * 0.05;                   // Create Synthetic BarHistory to place the trade                   // We need this because 'PlaceTrade' requires a BarHistory object to attach the order to.                   // We use a dummy IV (0.50) since we are placing a Limit order based on price, not IV.                   BarHistory optBars = OptionSynthetic.GetHistory(bars, longPut.Symbol, 0.50);                   optBars.Symbol = longPut.Symbol; // Critical: Ensure symbol matches exactly                   // Place the Sell Order                   // Note: Using TransactionType.Sell because we are Long                   Transaction t = PlaceTrade(optBars, TransactionType.Sell, OrderType.Limit, targetPrice, $"Exit 30% Profit");                   t.Quantity = longPut.Quantity; // Close entire position                   // Log Action                   string logMsg = $"PLACING SELL: {longPut.Symbol} | Entry: {entryPrice:F2} | Target: {targetPrice:F2}";                   WriteToDebugLog(logMsg);                   sw.WriteLine($"SELL LIMIT,{longPut.Symbol},{targetPrice:F2},Target Hit (30%)");                }             }             WriteToDebugLog($"Processing Complete. Log saved to: {filePath}");          }          catch (Exception ex)          {             WriteToDebugLog($"Critical Error: {ex.Message}");          }       }    } }
0

Reply

Bookmark

Sort