Python for Options Trading (5): Reducing risk on a losing long call

Roberto Gomes, PhD
6 min readJan 14, 2024

--

Image created by Bing Image Creator.

EDIT: OptionLab is undergoing extensive modifications to its source code, which impliest that the example showcased in this article does not work with the latest version. You can access the source code of the old library here. It is also possible to install the old version using pip, as shown below.

First and foremost, I would like to express my gratitude to you, my rare and esteemed reader, for being here and engaging with what will be my first article published on Medium in 2024. It comes in the heels of my previous article outlining a dynamic options strategy based on adjusting a winning long call to eliminate the risk of loss.

Indeed, in that aforementioned article, which, as of my writing this, stands as my most widely read and discussed work on this platform, I faced significant questioning and even encountered some rude criticism. A recurring inquiry, posed in varying degrees of politeness, was: “Alright, but wouldn’t that strategy only be effective if the price of the underlying asset increased? What if the price moved in the opposite direction?” My somewhat obvious response was: “In that scenario, we will need to look for a different type of options combination to, in the worst-case scenario, mitigate the risk.”

This article will delve into precisely that. Please, keep with me.

Before we proceed, the calculations presented in this article make use of the Python library OptionLab. Therefore, if you wish to run the code provided below, you need to first install OptionLab, which can be done through the command pip install optionlab==0.2.0.

The source code of the library is also available on the project’s GitHub page. Giving the project a star is always welcome.

OptionLab is free, open source code and is provided as is, with no guarantee that its results are accurate. As such, I am not responsible for any losses caused by the use of the code. I am open to hearing any questions, corrections, comments or suggestions about the code. You can also reach me on Linkedin.

Bugs can be reported as issues on the project’s GitHub page above.

Please bear in mind that options trading is a highly risky activity and should be approached with due diligence, as it can lead to significant losses. The content of this article is not a recommendation and is purely for educational purposes.

We will once again use Nvidia stock (ticker: NVDA) for illustrative purposes as the underlying asset. On January 16, 2023, this stock was trading at $168.99. Let’s assume that at some point before this date, you were bullish on Nvidia and purchased 100 call options with a strike price of $175, expiring on February 17, 2023, at $9 each. However, since then, these calls have depreciated, and by January 16, 2023, their value had fallen to only $7.55. This implies a loss, up to that point, of $1.45 per option contract. You were no longer as confident that the stock would appreciate enough to make this position profitable. In fact, the odds were not in your favor, as indicated by the result of the Python code snippet below.

# Import the required libraries
from optionlab.strategy import Strategy
import pandas as pd
import matplotlib.pyplot as plt

# Input data (Nvidia, NVDA)
stockprice=168.99
volatility=0.483
startdate="2023-01-16"
targetdate="2023-02-17"
interestrate=0.045
minstock=stockprice-100.0
maxstock=stockprice+100.0

st=Strategy()

# Current situation
strategy=[{"type":"call","strike":175.0,"premium":7.55,"n":100,"action":"buy",
"prevpos":9.00}]

st.getdata(stockprice=stockprice,startdate=startdate,
targetdate=targetdate,volatility=volatility,
interestrate=interestrate,minstock=minstock,
maxstock=maxstock,strategy=strategy)

out0=st.run()

print("Stock price profitable range at maturity: %.2f -> %.2f" %
(out0["ProfitRanges"][0][0],out0["ProfitRanges"][0][1]))
print("Maximum loss: %.2f" % (abs(out0["MinimumReturnInTheDomain"])))
print("Probability of profit: %.1f%%" % (out0["ProbabilityOfProfit"]*100.0))

The result:

Stock price profitable range at maturity: 184.01 -> inf
Maximum loss: 900.00
Probability of profit: 26.5%

For this long call to end in profit, Nvidia’s stock price on February 17, 2023, would need to be higher than $184. Using the Black-Scholes model, OptionLab estimated that the probability of this happening was a mere 26.5%. Since it is a long call, the maximum loss was $900.

In the controversial article where I showcased an options strategy with a Probability of Profit (PoP) of 100%, what we had was a winning long call where the risk of loss was — theoretically — completely eliminated. This was achieved by turning the long call into a kind of vertical spread, wherein calls with a higher strike, compared to the purchased calls, were sold.

To mitigate the risk of the current losing long call, something that could also be done (whether it is convenient or not is up to each individual to decide) is also selling calls with a higher strike. This trade can reduce the maximum loss, increase the PoP even by a small margin, albeit at the cost of limiting the potential profit, a characteristic typical of vertical spreads.

In this study, we will analyze the effect of selecting the strike for the sold leg using the following Python code snippet.

# Load the option chain data (calls only) from a .csv file
df=pd.read_csv("nvda_16-January-2023.csv")
chain=[]

for i,_ in enumerate(df["Expiration"]):
if df["Expiration"][i]==targetdate and df["Type"][i]=="call" and \
df["Strike"][i]>175.0:
chain.append([df["Strike"][i],df["Bid"][i]])

# Funtion that compute the adjustments of the long call
def compute_adjustment():
strikes=[]
losses=[]
profits=[]
PoPs=[]

for c in chain:
strategy=[{"type":"call","strike":175.0,"premium":7.55,"n":100,
"action":"buy","prevpos":9.00},
{"type":"call","strike":c[0],"premium":c[1],"n":100,
"action":"sell"}]

st.getdata(stockprice=stockprice,startdate=startdate,
targetdate=targetdate,volatility=volatility,
interestrate=interestrate,minstock=minstock,
maxstock=maxstock,strategy=strategy)

out=st.run()

strikes.append(c)
losses.append(abs(out["MinimumReturnInTheDomain"]))
profits.append(out["MaximumReturnInTheDomain"])
PoPs.append(out["ProbabilityOfProfit"]*100.0)

return strikes,losses,profits,PoPs

strikes,losses,profits,PoPs=compute_adjustment()

# Make plots
plt.xlabel("Strike")
plt.ylabel("Maximum loss")
plt.xlim(175,400)
plt.plot(strikes,losses)
plt.show()
plt.xlabel("Strike")
plt.ylabel("Maximum profit")
plt.xlim(175,400)
plt.plot(strikes,profits)
plt.show()
plt.ylabel("Probability of Profit")
plt.xlim(175,400)
plt.ylim(25,35)
plt.plot(strikes,PoPs)
plt.show()

Market data related to Nvidia’s option chain, obtained from Yahoo! Finance and stored in a .csv file, is loaded into a Pandas dataframe. Subsequently, all calls with a strike greater than $175 and expiring on February 17, 2023, are selected. The compute_adjustments() function essentially loops through each of these strikes, adding a short call to the already open long call. After calling the run() method of the Strategy object, the strikes, maximum losses, maximum profits, and PoPs of each of the calculated strategies are returned by compute_adjustments(). The results have been plotted in the following graphs.

Maximum loss as a function of strike price.

It is noticeable that the maximum loss increases as the strike of the options deviates from the long call’s strike, reaching a plateau of approximately $900, which is, in any case, the maximum loss of the purchased leg. This comes from the low value of these far OTM calls, which, once sold, don’t deduct significantly from the value paid for the purchased calls. The smallest maximum loss occurs for the $180 strike, the first one above the $175 long call strike, totaling $335 — just over a third of the maximum loss if no adjustment is made.

Maximum profit as a function of strike price.

It is not surprising at all that the maximum profit is also greatly influenced by the choice of the strike in the adjusted vertical spread. As the strike becomes more and more OTM, not only does the maximum loss increase, but the maximum possible profit does as well, reaching almost $8,500.

PoP as function of strike price.

Finally, the PoP proves to be less sensitive to the choice of the strike for composing the adjusted call spread. For the strike closest to the long call’s strike, the PoP reaches 34%, dropping to around 26.5% for the farthest OTM call.

In conclusion, for the initial position discussed in this article, consisting of a losing long call, adjustments through selling calls with higher strikes did not prove effective in significantly increasing the PoP. In the best-case scenario, the PoP reached one in three chances that the thus-created call spread would result in a profit. On the other hand, assuming a high likelihood of the trade ending in a loss, it was possible to at least reduce the maximum loss by almost two-thirds.

It is worth emphasizing: nothing above is a recommendation. This article is about a study, and its purpose is purely educational. It is crucial for the reader to study and make informed decisions rather than following trading prescriptions. Even when you know what you’re doing, it is still possible to lose money, let alone when engaging in activities you don’t understand just because some guru suggested it’s a good idea…

--

--

Roberto Gomes, PhD

I am a Professor of Materials Engineering specialized in computational research in materials science, physics, and engineering. Also interested in finance.