I am finding that the IBHistorical.Instance.GetOptionsSymbol() function often returns an invalid symbol. Here's debug log showing that on one intraday bar it worked correctly and on the next it did not.
There is no call strike at 149 in the option chain for this date. (The increment is 2.5). Sometimes it works, sometimes it doesn't. Have you found this as well?
QUOTE:
***** 11/29/2023 11:00:00 AM Get a live symbol for AMZN
AMZN Ki is 23.06
Do loop contract symbol is AMZN240119C147.5
Delta for initial option symbol is 0.5209474853974333
Strike increment is 2.5
Contract price limit reached
It's the last day on 11/29/2023 11:30:00 AM
***** 11/29/2023 11:30:00 AM Get a live symbol for AMZN
AMZN Ki is 23.06
Do loop contract symbol is AMZN240119C149
greek is null for initial option symbol AMZN240119C149; skipping trade
There is no call strike at 149 in the option chain for this date. (The increment is 2.5). Sometimes it works, sometimes it doesn't. Have you found this as well?
Rename
We've been over this before, but the IB Option Chain doesn't guarantee a valid contract. You just get all strikes and dates that are available.
We try to find one quickly around the intended strike, going through a few of the strikes available in the chain. After a few tries to find one that is trading, we give up. We could try a few more times, it's just more delay.
For now, you can also try to poll for data, and if nothing comes back, try again by changing the strike. It's what we do anyway.
---
By the way, Tradier is superior in a contract search. Tradier does provide only valid strikes for each date. Also, we found recently that you can get data for expired contracts back about 1 year - daily, trades only.
We try to find one quickly around the intended strike, going through a few of the strikes available in the chain. After a few tries to find one that is trading, we give up. We could try a few more times, it's just more delay.
For now, you can also try to poll for data, and if nothing comes back, try again by changing the strike. It's what we do anyway.
---
By the way, Tradier is superior in a contract search. Tradier does provide only valid strikes for each date. Also, we found recently that you can get data for expired contracts back about 1 year - daily, trades only.
Sorry to be repetitive with questions. As I get deeper into testing and debugging, I am seeing error patterns more clearly and have more confidence that it may be an external problem, not my coding. Thanks for your confirmation.
I understand that Tradier does not provide the Greeks like IB. Is that correct? My strike selection (from those available) is partially based on Delta.
I understand that Tradier does not provide the Greeks like IB. Is that correct? My strike selection (from those available) is partially based on Delta.
Tradier has Greeks too. But they're updated hourly.
Huh. And the API supports this already? (I thought IB was the only game in town for a complete options trading solution.)
Are there any other caveats you've seen with Tradier greeks? Any upsides (like more lenient about pacing violations)? Does Tradier API have quirks like IB's not returning broker position value directly? Or is it more standard, like TDA?
How can I find out more about the methods I can test this? Are they something like:
using the same variables?
Are there any other caveats you've seen with Tradier greeks? Any upsides (like more lenient about pacing violations)? Does Tradier API have quirks like IB's not returning broker position value directly? Or is it more standard, like TDA?
How can I find out more about the methods I can test this? Are they something like:
CODE:
TradierHistorical.Instance.GetOptionsSymbol()
CODE:
TradierHistorical.Instance.GetGreeks()
using the same variables?
QUOTE:We mentioned Tradier and IQFeed right at the top of the options article -
I thought IB was the only game in town for a complete options trading solution
https://www.wealth-lab.com/blog/backtest-auto-trade-options
IB is the only supported provider with bid/ask intraday data. If you're trading and backtesting options intraday, bid/ask data is superior to trade data, at least for illiquid contracts. And of course, if you prefer Trades, then you can choose that instead with IB.
IQFeed is option-ready too, but IQFeed doesn't have the greeks.
QUOTE:Yes, the only difference is the provider.
How can I find out more about the methods I can test this? Are they something like:
I'm looking for a Tradier Help topic and can't find it! Just inspect the OptionGreek class. Tradier has all the greeks, including rho and phi.
@Cone In Post #1, you wrote:
I didn't realize you had to do so much probing with IB API to get a valid strike. I thought you just give the IB API a few parameters and it would return a valid strike.
I am also probing the option chain to determine the strike increment after the
Does your method resolve the increment as a by-product or a required parameter for the chain you are processing?
QUOTE:
We try to find one quickly around the intended strike, going through a few of the strikes available in the chain. After a few tries to find one that is trading, we give up. We could try a few more times, it's just more delay.
I didn't realize you had to do so much probing with IB API to get a valid strike. I thought you just give the IB API a few parameters and it would return a valid strike.
I am also probing the option chain to determine the strike increment after the
CODE:returns a valid contract. I am collecting a lot of "negative pacing points" in the process as it typically requires multiple calls for Greeks on the underlying at different strikes.
IBHistorical.Instance.GetOptionsSymbol()
Does your method resolve the increment as a by-product or a required parameter for the chain you are processing?
We've talked about it before, but IB's Option Chain gives you all the dates and strikes available without telling you which strikes are valid for a specific expiration. We check if there is ANY trade history for a strike to determine if it's valid.
fwiw,
Tradier will return a valid contract from its chain every time, because the Tradier chain returns only valid strikes for each expiration. This will change over time, of course, as new contracts are added and become active.
Re: varying the price
It's a function of the price. e.g., by $2.50 for strikes above $200 and $0.50 for strikes below $20.
fwiw,
Tradier will return a valid contract from its chain every time, because the Tradier chain returns only valid strikes for each expiration. This will change over time, of course, as new contracts are added and become active.
QUOTE:The chain has the strikes and expirations. GetOptionsSymbol() will return the strike/expiration requested using the logic outlined in the QuickRef. We check if the result is a valid contract. If it's not, we call GetOptionsSymbol() internally up to 4 times by varying the price on either side of the price requested in the initial call to GetOptionsSymbol() until a valid strike/expiration combination is determined.
Does your method resolve the increment as a by-product or a required parameter for the chain you are processing?
Re: varying the price
It's a function of the price. e.g., by $2.50 for strikes above $200 and $0.50 for strikes below $20.
I wrote a method that uses IB Greeks calls to probe "validity" for a given exp date and price. It is testing for null return as a proxy for (in)validity. I use this to determine the option chain increment. Then I can construct the option chain (at least around the money) from the base (Mod 5) contract strike WL supplies. I'm not sure if yields the same result as testing for history (maybe testing for null bars?).
What do you think about this approach??
What do you think about this approach??
It seems like a lot of extra work.
Why don't you give me a specific example of a GetOptionSymbol() call that doesn't work? I need all the parameters you're using and I'll look into why it's not working.
Why don't you give me a specific example of a GetOptionSymbol() call that doesn't work? I need all the parameters you're using and I'll look into why it's not working.
Let me clarify. It's not that GetOptionSymbol() doesn't work. It works as advertised.
The issue is that I have additional parameters for selecting the strike, which include max price and Delta. These are not parameters that are included in GetOptionSymbol(), so I have to get valid options that meet the Delta and price ranges criteria. I first find "candidate" options using Delta and then select optimum strike based on price (could be higher or lower but maximizes the transaction value).
If the option chain for a specific underlying and date were available in a table or list form, I could just download it and do a lambda function to find the optimum. But I understand that such a list or table is not available from IB.
Addition: I don't need an entire option table. Generally, 20 strikes above or below the underlying price contain what I'm looking for.
The issue is that I have additional parameters for selecting the strike, which include max price and Delta. These are not parameters that are included in GetOptionSymbol(), so I have to get valid options that meet the Delta and price ranges criteria. I first find "candidate" options using Delta and then select optimum strike based on price (could be higher or lower but maximizes the transaction value).
If the option chain for a specific underlying and date were available in a table or list form, I could just download it and do a lambda function to find the optimum. But I understand that such a list or table is not available from IB.
Addition: I don't need an entire option table. Generally, 20 strikes above or below the underlying price contain what I'm looking for.
You have some pretty esoteric options requirements.
Here's how you can get at the IB Option Chain that we use for GetOptionsSymbol().
Sample output -
Here's how you can get at the IB Option Chain that we use for GetOptionsSymbol().
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using System.Collections.Generic; using WealthLab.InteractiveBrokers; namespace WealthScript12 { public class IBOptionChainDemo : UserStrategyBase { public override void Initialize(BarHistory bars) { // ** call GetOptionsSymbol() at least once to initialize the chain for a symbol ** int bar = bars.Count - 1; _ = IBHistorical.Instance.GetOptionsSymbol(bars, OptionType.Call, bars.Close.LastValue, bars.DateTimes[bar], 10, false, false, true); // access the chain like this OptionChain chain = IBConnection.Instance.Options[bars.Symbol]; if (chain == null) { WriteToDebugLog("null chain"); return; } List<DateTime> regExpirations = chain.Expirations(weeklies: false); WriteToDebugLog("All regular Expirations in chain"); WriteToDebugLog(string.Join('\n', regExpirations)); List<DateTime> weeklyExpirations = chain.Expirations(weeklies: true); WriteToDebugLog("\nAll non-regular Expirations in chain"); WriteToDebugLog(string.Join('\n', weeklyExpirations)); // for IB, strikes will be the same for all expirations, so we'll just pick the first one OptionSymExp ose = chain.GetOptionSymExp(regExpirations[0], false); WriteToDebugLog("\nAll strikes in chain"); WriteToDebugLog(string.Join(',', ose.Strikes)); } public override void Execute(BarHistory bars, int idx) { } } }
Sample output -
CODE:
---CSCO--- All regular Expirations in chain 12/15/2023 12:00:00 AM 1/19/2024 12:00:00 AM 2/16/2024 12:00:00 AM 3/15/2024 12:00:00 AM 4/19/2024 12:00:00 AM 6/21/2024 12:00:00 AM 7/19/2024 12:00:00 AM 9/20/2024 12:00:00 AM 1/17/2025 12:00:00 AM 6/20/2025 12:00:00 AM 9/19/2025 12:00:00 AM 12/19/2025 12:00:00 AM 1/16/2026 12:00:00 AM All non-regular Expirations in chain 12/22/2023 12:00:00 AM 12/29/2023 12:00:00 AM 1/5/2024 12:00:00 AM 1/12/2024 12:00:00 AM 1/26/2024 12:00:00 AM All strikes in chain 20,22.5,25,27.5,30,32.5,35,37.5,38,39,40,41,41.5,42,42.5,43,43.5,44,44.5,45,45.5,46,46.5,47,47.5,48,48.5,49,49.5,50,51,51.5,52,52.5,53,54,55,56,57,57.5,58,59,60,61,62,62.5,63,64,65,66,67.5,70,72.5,75,80,85,90,95
QUOTE:
We've been over this before, but the IB Option Chain doesn't guarantee a valid contract. You just get all strikes and dates that are available.
We try to find one quickly around the intended strike, going through a few of the strikes available in the chain. After a few tries to find one that is trading, we give up. We could try a few more times, it's just more delay.
I hope you don't consider this a repeat question, but I have been operating under the apparently incorrect belief that IB.Historical() *always* return a valid strike which is a multiple of 5. In re-reading the above and researching further I now realize that was the "guarantee" for synthetic options, but I can't find any such promise for real options. My strike probing algorithms are based on the IB.Historical() and OptionSynthetic.GetOptionsSymbol methods returning a valid strike with a multiple of 5. It works great with synthetics, but now I think I understand why I get fails with live trading throwing null errors when it tries to get Greeks from invalid symbols.
Not sure how I will work around this. I will dig into the code you provided above to see if I can figure a way to make my "strike probe code" work reliably with live options.
QUOTE:
Not sure how I will work around this.
I just had an idea. What if I first use OptionSynthetic to get the valid 5-multiple strike, strip off the leading "!" from the symbol, and feed that into my strike prober? What do you think?
With the OptionChain example I posted 3 weeks ago in #11, I'm not sure how you could have still assumed steps of 5 (where was that ever written?) or why you'd be asking about using OptionSynthetic to probe for a valid strike.
Post #11 shows you how to get all the dates and all the strikes.
All strikes are not valid for all dates (that's the problem), but at least you have precisely the strikes that could be valid. And, keep in mind, the "validity" can change when a new strike/contract begins trading.
Post #11 shows you how to get all the dates and all the strikes.
All strikes are not valid for all dates (that's the problem), but at least you have precisely the strikes that could be valid. And, keep in mind, the "validity" can change when a new strike/contract begins trading.
QUOTE:
I'm not sure how you could have still assumed steps of 5 (where was that ever written?
from https://www.wealth-lab.com/blog/backtest-auto-trade-options
QUOTE:
Since synthetic options work without option chains, OptionSynthetic.GetOptionsSymbol() returns strike prices in $5 increments.
Of course, that's for synthetic options and I mistakenly thought that applied to live options as well.
Today my strategy under testing wrote this to the debug log
This is not a valid strike for 2/27. It explains why the strategy didn't take a position from a signal.
I understood that WL tests for valid bars when using the GetOptionsSymbol() method. Did I misunderstand?
Here is the relevant code:
QUOTE:
Buy signal 2/20/2024 12:48:00 PM
Contract Symbol QQQ240227C427.5
obars are null for QQQ240227C427.5 on 2/20/2024 12:48:00 PM
This is not a valid strike for 2/27. It explains why the strategy didn't take a position from a signal.
I understood that WL tests for valid bars when using the GetOptionsSymbol() method. Did I misunderstand?
Here is the relevant code:
CODE:
int minDaysAhead = 7; bool weeklies = true; bool expired = false; bool closestStrike = true; if (condition0) { WriteToDebugLog("Buy signal " + bars.DateTimes[idx], false); contractSymbol = IBHistorical.Instance.GetOptionsSymbol (bars, OptionType.Call, bars.Close[idx], curDate, minDaysAhead, weeklies, expired, closestStrike); if (contractSymbol != null && contractSymbol != "") { WriteToDebugLog("Contract Symbol " + contractSymbol, false); _hash = Math.Abs(contractSymbol.GetHashCode()); _syms.Add(contractSymbol); _obars = RetrieveOptionBars(bars, contractSymbol); } else WriteToDebugLog("Contract Symbol is null " + bars.DateTimes[idx], false); if (_obars != null) { _transaction = PlaceTrade(_obars, TransactionType.Buy, OrderType.Market, 0, 0, "Buy At Market (1)"); _settledCash -= _transaction.BasisAmount; WriteToDebugLog("Settled cash after transaction is " + _settledCash.ToString("N2"), false); if (_settledCash < 0) _transaction.Quantity = 0; //don't take trade if settled cash is gone if (Backtester.Orders.Count > 0) { foreach (Transaction t in Backtester.Orders) WriteToDebugLog(t, false); } else WriteToDebugLog("No positions in backtester", false); } else { WriteToDebugLog("obars are null for " + contractSymbol + " on " + bars.DateTimes[idx], false); return; } } private BarHistory RetrieveOptionBars(BarHistory syncbars, string symbol) { BarHistory obars; if (_dict.ContainsKey(symbol)) obars = _dict[symbol]; else { obars = GetHistory(syncbars, symbol); _dict.Add(symbol, obars); } return obars; }
QUOTE:It does, but it gives up after testing 4 strikes and returns the last strike it tested, even if it hadn't traded.
I understood that WL tests for valid bars when using the GetOptionsSymbol() method
I figured this out after finding a scenario where 8 consecutive strikes were not traded. I've already changed this for IB Provider Build 44 to test 10 strikes before giving up
- it should be out this week with the next round of updates.
Sounds good. I'll hold off on spending time trying to develop recovery code.
Your Response
Post
Edit Post
Login is required