- ago
In my option trading strategy under development, I get null bars retuned for certain symbols on certain dates when using

CODE:
OptionSynthetic.GetHistory()


Should I be concerned or is this to be expected?
0
276
Solved
10 Replies

Reply

Bookmark

Sort
Cone8
 ( 7.88% )
- ago
#1
Sure, null is expected.
For example, if the certain symbols/scales for the underlying are null, then so will be the Synthetic.

.. on certain dates
Like on dates after the expiration?

Can you share a specific example so that I don't have to guess?
0
- ago
#2
Sure

QUOTE:
"1/12/2024 08:10:11:740" "!INTC231215C40" " oBars Close price is NaN on 11/14/2023 12:00:00 AM"
"1/12/2024 08:10:11:670" "!INTC231215C40" " oBars Close price is NaN on 11/13/2023 12:00:00 AM"
"1/12/2024 08:10:11:630" "!PLTR231215C20" " oBars Close price is NaN on 11/10/2023 12:00:00 AM"
"1/12/2024 08:10:11:599" "!AAPL231215C175" " oBars Close price is NaN on 11/9/2023 12:00:00 AM"
"1/12/2024 08:10:11:594" "!AAPL231215C175" " oBars Close price is NaN on 11/8/2023 12:00:00 AM"
"1/12/2024 08:10:11:586" "!BAC231215C30" " oBars Close price is NaN on 11/7/2023 12:00:00 AM"
"1/12/2024 08:10:11:567" "!META231215C305" " oBars Close price is NaN on 11/6/2023 12:00:00 AM"
"1/12/2024 08:10:11:545" "!META231215C300" " oBars Close price is NaN on 11/3/2023 12:00:00 AM"
"1/12/2024 08:10:11:539" "!META231215C300" " oBars Close price is NaN on 11/2/2023 12:00:00 AM"
"1/12/2024 08:10:11:532" "!INTC231215C40" " oBars Close price is NaN on 11/1/2023 12:00:00 AM"
"1/12/2024 08:10:11:527" "!INTC231215C40" " oBars Close price is NaN on 10/31/2023 12:00:00 AM"
"1/12/2024 08:10:11:521" "!INTC231215C40" " oBars Close price is NaN on 10/30/2023 12:00:00 AM"
"1/12/2024 08:10:11:517" "!MSFT231215C320" " oBars Close price is NaN on 10/27/2023 12:00:00 AM"
"1/12/2024 08:10:11:490" "!MSFT231215C325" " oBars Close price is NaN on 10/26/2023 12:00:00 AM"
"1/12/2024 08:10:11:476" "!META231215C295" " oBars Close price is NaN on 10/25/2023 12:00:00 AM"
"1/12/2024 08:10:11:471" "!META231215C305" " oBars Close price is NaN on 10/24/2023 12:00:00 AM"
"1/12/2024 08:10:11:462" "!META231215C300" " oBars Close price is NaN on 10/23/2023 12:00:00 AM"
"1/12/2024 08:10:11:458" "!META231215C300" " oBars Close price is NaN on 10/20/2023 12:00:00 AM"
"1/12/2024 08:10:11:455" "!META231215C305" " oBars Close price is NaN on 10/19/2023 12:00:00 AM"
"1/12/2024 08:10:11:439" "!META231215C310" " oBars Close price is NaN on 10/18/2023 12:00:00 AM"
"1/12/2024 08:10:11:434" "!META231117C305" " oBars Close price is NaN on 10/17/2023 12:00:00 AM"
"1/12/2024 08:10:11:432" "!META231117C305" " oBars Close price is NaN on 10/16/2023 12:00:00 AM"
"1/12/2024 08:10:11:426" "!META231117C310" " oBars Close price is NaN on 10/13/2023 12:00:00 AM"

Note: the error message is in the log generated by data validity checking in my code.
0
Cone8
 ( 7.88% )
- ago
#3
Pretty odd. My only guess is that the synthetic bars are not synchronized with the underlying for some reason. I only get NaNs after expiration.
Can you come up with a bare-bones code example that demonstrates it?

By the way, this isn't a "null BarHistory". These are NaN values (Not a Number).
0
- ago
#4
Yes, you're right. I guess the bars might not be null but there might not be data at that index.

CODE:
                     if (_obars == null)                      {                         if (_debugOption)                         {                            WriteToDebugLog("Contract symbol obars for " + bars.Symbol + " were null on " + bars.DateTimes[idx].ToShortDateTimeString());                            WLHost.Instance.AddLogItem(indexSym, " oBars are Null", WLColor.Red);                         }                      }                      else if (Double.IsNaN(_obars.Close[idx]))                      {                         if (_debugOption)                         {                            WriteToDebugLog("Price for " + _contractSymbol + " was not available on " + bars.DateTimes[idx].ToShortDateTimeString());                            WLHost.Instance.AddLogItem(_contractSymbol, " oBars Close price is NaN on " + bars.DateTimes[idx].Date, WLColor.Red);                         }


I'll see if I can put a simple extract together that you can run.
0
- ago
#5
@Cone - here is a slimmed down version of the strategy that shows the problem. In extracting and putting together this code, purely by accident, I discovered that the cause of this anomaly is found in Line 54 (the OptionSynthetic.GetOptionsSymbol() method), where I estimate an IV value based on HV. If I replace this dynamic parameter with 0.4 (or another fixed %), the NaN issue goes away.

Because this is the root cause, I added the HV coding back in the Initialize() method, so you can see this yourself.

Note: When you run this, look for the error in the Log, not the debug window.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using System.Linq; namespace WealthScript2 { public class MyStrategy : UserStrategyBase { public override void Initialize(BarHistory bars) {          DateTime startDate = bars.DateTimes[0];          DateTime currentDate = bars.DateTimes[bars.Count - 1];          _dailyBars = WLHost.Instance.GetHistory(bars.Symbol, HistoryScale.Daily, startDate, currentDate, 0, null);          _dailyBarsSynced = BarHistorySynchronizer.Synchronize(_dailyBars, bars);          _dailySeries = _dailyBars.Close;          _hvSeries = HV.Series(_dailySeries, 60, 252); //HV60 annualized          _dailySyncHVSeries = TimeSeriesSynchronizer.Synchronize(_hvSeries, bars.Close); }       public override void Execute(BarHistory bars, int idx)       {          string indexSym = bars.Symbol;          double strike = Math.Ceiling(bars.Close[idx] / 5) * 5; // increment;          _weeklies = false;          _expired = true;          _closestStrike = true;          _contractSymbol = "";          _curDate = bars.DateTimes[idx]; _optAllocAmt = 1000; //placeholder value          int daysToNextExpiry = bars.TradingDaysBetweenDates(bars.DateTimes[idx], bars.NextOptionExpiryDate(idx));          daysToNextExpiry = daysToNextExpiry > 1 ? daysToNextExpiry : daysToNextExpiry + 28;             _sPos = (OpenPositionsAllSymbols.Where(p => (p.Symbol.Length > 8                && OptionsHelper.ParseSymbol(p.Symbol).Item1 == bars.Symbol)).FirstOrDefault());             ProcessSyntheticSymbol(_sPos);          void ProcessSyntheticSymbol(Position pos)          {             if (pos == null)             {                _contractSymbol = OptionSynthetic.GetOptionsSymbol                   (bars, OptionType.Call, strike * .95, bars.DateTimes[idx].Date, daysToNextExpiry + 28,                   _weeklies, _expired, _closestStrike);                if (_contractSymbol != null && _contractSymbol != "")                {                   if (_obh.ContainsKey(_contractSymbol))                      _obars = _obh[_contractSymbol];                   else _obars = OptionSynthetic.GetHistory(bars, _contractSymbol, _HV / 100);                   _obh[_contractSymbol] = _obars;                      WriteToDebugLog(bars.DateTimes[idx].ToShortDateTimeString() + " Contract Symbol is " + _contractSymbol);                }                else                {                      WLHost.Instance.AddLogItem(indexSym, " did not return contract that meets all criteria", WLColor.Red);                }                if (_obars == null)                {                   {                      WriteToDebugLog("Contract symbol obars for " + bars.Symbol + " were null on " + bars.DateTimes[idx].ToShortDateTimeString());                      WLHost.Instance.AddLogItem(indexSym, " oBars are Null", WLColor.Red);                   }                }                else if (Double.IsNaN(_obars.Close[idx]))                {                   {                      WriteToDebugLog("Price for " + _contractSymbol + " was not available on " + bars.DateTimes[idx].ToShortDateTimeString());                      WLHost.Instance.AddLogItem(_contractSymbol, " oBars Close price is NaN on " + bars.DateTimes[idx].Date, WLColor.Red);                   }                }                else                {                   double contractPrice = _obars.Close[idx] * 100;                   int contractQty = (int)(_optAllocAmt / contractPrice);                   {                      if (contractQty > 0)                      {                         Transaction tn = PlaceTrade(_obars, TransactionType.Buy, OrderType.Market, 0);                         tn.Quantity = contractQty;                      }                         WriteToDebugLog(bars.DateTimes[idx].ToShortDateString() + " " + _obars.Symbol + " is too expensive");                   }                }             }          }       }       //declare private variables below       static Dictionary<string, BarHistory> _obh = new Dictionary<string, BarHistory>();       private BarHistory _obars;       private BarHistory _dailyBars;       private BarHistory _dailyBarsSynced;              private TimeSeries _dailySeries;       private TimeSeries _dailySyncHVSeries;       private Position _sPos;              private double _optAllocAmt;       private bool _weeklies;       private bool _expired;       private bool _closestStrike;       private DateTime _curDate;       private string _contractSymbol;          private double _HV;              private IndicatorBase _hvSeries;    } }
0
Cone8
 ( 7.88% )
- ago
#6
When using the single value volatility, _HV is not assigned to anything. At least do this:
private double _HV = 25; // later assumed to be divided by 100

When using the series, _hvSeries = HV.Series(_dailySeries, 60, 252);
The first 60 bars are NaNs. Option prices can't be calculated on those bars.
0
Best Answer
- ago
#7
I added a conditional statement before trying to fetch synthetic symbol
CODE:
if (!Double.IsNaN(_HV)){}

Looks like that eliminates the error message.

Thanks for figuring this out and for the suggestion.
1
- ago
#8
After considering further, I realized a better approach would be to use the StartIndex function to delay processing until HV is valid. I expected a StartIndex = 60 days (* 13 bars per day for 30 min data) would do the trick, but it required 62 days to eliminate the NaN. Could this mean that my daily series are not synced properly?

(I ran this with a single symbol: RBLX)

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.InteractiveBrokers; using WealthLab.Indicators; using System.Collections.Generic; using System.Linq; namespace WealthScript3 { public class MyStrategy : UserStrategyBase { public override void Initialize(BarHistory bars) {          DateTime startDate = bars.DateTimes[0];          DateTime currentDate = bars.DateTimes[bars.Count - 1];          _dailyBars = WLHost.Instance.GetHistory(bars.Symbol, HistoryScale.Daily, startDate, currentDate, 0, null);          _dailyBarsSynced = BarHistorySynchronizer.Synchronize(_dailyBars, bars);          _dailySeries = _dailyBars.Close;          _hvSeries = HV.Series(_dailySeries, 60, 252); //HV60 annualized          _dailySyncHVSeries = TimeSeriesSynchronizer.Synchronize(_hvSeries, bars.Close);          StartIndex = 62 * 13;       }       public override void Execute(BarHistory bars, int idx)       {          string indexSym = bars.Symbol;          double strike = Math.Ceiling(bars.Close[idx] / 5) * 5; // increment;          _weeklies = false;          _expired = true;          _closestStrike = true;          _contractSymbol = "";          _curDate = bars.DateTimes[idx];          _optAllocAmt = 1000; //placeholder value                    _HV = _dailySyncHVSeries[idx];          int daysToNextExpiry = bars.TradingDaysBetweenDates(bars.DateTimes[idx], bars.NextOptionExpiryDate(idx));          daysToNextExpiry = daysToNextExpiry > 1 ? daysToNextExpiry : daysToNextExpiry + 28;             _sPos = (OpenPositionsAllSymbols.Where(p => (p.Symbol.Length > 8                && OptionsHelper.ParseSymbol(p.Symbol).Item1 == bars.Symbol)).FirstOrDefault());             ProcessSyntheticSymbol(_sPos);          void ProcessSyntheticSymbol(Position pos)          {             if (pos == null)             {                _contractSymbol = OptionSynthetic.GetOptionsSymbol                   (bars, OptionType.Call, strike * .95, bars.DateTimes[idx].Date, daysToNextExpiry + 28,                   _weeklies, _expired, _closestStrike);                if (_contractSymbol != null && _contractSymbol != "")                {                   if (_obh.ContainsKey(_contractSymbol))                      _obars = _obh[_contractSymbol];                   else _obars = OptionSynthetic.GetHistory(bars, _contractSymbol, _HV / 100);                   _obh[_contractSymbol] = _obars;                   WriteToDebugLog(bars.DateTimes[idx].ToShortDateTimeString() + " Contract Symbol is " + _contractSymbol + " HV is " + _HV);                }                else                {                      WLHost.Instance.AddLogItem(indexSym, " did not return contract that meets all criteria", WLColor.Red);                }                if (_obars == null)                {                   {                      WriteToDebugLog("Contract symbol obars for " + bars.Symbol + " were null on " + bars.DateTimes[idx].ToShortDateTimeString());                      WLHost.Instance.AddLogItem(indexSym, " oBars are Null", WLColor.Red);                   }                }                else if (Double.IsNaN(_obars.Close[idx]))                {                   {                      WriteToDebugLog("Price for " + _contractSymbol + " was not available on " + bars.DateTimes[idx].ToShortDateTimeString());                      WLHost.Instance.AddLogItem(_contractSymbol, " oBars Close price is NaN on " + bars.DateTimes[idx].Date, WLColor.Red);                   }                }                else                {                   double contractPrice = _obars.Close[idx] * 100;                   int contractQty = (int)(_optAllocAmt / contractPrice);                   {                      if (contractQty > 0)                      {                         Transaction tn = PlaceTrade(_obars, TransactionType.Buy, OrderType.Market, 0);                         tn.Quantity = contractQty;                      }                         WriteToDebugLog(bars.DateTimes[idx].ToShortDateString() + " " + _obars.Symbol + " is too expensive");                   }                }             }          }       }              //declare private variables below       static Dictionary<string, BarHistory> _obh = new Dictionary<string, BarHistory>();       private BarHistory _obars;       private BarHistory _dailyBars;       private BarHistory _dailyBarsSynced;              private TimeSeries _dailySeries;       private TimeSeries _dailySyncHVSeries;       private Position _sPos;              private double _optAllocAmt;       private bool _weeklies;       private bool _expired;       private bool _closestStrike;       private DateTime _curDate;       private string _contractSymbol;          private double _HV;       int _barsPerDay;              private IndicatorBase _hvSeries;    } }
0
Cone8
 ( 7.88% )
- ago
#9
A daily HV(60) isn't valid until the 61st daily bar, which is applied only to the final intraday bar on the 61st day.
On day 62, you'd expect all bars to have a valid HV, just as you found.
0
- ago
#10
Wow. Thanks.
0

Reply

Bookmark

Sort