AMZN Stock Study (2-1-23)
Posted by Mark on October 7, 2022 at 06:52 | Last modified: February 27, 2023 17:01I recently* did a stock study on Amazon.com Inc. (AMZN) with a closing price of $103.13.
Value Line writes:
> Amazon.com is the largest online retailers [sic]. The company opened
> its virtual doors in 1995. Sales breakdown (2021): North America;
> 59% of sales. International sales, 27% of total. Amazon Web Services
> (AWS), 14%. Third-party sellers (Marketplace) account for about 20%
> of sales. Seasonality: Q4 accounted for 29% of ’21 revenue.
> Acquired Audible.com, ’08, Zappos, ’09, Whole Foods Market, ’17.
This mega-sized (> $50B annual revenue) company has grown sales at an annualized rate of 26.1% over the last 10 years and EPS 71.1% per year since 2016 (excluding previous years with tiny EPS that skew the historical average even higher). Lines are mostly up and parallel except for EPS decline in ’22. PTPM has trended higher over the last 10 years from ~1% to ~8% with a last-5-year average of 5.3%. This slightly trails peer (stated as VIPS, JDD, and PDD) and industry averages.
ROE has trended up from ~0% to ~27% over the last 10 years with a last-5-year average of 22.1%. Debt-to-Capital has increased from 31.9% to 45.7% with a last-5-year average of 49.8%. This is lower than peer and industry averages. Although Interest Coverage is only 5.6 and the Quick Ratio only 0.68, the M* analyst writes:
> The balance sheet is sound with a net cash position and only modest
> gross debt. We expect the balance sheet to remain sound as the
> company has typically maintained a conservative balance sheet and
> generates more than enough FCF from [Amazon Web Services] and
> advertising to fund growth throughout the business.
I assume long-term annualized sales growth of 8% based on the following:
- YF projects YOY 8.6% and 9.6% growth for ’22 and ’23, respectively (based on 46 analysts).
- Zacks projects YOY 8.6% and 9.1% growth for ’22 and ’23, respectively (13).
- Value Line projects 17.8% growth per year from ’21-’26.
- CFRA projects 13.3% YOY and 10.8% per year for ’22 and ’21-’23, respectively.
- M* provides a 2-year estimate of 8.6%.
>
I assume long-term annualized EPS growth of 9% based on the following:
- CNN Business projects 5-year annualized growth of 12.2%.
- MarketWatch projects annualized ACE of -8.1% and 11.6% for ’21-’23 and ’21-’24, respectively (based on 54 analysts).
- Nasdaq.com projects 65.8% YOY and 66.9% per year for ’24 and ’23-’25, respectively (12, 8, and 4 analysts).
- YF projects YOY growth of -103.7% and 1433% as negative ’22 earnings rebound in ’23 off a fractional base (46).
- YF also gives a 5-year annualized estimate of 26%.
- Zacks similarly projects YOY growth of -104.3% and 1228% as earnings turn negative in ’22 then rebound in ’23 (11).
- Zacks gives a 5-year annualized estimate of 18%.
- Value Line projects annualized growth of 21.2% from ’21-’26.
- CFRA projects -103.7% YOY and -24.6% per year for ’22 and ’21-’23, respectively.
- M* gives a long-term growth estimate of 8.9%.
>
I’m forecasting near the bottom of the long-term-estimate range (8.9% – 26%). Because a rebound is forecast following a sharp [quarterly] EPS drop in ’22, I decided to override projection from the last annual (vs. quarterly) data point.
My Forecast High P/E is 35. Since 2016, high P/E has eased from 173 to 58.2 (2021). The last 5-year average is 106.1. At some point, I expect P/E to fall into a “normal” range, but predicting when this happens is like taking a shot in the dark.
My Forecast Low P/E is 25. Since 2016, low P/E has eased from 97 to ~44 (2021) with a last 5-year average of 65.3. I expect this to fall into a “normal” range eventually (I’m astounded that this hasn’t happened already to the mega-sized company).
My Low Stock Price Forecast is $81. The default low price is $27.30 based on the depressed $1.09 EPS. This does not seem reasonable. Instead, I will use the 2020 low price of $81.30, which is 21% below the last closing price.
All this results in an U/D ratio of 3.2. This makes AMZN a BUY with a Total Annualized Return (TAR) of 11.1%.
AMZN’s 17% ownership stake in RIVN is what makes this analysis so complex. AMZN lost $10.4B over the first nine months of 2022 due to the drop in RIVN stock price. This is why analysts forecast -100% EPS growth for 2022 and why some 2023 [provided that RIVN shares do not continue the precipitous decline] growth projections are over 1000%. In my view, any math amounting to a 20%+ long-term (e.g. 3-5) EPS growth rate after years 1-2 are negative is suspect.
While TAR is decent, PAR (using forecast average, not high, P/E) is only 7.7%. Is it reasonable to expect the former, which is consistent with the highest long-term analyst estimates?
To answer this, I assess margin of safety (MOS) by comparing my inputs with Member Sentiment (MS). Out of 1142 studies over the past 90 days, projected sales, projected EPS, high P/E, and low P/E average 13.9%, 17%, 78.6, and 67, respectively. I am dramatically lower on all inputs.
To calculate average MS Low Stock Price Forecast, I excluded 199 studies using $100 or more. At least half of these were four digits, and I deem all them to be unreasonable and/or invalid. The revised averages are 13.1%, 16.4%, 73.6, and 61.5, respectively: still well higher than mine. The average Low Stock Price Forecast is $70.50, which is lower than mine and would have resulted in a HOLD for this study rather than BUY. I’m not sure how meaningful this is as I would similarly have had a lower Low Stock Price Forecast had I chosen to use the 2020 low—a decision one could argue to be unreasonable.
While my aim is to use MS to evaluate MOS, I wonder if such comparison here might be apples-to-oranges because I don’t know how many changed projection from the last quarterly (default) to annual data point. Nevertheless, both my growth rate and P/E projections are lower than MS. Even if the conservativism of one set effectively offsets the [more aggressive decision to] override from quarterly to annual data point, I still have the other conservative set providing MOS.
And Value Line has a projected average annual P/E of 40 compared to my 30, which further bolters the conservative case.
Despite the lackluster PAR, given the apparent MOS I like a BUY on these shares up to $104.
>
*—Publishing in arrears as I’ve been doing one daily stock study while only posting two blogs per week.
Backtester Logic (Part 10)
Posted by Mark on October 4, 2022 at 07:11 | Last modified: June 22, 2022 08:35Today I will continue by following the road map laid out at the end of Part 9.
I will begin with the results file. I have been using Jupyter Notebook for development and I can plot some graphs there, but I want to print detailed results to a .csv file. I am currently generating one such file that shows every day of every trade. Eventually, I want to generate a second file that shows overall trade statistics.
The results file gets opened at the beginning and closed at the end of the program with these lines:
In an earlier version, I then printed to this file as part of the find_short and update_short branches with lines like this:
While I find the syntax interesting, I realized these are pretty much just string operations that won’t help me to calculate higher-level trade statistics. Numpy will be much better for that, which is why I decided to compile the results into btstats (dataframe). Done that way, I can still get the results file in the end with this line:
The dataframe is created near the beginning of the program:
Most of the columns have corresponding variables (see key) and/or are derived through simple arithmetic on those variables.
Now, instead of printing to the results file in two out of the four branches I add a list to the dataframe as a new row:
I searched the internet to find this solution here. One nice thing about Python is that I can find solutions online for most things I’m looking to accomplish. That doesn’t mean I understand how they work, though. For example, I understand df.loc[] to accept labels rather than integer locations (df.iloc[], which I have also learned cannot expand the size of a dataframe). len(btstats.index) is an integer so I’m not sure why it works. This is a big reason why I still consider myself a pupil in Python.
L127 is an example of variable reset (discussed in Part 9). This is what I want to do for every variable once it has served its purpose to make sure I don’t accidentally use old data for current calculations (e.g. next historical date).
Let’s take a closer look at L121:
The data file includes a date field as “number of days since Jan 1, 1970” format. Multiplying that by 86400 seconds/day yields number of seconds since midnight [UTC], Jan 1, 1970, which is the proper format for the datetime module’s UTC timestamp. I can now use the .strftime() method and ‘%b’ to get the first three letters as an abbreviated month name. Being much more readable than a nonsensical 5-digit integer, this is what I want to see in the results file.
The light at the end of the tunnel is getting brighter!
Categories: Python | Comments (0) | PermalinkFOXF Stock Study (1-31-23)
Posted by Mark on September 29, 2022 at 06:45 | Last modified: February 23, 2023 16:40I recently* did a stock study on Fox Factory Holding Corp. (FOXF) with a closing price of $113.39.
Value Line writes:
> Fox Factory Holding Corp. designs, engineers, manufactures, and
> markets performance ride dynamics products for customers
> worldwide. Fox Factory Holding is the holding company of Fox
> Factory, Inc. The company’s premium brand ride dynamics products
> are used primarily on bicycles, side-by-side vehicles, onroad
> vehicles with off-road capabilities, off-road vehicles and
> trucks, all-terrain vehicles, snowmobiles, specialty vehicles
> and applications, and motorcycles.
This medium-sized company has grown sales and EPS at annualized rates of 19.8% and 25.5% over the last 10 years. Lines are mostly up and parallel with slight EPS pullbacks in ’15 and ’20. PTPM over the last 10 years has increased from 9.5% to 14.5% with a last-5-year average of 13.8%. This beats peer (stated as THRM, ALSN, and GTX) and industry averages.
ROE has trended down from 28.5% in ’13 to 19.2% in ’21 with the last five years averaging 20.3%: slightly better than peer and industry averages. Debt-to-Capital has ranged from 8% (’13) to 35.5% (’20) over the last nine years with a last-5-year average of 25.2%: lower than peer and industry averages. Interest Coverage is 28, and the company has no long-term debt.
I assume long-term annualized sales growth of 8% based on the following:
- CNN Business projects 23.1% YOY and 14.4% per year for ’22 and ’21-’23, respectively (based on 6 analysts).
- YF projects YOY 21.4% and 5.4% for ’22 and ’23, respectively (7 analysts).
- Zacks projects YOY 21.4% and 5.1% for ’22 and ’23, respectively (5).
- Value Line projects 8.5% annualized from ’21-’26.
>
I assume long-term annualized EPS growth of 9% based on the following:
- CNN Business projects 17.1% YOY and 13.3% per year for ’22 and ’21-’23, respectively (based on 6 analysts), along with a 5-year annualized estimate of 14.4%.
- MarketWatch projects annualized ACE of 13.5% and 12.4% for ’21-’23 and ’21-’24, respectively (7 analysts).
- Nasdaq.com projects 9.5% YOY and 9.4% per year for ’23 and ’22-’24, respectively [5, 5, and 1 analyst(s) for ’22, ’23, and ’24].
- YF projects YOY 17.1% and 9.7% for ’22 and ’23, respectively, and 15% annualized for the next five years (7).
- Zacks projects YOY 17.1% and 9.5% for ’22 and ’23, respectively, and 13.8% annualized for the next five years (5).
- Value Line projects 12.6% annualized from ’21-’26.
>
I’m forecasting beneath the range (12.6%) of four long-term estimates.
My Forecast High P/E is 27. High P/E has ranged from 26.1 (’14) to 51.1 (’20) and trended higher since 2013. The last-5-year average is 42.9.
My Forecast Low P/E is 15. Low P/E has ranged from 14.7 (’16) to 26.3 (’21) since 2013. The last-5-year average is 20.6.
My Low Stock Price Forecast is $67.20, which is the default value. This is 40% less than the previous closing price and just below the 52-week low of 69.3. The stock has had a big run-up in price over the last few months.
All this results in an U/D ratio of 1.6, which makes FOXF a Hold. Total Annualized Return (TAR) is 10.4%.
While TAR is decent, PAR (using Forecast Average, not High, P/E) is only 5%. I want more from a medium-sized company.
For more context, I like to assess margin of safety (MOS) by comparing with Member Sentiment (MS). Out of 212 studies over the past 90 days, projected sales, projected EPS, Forecast High P/E, and Forecast Low P/E average 12.9%, 13.2%, 31.3, and 18.9, respectively. I’m lower on all inputs and also lower than Value Line’s projected average annual P/E of 25 (vs. 26.1 for MS and 21 for me). The average MS Low Stock Price Forecast is also above mine at $68.87.
The MOS is alive and well in this study: enough to preclude a buy over $97/share.
>
*—Publishing in arrears as I’ve been doing one stock study per day while usually posting two blogs per week.
Backtester Logic (Part 9)
Posted by Mark on September 26, 2022 at 06:39 | Last modified: June 22, 2022 08:35Having completed analysis of the first two, I now continue with the update_long and update_short branches.
The conditional skeleton for these two branches of program flow looks like this:*
As discussed in the third paragraph of Part 2, rather than follow trade entry criteria these branches just need to locate previous selections. L134 (for update_long) and L150 (for update_short) look to match strike price and expiration. Short and sweet.
Here are considerations with regard to historical date:
- After find_short, historical date should be advanced to update the spread.
- After update_long, historical date should not be advanced since two legs need to be updated on the same date.
- After update_short, historical date should be advanced assuming the trade remains open.
>
(1) Near the end of find_short, the wait_until_next_day flag is set to True, but current_date has not yet been assigned. I have already discussed this oversight (near end of Part 3 and beginning of Part 4).
(2) update_long does nothing with the wait_until_next_day flag, and…
(3) In L145, update_short checks to make sure historical date has not advanced before progressing to update. If historical date has advanced, then an exception is raised. This should not happen if the data file is complete unless the short option has expired. Whether it be short option expiration or a predetermined DTE, I still need to build in time-stop logic to handle this.
When any branch of program flow finds a match, encoding from the data file assigns to variables shown in the key.
find_short and update_short include several lines devoted to variable reset. Variable reset prevents previous (stale) values to be used in current calculations. Variable reset will only be done as part of find_short if a short option is not found, which itself in an error. To do this effectively, I will make a list to ensure every variable involved in assignment is subsequently reset.
I will also decide whether a user-defined function to reset variables (as mentioned in Part 2 footnote) is indicated.
Is that the light at the end of the tunnel I might be starting to see?
Here are some other topics I still want to discuss:
- Printing to results file
- trade_status
- Rationale behind btstats
- L121 syntax
- Exit logic
- Encoding process
>
I will continue next time.
*—As discussed in the Part 6 footnote, I’ve used Atom’s folding feature for display purposes, which
means the higher level (indented) lines are hidden from view.
BOOT Stock Study (1-26-23)
Posted by Mark on September 23, 2022 at 06:50 | Last modified: February 23, 2023 11:27I recently* did a stock study on Boot Barn Holdings Inc. (BOOT) with a closing price of $74.37.
From M*:
> Boot Barn Holdings Inc operates specialty retail stores.
> The company sells western and work-related footwear,
> apparel, and accessories in the United States. It is a
> single operating segment, which includes net sales
> generated from its retail stores and e-commerce websites.
This medium-sized company has grown sales at an annualized rate of 18.5% over the last 10 years and EPS 41.8% per year since ’14. Lines are mostly up, straight, and parallel. PTPM over the last 10 years has risen from 0.6% to 17% (upside outlier) with a last 5-year average (excluding the outlier) of 6.6%. This was below peer (stated as BURL and VSCO) and industry averages until ’20.
ROE has increased from 10.1% in ’14 to 34% in ’21 with the latter appearing to be an upside outlier. The last 5-year average is 18.9%, which seems slightly below (above) industry (peer) averages. Debt-to-Capital has averaged 45.7% over the last five years and was higher than peer and industry averages until 2019. The company has no long-term debt [operating leases], though, and Interest Coverage is over 60.
I assume long-term annualized sales growth of 6% based on the following:
- CNN Business projects 13.3% YOY and 9.5% per year for ’22 and ’21-’23, respectively (based on 12 analysts).
- YF projects YOY 11.8% and 6.8% for ’23 and ’24, respectively (13 analysts).
- Zacks projects YOY 12.2% and 7.6% for ’23 and ’24, respectively (5).
- Value Line projects 7.9% annualized from ’21-’26.
- CFRA projects 11.9% YOY and 13% per year for ’23 and ’22-’24, respectively (13).
- M* gives a 2-year ACE of 8.7% per year.
>
I assume long-term annualized EPS growth of 5% based on the following:
- CNN Business reports ACE of 7.1% YOY contraction and 1.6% per year contraction for ’22 and ’21-’23, respectively (based on 12 analysts).
- MarketWatch projects annualized ACE of 1.6% contraction and 3.2% growth from ’22-’24 and ’22-’25, respectively (13 analysts).
- Nasdaq.com projects growth of 5.2% YOY and 10% per year for ’24 and ’23-’25, respectively (6, 7, and 2 analysts for ’23, ’24, and ’25).
- YF projects YOY 9.6% contraction and 4.4% growth for ’23 and ’24, respectively, along with 11.9% annualized growth for the next five years (13).
- Zacks projects YOY 6.5% contraction and 4.9% growth for ’23 and ’24, respectively, along with 10.6% annualized growth for the next five years (6).
- Value Line projects annualized growth of 2.9% from ’21-’26.
- CFRA projects 9.6% contraction YOY and 2.9% per year contraction for ’23 and ’22-’24, respectively (13).
>
My Forecast High P/E is 20. High P/E has ranged from 19.3 (’17) to 93.1 (upside outlier in ’15) since 2014. The last 5-year average is 25.6.
My Forecast Low P/E is 6. Low P/E has ranged from 4.9 (’19) to 31.3 (upside outlier in ’14) since 2014. The last 5-year average is 7.2.
My Low Stock Price Forecast is $36.50. This is the default value and 51% below the previous closing price. While this is more than the 20% rule of thumb, given the earnings contraction projected over the next couple years, I don’t see good reason to override (which would effectively be using a higher Forecast Low P/E).
All this results in an U/D ratio of 2.1, which makes BOOT a Hold. Total Annualized Return is 15.9%.
While the total return projection is impressive, PAR (using forecast average, not High, P/E) is a lukewarm 6.3%. I want more from a medium-sized company.
For added context, I like to assess margin of safety (MOS) by looking to Member Sentiment. Out of 120 studies over the past 90 days, projected sales, projected EPS, High P/E, and Low P/E average 10.7%, 9.8%, 21.9, and 8.5, respectively (two studies excluded with Projected High P/E’s of 100 and 2128, which skewed the average Projected High P/E to 39.8). I’m lower on all inputs. Same goes for the average projected low price of $41.90, which is $5.40 higher than mine.
Finally, Value Line has projected average annual P/E at 17 compared to my 13.
It will suffice to say that the MOS is alive and well in this analysis. I’m a buyer on a stock price below $66.
>
*—Publishing in arrears as I’ve been doing one stock study per day while usually posting two blogs per week.
Backtester Logic (Part 8)
Posted by Mark on September 20, 2022 at 06:57 | Last modified: June 22, 2022 08:35Having tied up several loose ends in the last post, today I want to analyze logic for the find_short branch.
As with find_long (see here), find_short involves a multiple if statement:
The input variable (see key) from the top of the program is defined as follows:
width = int(input(“Enter spread width (in monthly expirations): “))
L98 searches for a DTE between upper and lower bounds. The difference between bounds is 10 days. The upper bound is the long option expiration minus (28 * width). With 28 or 35 days between expiration cycles, (28 * width) is the highest value it should ever take. The extra 10 [days] should accommodate a 35-day cycle.
If the width covers two (or more) 35-day cycles then this may not work because 7 * 2 = 14 days is more than the 10 provided. Moving the 10 inside parentheses won’t work because width = 4 (the least required to get two 35-day cycles) would allow for a lower bound that is ((4 * 28) + 40) below the upper bound: enough to capture two potential options for the short leg (although the desired longer-dated option will match first if the data file is complete).
To better code L98, I should include the additional days for each three months of width:
DTE > L_dte_orig – ((28 * width) + (10 * ((width + 2) // 3)))
Floor division ( // ) truncates the remainder for positive numbers. For one month, this is 3 // 3 = 1. For three months, this is >
5 // 3 = 1. Only when I get to four months, which is 6 // 3 = 2, will 20 additional days be included.
I will make this modification, but for a couple reasons it’s not something I will use anytime soon. First, I really worry about liquidity and option availability for width > 3 because the long will then be very far from expiration and probably lightly traded. Practically speaking, I would only consider a max width of two months. Second, the lower bound seems unnecessary. Any option available in the long expiration should be available in the short as long as the data file is complete.*
If I want to clean the data files, which includes assessing completeness by searching for omitted and erroneous data, then I can create some simple Python scripts. That’s a topic for another post.
L99 looks to match strike price with the long option. One option per expiration cycle should match. I am okay with this line coming after L98 because the more restrictive if statement (see Part 4) may depend on how many expirations are available: a variable number over the years.
L99 also looks to match date with long option purchase. This is redundant because the previous if statement (not shown) checks for this and makes an entry in missing_s_s_p_dict if short not found.
I will continue next time.
*—This brings to mind another problem with 10-point multiples in find_long (see fourth paragraph Part 7).
Far from expiration, I have sometimes noticed that only 25-point strikes are available (and only 50-point
strikes going back many years). Requiring 10-multiple is actually constrained to 50-point strikes in that
case. The difference between one 50-point strike and the next can be much more than 1% of the
underlying in those earlier years, which is enough to make for a clearly directional trade.
Introduction to BetterInvesting
Posted by Mark on September 15, 2022 at 07:10 | Last modified: January 13, 2023 14:42I mentioned BetterInvesting in the last paragraph here. This is something I plan to use to help manage individual stock positions as part of a longer-term portfolio.
I subscribed to BetterInvesting late last year after finding the organization in fall 2021. From their website:
> Today NAIC [National Association of Investment Clubs] is known as BetterInvesting, a 501(c) (3)
> nonprofit association that remains dedicated to helping individuals and investment clubs learn and
> practice our fundamental approach to stock investing. Passionate volunteers, who follow our
> principles and practice the SSG [Stock Selection Guide], teach our educational programs. The SSG
> is available online 24/7, making it even easier to study and invest in stocks.
I don’t think of it like a service to be sold as much as I do a tool for analyzing stocks. Their approach covers a wide breadth and if someone is going to do fundamental analysis to any degree, then something to organize all the inputs and a process guiding what to do with them can be extremely valuable. One could do without such a tool and if this is something you try, then I’d be interested to hear what you do to get all the information and just how long it takes.
Personally, learning the BetterInvesting approach has given me a process to gather relevant data and make investment decisions in 1-2 hours per stock. Prior to this, I really had no idea. If I wanted to make decisions based on fundamental analysis, then I could look at suggested buy lists, perhaps investment newsletters (which I think are generally a waste of money), and some other places.
In 2001, I developed a stock screen after some book reading that was effectively the beginning to managing my own portfolio. It took me about an hour per week and I fared well with it on an absolute basis. I never revisited this once I discovered options in 2006. While I’ve thought about rekindling the original stock screening effort in recent years, I have also become a true believer in classic fundamental analysis for longer-term purposes. This is more the BetterInvesting way.
Any explanatory content I publish under the category BetterInvesting might be better studied in the video education library on their website since I’m a new member rather than an official instructor or volunteer. I might actually like to become a volunteer if it got me out to the community to teach the process to other like-minded individuals.
This would then beg the question whether their process is the best? I think we’re far away from having enough information to answer that question with any validity. I believe having a process and sticking with something repeatable to optimize efficiency is probably as good as anything else.
Categories: BetterInvesting® | Comments (0) | PermalinkBacktester Logic (Part 7)
Posted by Mark on September 12, 2022 at 06:54 | Last modified: June 22, 2022 08:35Today, I want to tie up some loose ends related to multiple topics.
The issue of trading options farther out in time (see paragraphs 3-4 of Part 6) will resurface later when I discuss time spread width. For now it will suffice to say that if I want to increase width, besides trading a longer-dated long option I can also do a shorter-dated short option when slippage (open interest, volume, liquidity, etc.) is a concern.
L71 (see Part 4) filters for a 10-point strike by requiring the remainder of strike price / 10 = 0. Due to liquidity concerns, I would prefer to trade only 25-point strikes. My second choice would be 10-, and 5-point strikes would be last. I don’t have actual execution data to support this—it’s just gut feeling based on what I’ve anecdotally heard from other traders.
One potential issue with filtering for strike multiple is a strike density decrease with lower price of the underlying. The relative difference between one 10-point strike and another generally gets larger going back in time where SPX is valued lower. With SPX at 4000, 10 points is only 0.25%. With SPX at 1500, though, 10 points is 0.67%. I cannot say how significant a concern this is, but it would be 2.5x worse for 25-point strikes. The base strategy is not intended to be directional.
Going back to L70, I no longer need the lower bound with my proposed solution in the second paragraph of Part 6. The program will find the first option that has less than (((mte + 1) * 30 ) + 5) DTE. The extra five along with 30 (two more than the more common 28-day) will cover a 35-day expiration cycle, which happens once per quarter. The option I want will be that or the next-lower DTE option to match. The latter will be checked by the check_second flag.
Rather than nested ifs, I could code as an if-else block per fifth paragraph of Part 4, but I don’t see any real advantage. Additional logic in the else block may dictate that nothing be executed for the current iteration (only 0.4% of rows are used). I would have to include a pass (null) statement or two (as if-elif-else) since if-else forces execution of one branch or the other.
In the second paragraph of Part 4, I said the update_long logic avoids a subsequent match to options on the same date even though the same-date options are not being skipped. That logic is in L134:
This looks to match strike price and expiration date. Recall the third paragraph here. Once find_short is complete, same-date options are avoided despite the wait_until_next_day flag being circumvented because each strike/expiration combination only appears once per historical date. Is this more or less efficient?
Although I may be wrong due to “mitigating factors” (second-to-last paragraph of Part 4), I would hypothesize matching strike price and expiration date to be less efficient than skipping dates with wait_until_next_day. The latter requires one truth test to compare current_date and historical date whereas update_long’s and statement requires two.
Loose ends be gone!
Categories: Python | Comments (0) | PermalinkBack from the Hack
Posted by Mark on September 9, 2022 at 06:37 | Last modified: January 13, 2023 13:52My apologies for any “404 error” you may have gotten recently when trying to navigate my website. The site was hacked by malware. I don’t know if it was down for days, for weeks, or for months. I subscribed to a security package and have been told that everything is now restored.
2022 has been a tough time for trading: my most difficult since beginning full-time in [and including] 2008. This year has left me staggered and raw. It’s attacked my positivity and hope. I will blog more about this in coming posts.
Despite the hit, I’m not ready to give up yet. Throwing out the baby with the bathwater is never a good idea and to that end, I still have work to do.
The Python backtester is on life support. You will continue to see posts on Python work I did earlier this year. For me, the dagger was realization that the data and corresponding results were irrevocably compromised.
While I haven’t practiced Python for several months, the next time I revisit will be my fourth “tour of duty.” I got farther the second time in 2020 than I did the first (2019). The third (2021-2022) was mostly practical application in working with the backtester. Hopefully the fourth comes easier and allows me to reach greater depths. Backtester aside, I have other projects in mind for which programming may be useful. Stay tuned for additional blog posts in the Python category.
Thankfully, some new automated backtesters have come onto the market. I have taken a good look at Option Omega, which has many appealing features. I also recently learned about MesoSim, which may be more tailored to my kind of trading.
I’m not really sure whether subscribing to one of these services solves the data issue. It’s at least likely to hide data flaws because close scrutiny of the backtrade log will be required. Making matters worse, some automated backtesters don’t provide such a log with essential data like entry and exit prices. This would allow for verification especially when trade results are highly discrepant from anything seen in ONE where the complete option chain is on display.
Either way, I have not entirely given up on backtesting and still have option strategies I’d like to study with an automated product. I will blog about this in the near future.
Something I have discovered in the past several months is BetterInvesting, which focuses on technamental [see Take Stock by Ellis Traub (2010)] analysis of stocks. Most important to me in this pursuit would be a repeatable process, which BetterInvesting provides. I don’t care so much what the process is because testing/validating the process would require [backtesting] tools that I’m not even sure exist. Regardless, you can expect some future blog posts in this area under the new category tag “BetterInvesting.”
Categories: About Me | Comments (0) | PermalinkBacktester Logic (Part 6)
Posted by Mark on September 6, 2022 at 06:45 | Last modified: June 22, 2022 08:35Today I want to finish ironing out the logic from L70 shown here then continue to analyze that conditional skeleton.
As an example of my proposed solution at the end of Part 5, in 2015 (date 16448) we have options with 94 DTE followed by options with 66 DTE. Both pass L70, which means the latter is what I want. I can add a Boolean flag check_second that starts out as False. Once int(float(stats[2])) no longer equals L_dte_orig (see key), the flag gets changed to True. If this passes L70 then find_long continues in this DTE. If it fails L70, then change control_flag to find_short and continue (to the next iteration) without setting wait_until_next_day to True.
In addition to the 60 – 95 DTE range, I am also interested in studying longer-dated time spreads despite potential issues with slippage. Examples include 90 – 125, 120 – 155, and 150 – 185 DTE. Live trading is the best way to understand slippage. Unfortunately, I can’t go back and live trade in previous years. Common wisdom suggests slippage will be lower with higher volume and open interest, but the data files don’t allow me to test this. I could plot volume and open interest over time, but this may be a waste of time since I have no way to know how that might translate to slippage.
Another thing that may limit my ability to backtest longer-dated options is historical availability. Over the years, more expirations and more strike prices have come available coincident [I suspect] with higher-volume option trading. In 2015, 168 DTE is available followed by 105 DTE, which means I can’t do a time spread one month wide. Things change in 2020. In early Jan 2015, monthly expirations appear for the first four months. In early Jan 2020, monthly expirations appear for the first six months. This expresses my concern despite being anecdotal observation.
One thing I don’t see in the conditional skeleton is a continue statement at the bottom. Whether or not the backtester identifies the current row as the long option, it can then advance to the next iteration (row). Without a continue statement, the program will go on to unnecessarily evaluate control_flag three more times before advancing. “Continue” should conclude the first three branches. Completing the block, the last branch doesn’t need a continue statement as advancing to the next iteration will automatically take place.*
I will press forward next time.
*—Actually, I did remember this. The Atom text editor features folding to hide blocks of code as described
here. With “continue” being the last line of the previously indented blocks, they were hidden.



