BrononymousEngineer

3D Options Plots

Jun 2nd, 2020
4,053
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 46.87 KB | None | 0 0
  1. '''
  2. Referemces:
  3. - https://www.cboe.com/micro/vix/vixwhite.pdf
  4. - https://en.wikipedia.org/wiki/Greeks_(finance)
  5. - https://en.wikipedia.org/wiki/Bisection_method
  6. - https://en.wikipedia.org/wiki/Newton%27s_method
  7. - https://quant.stackexchange.com/questions/7761/a-simple-formula-for-calculating-implied-volatility
  8. - https://www.risklatte.xyz/Articles/QuantitativeFinance/QF135.php
  9. - https://jakevdp.github.io/PythonDataScienceHandbook/04.12-three-dimensional-plotting.html
  10. - https://stackoverflow.com/questions/42677160/matplotlib-3d-scatter-plot-date
  11. '''
  12. import datetime as dt
  13. import math as m
  14.  
  15. import matplotlib.pyplot as plt
  16. import yfinance as yf
  17. from mpl_toolkits import mplot3d
  18. from scipy.stats import norm
  19.  
  20. plt.switch_backend('TkAgg')
  21.  
  22. '''
  23. Template for an option contract.
  24. '''
  25.  
  26.  
  27. class Option:
  28.     '''
  29.    Option contract 'object' according to the vanilla Black-Scholes-Merton model for the price of a European option.
  30.  
  31.    Required parameters:
  32.  
  33.    - current date
  34.        Starting date to use for the calculation of time left until expiration,
  35.        Must be a string with the format 'YYYY-MM-DD'.
  36.    - current time
  37.        Starting time to use for the calculation of time left until expiration.
  38.        Must be a string in 24 hour time with the format 'HH:MM'.
  39.        Time to expiration is calculated in minutes, and then converted to years.
  40.    - opt_type
  41.        The type of option.
  42.        Must be a string with the value of either 'call' or 'put'.
  43.    - exp
  44.        Expiration date of the option.
  45.        Must be a string with the format 'YYYY-MM-DD'
  46.    - V
  47.        Price of the option.
  48.        Must be a numerical value.
  49.    - S
  50.        Current price of the underlying stock or ETF.
  51.        Must be a numerical value.
  52.    - K
  53.        Strike price of the option.
  54.        Must be a numerical value.
  55.    - r
  56.        Annualized risk-free interest rate. Conventional wisdom is to use the interest rate of
  57.        treasury bills with time left to maturity equal to the length of time left to expiration
  58.        of the option.
  59.        Must be a numerical value in decimal form. Example: 1% would be entered as 0.01
  60.  
  61.    Optional parameters. Set these to 0 unless they are relevant (analyzing an entire option chain for example).
  62.  
  63.    - volume
  64.        Number of contracts traded today.
  65.        Must be an integer.
  66.    - openInterest
  67.        Number of outstanding contracts of the same type, strike, and expiration.
  68.        Must be an integer.
  69.    - bid
  70.        Current bid price for the contract.
  71.        Must be a numerical value.
  72.    - ask
  73.        Current asking price for the contract.
  74.        Must be a numerical value.
  75.  
  76.    **How this works**
  77.  
  78.    This is what takes place When input values are passed and a contract is instantiated:
  79.  
  80.        1) The following values are bound to the contract, available to call at any future point:
  81.           (For example: if you have created an option called "x", the expression "x.K" will return
  82.           the strike price of the option.)
  83.            - V
  84.            - S
  85.            - K
  86.            - t
  87.            - r
  88.            - itm  (returns True if the option is in the money or False if it is out of the money)
  89.            - date (current date that was entered into the contract)
  90.            - time (current time that was entered into the contract)
  91.            - exp  (expiration date of the contract)
  92.            - volume
  93.            - openInterest
  94.            - bid
  95.            - ask
  96.  
  97.        2) Implied volatility is iteratively calculated using a bisection algorithm. The Newton-Raphson
  98.           algorithm was initially used because it is very fast, but has problems converging for deep ITM
  99.           options, even with a good initial guess (analytical approximation).
  100.           - can be called like "x.IV" using the option called "x" from the previous example.
  101.  
  102.        3) The following greeks are calculated and also available to call:
  103.            - delta  (change in option price with respect to the change in underlying price)
  104.            - gamma  (change in delta with respect to the underlying price)
  105.            - theta  (change in option price with respect to time)
  106.            - vega   (change in option price with respect to implied volatility)
  107.            - rho    (change in option price with respect to the risk-free rate)
  108.            - Lambda (capitalized because python has a built in 'lambda' function,
  109.                      is the "leverage" that the contract provides)
  110.  
  111.            **Note: the formulas for the higher order greeks have not been rigorously verified for correctness
  112.  
  113.            - vanna  (change in delta with respect to implied volatility)
  114.            - charm  (change in delta with respect to time)
  115.            - vomma  (change in vega with respect to implied volatility)
  116.            - veta   (change in vega with respect to time)
  117.            - speed  (change in gamma with respect to the underlying price)
  118.            - zomma  (change in gamma with respect to implied volatility)
  119.            - color  (change in gamma with respect to time)
  120.            - ultima (change in vomma with respect to volatility)
  121.  
  122.    The following functions can also be called on the option:
  123.    (look at the functions themselves for further explanation)
  124.  
  125.        - theoPrice
  126.        - theoPnL
  127.        - impliedPrices
  128.  
  129.    This class can also be modified to take the dividend yield as an input. It is currently not used and set to 0.
  130.    '''
  131.     # TODO Make the impliedPrices function in terms of standard deviations
  132.     # TODO Create function that steps the contract through various times, IVs, and Ss
  133.  
  134.     def __init__(self, current_date, current_time, opt_type, exp, V, S, K, r, volume, openInterest, bid, ask):
  135.         '''
  136.        Sets all the attributes of the contract.
  137.        '''
  138.         self.opt_type = opt_type.lower()
  139.  
  140.         if self.opt_type == 'call':
  141.  
  142.             if K < S:
  143.                 self.itm = True
  144.  
  145.             else:
  146.                 self.itm = False
  147.  
  148.         elif self.opt_type == 'put':
  149.  
  150.             if S < K:
  151.                 self.itm = True
  152.  
  153.             else:
  154.                 self.itm = False
  155.  
  156.         self.exp = exp
  157.         self.V = round(V, 2)
  158.         self.S = round(S, 2)
  159.         self.K = round(K, 2)
  160.         self.date = current_date
  161.         self.time = current_time
  162.         self.exp = exp
  163.         self.t = self.__t(current_date, current_time, exp)
  164.         self.r = r
  165.         self.q = 0
  166.         self.volume = volume
  167.         self.openInterest = openInterest
  168.         self.bid = bid
  169.         self.ask = ask
  170.         vol_params = self.__BSMIV(self.S, self.t)
  171.         self.IV = vol_params[0]
  172.         self.vega = vol_params[1]
  173.         self.delta = self.__BSMdelta()
  174.         self.gamma = self.__BSMgamma()
  175.         self.theta = self.__BSMtheta()
  176.         self.rho = self.__BSMrho()
  177.         self.Lambda = self.__BSMlambda()
  178.         self.vanna = self.__BSMvanna()
  179.         self.charm = self.__BSMcharm()
  180.         self.vomma = self.__BSMvomma()
  181.         self.veta = self.__BSMveta()
  182.         self.speed = self.__BSMspeed()
  183.         self.zomma = self.__BSMzomma()
  184.         self.color = self.__BSMcolor()
  185.         self.ultima = self.__BSMultima()
  186.  
  187.     def __repr__(self):
  188.         '''
  189.        Basic contract information.
  190.        '''
  191.         return '${:.2f} strike {} option expiring {}.'.format(self.K,
  192.                                                               self.opt_type,
  193.                                                               self.exp)
  194.  
  195.     def __t(self, current_date, current_time, exp):
  196.         '''
  197.        Calculates the number of minutes to expiration, then converts to years.
  198.        Minutes are chosen because the VIX does this.
  199.        '''
  200.         hr, minute = 17, 30
  201.         year, month, day = [int(x) for x in exp.split('-')]
  202.         exp_dt = dt.datetime(year, month, day, hr, minute)
  203.  
  204.         hr, minute = [int(x) for x in current_time.split(':')]
  205.         year, month, day = [int(x) for x in current_date.split('-')]
  206.         current_dt = dt.datetime(year, month, day, hr, minute)
  207.  
  208.         days = 24*60*60*(exp_dt - current_dt).days
  209.         seconds = (exp_dt - current_dt).seconds
  210.  
  211.         return (days + seconds) / (365*24*60*60)
  212.  
  213.     def __d1(self, S, t, v):
  214.         '''
  215.        Struggling to come up with a good explanation.
  216.        It's an input into the cumulative distribution function.
  217.        '''
  218.         K = self.K
  219.         r = self.r
  220.         q = self.q
  221.  
  222.         return ((m.log(S / K) + (r - q + 0.5*v**2)*t)) / (v*m.sqrt(t))
  223.  
  224.     def __d2(self, S, t, v):
  225.         '''
  226.        Struggling to come up with a good explanation.
  227.        It's an input into the cumulative distribution function.
  228.        '''
  229.         d1 = self.__d1(S, t, v)
  230.  
  231.         return d1 - v*m.sqrt(t)
  232.  
  233.     def __pvK(self, t):
  234.         '''
  235.        Present value (pv) of the strike price (K)
  236.        '''
  237.         K = self.K
  238.         r = self.r
  239.         return K*m.exp(-r*t)
  240.  
  241.     def __pvS(self, S, t):
  242.         '''
  243.        Present value (pv) of the stock price (S)
  244.        '''
  245.         q = self.q
  246.  
  247.         return S*m.exp(-q*t)
  248.  
  249.     def __phi(self, x): return norm.pdf(x)
  250.  
  251.     def __N(self, x): return norm.cdf(x)
  252.  
  253.     def __BSMprice(self, S, t, v):
  254.         '''
  255.        Black-Scholes-Merton price of a European call or put.
  256.        '''
  257.         K = self.K
  258.         pvK = self.__pvK(t)
  259.         pvS = self.__pvS(S, t)
  260.  
  261.         if t != 0:
  262.  
  263.             if self.opt_type == 'call':
  264.                 Nd1 = self.__N(self.__d1(S, t, v))
  265.                 Nd2 = -self.__N(self.__d2(S, t, v))
  266.  
  267.             elif self.opt_type == 'put':
  268.                 Nd1 = -self.__N(-self.__d1(S, t, v))
  269.                 Nd2 = self.__N(-self.__d2(S, t, v))
  270.  
  271.             return round(pvS*Nd1 + pvK*Nd2, 2)
  272.  
  273.         elif t == 0:
  274.  
  275.             if self.opt_type == 'call':
  276.                 intrinsic_value = max(0, S - K)
  277.  
  278.             elif self.opt_type == 'put':
  279.                 intrinsic_value = max(0, K - S)
  280.  
  281.             return round(intrinsic_value, 2)
  282.  
  283.     # First order greek
  284.     def __BSMvega(self, S, t, v):
  285.         '''
  286.        First derivative of the option price (V) with respect to implied volatility (v or IV).
  287.  
  288.        - For a 1% increase in IV, how much will the option price rise?
  289.        '''
  290.         pvK = self.__pvK(t)
  291.         phid2 = self.__phi(self.__d2(S, t, v))
  292.  
  293.         return round((pvK*phid2*m.sqrt(t)) / 100, 4)
  294.  
  295.     def __BSMIV(self, S, t):
  296.         '''
  297.        Volatility implied by the price of the option (v/IV).
  298.  
  299.        - For the option price (V) to be fair, how volatile does the underlying (S) need to be?
  300.  
  301.        Note: this function also returns vega, because initially the Newton-Raphson method was used, and
  302.        that requires vega to be solved at the same time. I didn't feel like rewriting things so I just
  303.        tacked on the vega calculation after IV was calculated.
  304.        '''
  305.         V = self.V
  306.         # K = self.K
  307.  
  308.         # Bisection method
  309.  
  310.         # We are trying to solve the error function which is the difference between the estimated price and the actual price
  311.         # V_est - V
  312.         # We need to generate two initial guesses at IV, one with a positive error and one with a negative error
  313.         # Should be reasonable to assume that vol will be somewhere between 1% and 2000%
  314.         v_lo = 0
  315.         v_hi = 20
  316.         v_mid = 0.5*(v_lo + v_hi)
  317.         V_mid = self.__BSMprice(S, t, v_mid)
  318.         error = V_mid - V
  319.  
  320.         # Keep iterating until the error in estimated price is less than a cent
  321.         while v_hi - v_lo >= 0.1 / 100:
  322.  
  323.             if error > 0:
  324.                 v_hi = v_mid
  325.  
  326.             elif error < 0:
  327.                 v_lo = v_mid
  328.  
  329.             elif error == 0:
  330.                 break
  331.  
  332.             v_mid = 0.5*(v_hi + v_lo)
  333.             V_mid = self.__BSMprice(S, t, v_mid)
  334.             error = V_mid - V
  335.  
  336.         vega = self.__BSMvega(S, t, v_mid)
  337.  
  338.         # Newton-Raphson method
  339.         # Kind of a mess because I was trying different things to get it to converge better for certain deep ITM options.
  340.         # v = (m.sqrt(2*m.pi) / t)*(V / S)
  341.         # min_err = 1000000
  342.         # best_v = 0
  343.         # best_vega = 0
  344.  
  345.         # i = 0
  346.         # if self.opt_type == 'call':
  347.         #     if V < S - K:
  348.         #         V = S - K
  349.  
  350.         # elif self.opt_type == 'put':
  351.         #     if V < K - S:
  352.         #         V = K - S
  353.  
  354.         # while i < 5000:
  355.         #     V_est = max(self.__BSMprice(S, t, v), 0.01)
  356.         #     vega = max(self.__BSMvega(S, t, v), 0.0001)
  357.         #     error = V_est - V
  358.  
  359.         #     if abs(error) < min_err:
  360.         #         min_err = abs(error)
  361.         #         best_v = v
  362.         #         best_vega = vega
  363.  
  364.         #     if error == 0:
  365.         #         break
  366.  
  367.         #     else:
  368.         #         v = v - (error/(vega*100))*0.25
  369.  
  370.         #     if (i == 4999) & (error != 0):
  371.         #         print('error in IV loop')
  372.  
  373.         #     i += 1
  374.  
  375.         # return round(best_v, 4), round(best_vega, 4)
  376.         return round(v_mid, 4), round(vega, 4)
  377.  
  378.     # First order greek
  379.     def __BSMdelta(self):
  380.         '''
  381.        First derivative of the option price (V) with respect to
  382.        the underlying price (S).
  383.  
  384.        - For a $1 increase in S, how much will V rise?
  385.  
  386.        - Also is the risk neutral probability S is at or below K by expiration.
  387.  
  388.        Note that a risk neutral probability is not a real life probability.
  389.        It is simply the probability that would exist if it were possible to
  390.        create a completely risk free portfolio.
  391.        '''
  392.         S = self.S
  393.         t = self.t
  394.         v = self.IV
  395.  
  396.         if self.opt_type == 'call':
  397.             Nd1 = self.__N(self.__d1(S, t, v))
  398.  
  399.         elif self.opt_type == 'put':
  400.             Nd1 = -self.__N(-self.__d1(S, t, v))
  401.  
  402.         return round(Nd1, 4)
  403.  
  404.     # Second order greek
  405.     def __BSMgamma(self):
  406.         '''
  407.        First dertivative of delta with respect to the price of the underlying (S).
  408.        Second derivative of the option price (V) with respect to the stock price.
  409.  
  410.        - For a $1 increase in the stock price, how much will delta increase?
  411.        '''
  412.         S = self.S
  413.         t = self.t
  414.         v = self.IV
  415.         pvK = self.__pvK(t)
  416.         phid2 = self.__phi(self.__d2(S, t, v))
  417.  
  418.         return round((pvK*phid2) / (S**2*v*m.sqrt(t)), 4)
  419.  
  420.     # First order greek
  421.     def __BSMtheta(self):
  422.         '''
  423.        First derivative of the option price (V) with respect to time (t).
  424.  
  425.        - How much less will the option be worth tomorrow?
  426.        '''
  427.         S = self.S
  428.         t = self.t
  429.         v = self.IV
  430.         pvK = self.__pvK(t)
  431.         pvS = self.__pvS(S, t)
  432.  
  433.         if self.opt_type == 'call':
  434.             phid1 = self.__phi(self.__d1(S, t, v))
  435.             r = -self.r
  436.             q = self.q
  437.             Nd1 = self.__N(self.__d1(S, t, v))
  438.             Nd2 = self.__N(self.__d2(S, t, v))
  439.  
  440.         elif self.opt_type == 'put':
  441.             phid1 = self.__phi(-self.__d1(S, t, v))
  442.             r = self.r
  443.             q = -self.q
  444.             Nd1 = self.__N(-self.__d1(S, t, v))
  445.             Nd2 = self.__N(-self.__d2(S, t, v))
  446.  
  447.         return round((-((pvS*phid1*v) / (2*m.sqrt(t))) + r*pvK*Nd2 + q*pvS*Nd1) / 365, 4)
  448.  
  449.     # First order greek
  450.     def __BSMrho(self):
  451.         '''
  452.        First derivative of the option price (V) with respect to the risk free interest rate (r).
  453.  
  454.        - For a 1% change in interest rates, by how many dollars will the value of the option change?
  455.        '''
  456.         S = self.S
  457.         t = self.t
  458.         v = self.IV
  459.  
  460.         if self.opt_type == 'call':
  461.             pvK = self.__pvK(t)
  462.             Nd2 = self.__N(self.__d2(S, t, v))
  463.  
  464.         elif self.opt_type == 'put':
  465.             pvK = -self.__pvK(t)
  466.             Nd2 = self.__N(-self.__d2(S, t, v))
  467.  
  468.         return round((pvK*t*Nd2) / 100, 4)
  469.  
  470.     # First order greek
  471.     def __BSMlambda(self):
  472.         '''
  473.        Measures the percentage change in the option price (V) per percentage change
  474.        in the price of the underlying (S).
  475.  
  476.        - How much leverage does this option have?
  477.        '''
  478.         V = self.V
  479.         S = self.S
  480.         delta = self.delta
  481.  
  482.         return round(delta*(S / V), 4)
  483.  
  484.     # Second order greek
  485.     def __BSMvanna(self):
  486.         '''
  487.        First derivative of delta with respect to implied volatility.
  488.  
  489.        - If volatility changes by 1%, how much will delta change?
  490.        '''
  491.         V = self.V
  492.         S = self.S
  493.         t = self.t
  494.         v = self.IV
  495.         d1 = self.__d1(S, t, v)
  496.  
  497.         return round((V / S)*(1 - (d1 / (v*m.sqrt(t)))), 4)
  498.  
  499.     # Second order greek
  500.     def __BSMcharm(self):
  501.         '''
  502.        First derivative of delta with respect to time.
  503.  
  504.        - How much different will delta be tomorrow if everything else stays the same?
  505.        - Also can think of it as 'delta decay'
  506.        '''
  507.         S = self.S
  508.         t = self.t
  509.         r = self.r
  510.         v = self.IV
  511.         pv = m.exp(-self.q*t)
  512.         phid1 = self.__phi(self.__d1(S, t, v))
  513.         d2 = self.__d2(S, t, v)
  514.         mess = (2*(r - self.q)*t - d2*v*m.sqrt(t)) / (2*t*v*m.sqrt(t))
  515.  
  516.         if self.opt_type == 'call':
  517.             q = self.q
  518.             Nd1 = self.__N(self.__d1(S, t, v))
  519.  
  520.         elif self.opt_type == 'put':
  521.             q = -self.q
  522.             Nd1 = self.__N(-self.__d1(S, t, v))
  523.  
  524.         return round((q*pv*Nd1 - pv*phid1*mess) / 365, 4)
  525.  
  526.     # Second order greek
  527.     def __BSMvomma(self):
  528.         '''
  529.        First derivative of vega with respect to implied volatility.
  530.        Also the second derivative of the option price (V) with respect to
  531.        implied volatility.
  532.  
  533.        - If IV changes by 1%, how will vega change?
  534.        '''
  535.         S = self.S
  536.         t = self.t
  537.         vega = self.vega
  538.         v = self.IV
  539.         d1 = self.__d1(S, t, v)
  540.         d2 = self.__d2(S, t, v)
  541.  
  542.         return round((vega*d1*d2) / v, 4)
  543.  
  544.     # Second order greek
  545.     def __BSMveta(self):
  546.         '''
  547.        First derivative of vega with respect to time (t).
  548.  
  549.        - How much different will vega be tomorrow if everything else stays the same?
  550.        '''
  551.         S = self.S
  552.         t = self.t
  553.         v = self.IV
  554.         pvS = self.__pvS(S, t,)
  555.         d1 = self.__d1(S, t, v)
  556.         d2 = self.__d2(S, t, v)
  557.         phid1 = self.__phi(d1)
  558.         r = self.r
  559.         q = self.q
  560.         mess1 = ((r - q)*d1) / (v*m.sqrt(t))
  561.         mess2 = (1 + d1*d2) / (2*t)
  562.  
  563.         return round((-pvS*phid1*m.sqrt(t)*(q + mess1 - mess2)) / (100*365), 4)
  564.  
  565.     # Third order greek
  566.     def __BSMspeed(self):
  567.         '''
  568.        First derivative of gamma with respect to the underlying price (S).
  569.  
  570.        - If S increases by $1, how will gamma change?
  571.        '''
  572.         gamma = self.gamma
  573.         S = self.S
  574.         v = self.IV
  575.         t = self.t
  576.         d1 = self.__d1(S, t, v)
  577.  
  578.         return round(-(gamma / S)*((d1 / (v*m.sqrt(t))) + 1), 4)
  579.  
  580.     # Third order greek
  581.     def __BSMzomma(self):
  582.         '''
  583.        First derivative of gamma with respect to implied volatility.
  584.  
  585.        - If volatility changes by 1%, how will gamma change?
  586.        '''
  587.         gamma = self.gamma
  588.         S = self.S
  589.         t = self.t
  590.         v = self.IV
  591.         d1 = self.__d1(S, t, v)
  592.         d2 = self.__d2(S, t, v)
  593.  
  594.         return round(gamma*((d1*d2 - 1) / v), 4)
  595.  
  596.     # Third order greek
  597.     def __BSMcolor(self):
  598.         '''
  599.        First derivative of gamma with respect to time.
  600.  
  601.        - How much different will gamma be tomorrow if everything else stays the same?
  602.        '''
  603.         S = self.S
  604.         t = self.t
  605.         r = self.r
  606.         v = self.IV
  607.         q = self.q
  608.         pv = m.exp(-q*t)
  609.         d1 = self.__d1(S, t, v)
  610.         d2 = self.__d2(S, t, v)
  611.         phid1 = self.__phi(d1)
  612.         mess = ((2*(r - q)*t - d2*v*m.sqrt(t)) / (v*m.sqrt(t)))*d1
  613.  
  614.         return round((-pv*(phid1 / (2*S*t*v*m.sqrt(t)))*(2*q*t + 1 + mess)) / 365, 4)
  615.  
  616.     # Third order greek
  617.     def __BSMultima(self):
  618.         '''
  619.        First derivative of vomma with respect to volatility.
  620.  
  621.        - ...why? At this point it just seems like an exercise in calculus.
  622.        '''
  623.         vega = self.vega
  624.         S = self.S
  625.         t = self.t
  626.         v = self.IV
  627.         d1 = self.__d1(S, t, v)
  628.         d2 = self.__d2(S, t, v)
  629.  
  630.         return round((-vega/(v**2))*(d1*d2*(1 - d1*d2) + d1**2 + d2**2), 4)
  631.  
  632.     def theoPrice(self, date, S, v):
  633.         '''
  634.        Calculates the theoretical price of the option given:
  635.  
  636.        - date 'YYYY-MM-DD'
  637.        - underlying price (S)
  638.        - implied volatility (v)
  639.        '''
  640.         year, month, day = date.split('-')
  641.         date = dt.datetime(int(year), int(month), int(day))
  642.         year, month, day = self.exp.split('-')
  643.         exp = dt.datetime(int(year), int(month), int(day))
  644.  
  645.         t = (exp - date).days / 365
  646.  
  647.         return self.__BSMprice(S, t, v)
  648.  
  649.     def theoPnL(self, date, S, v):
  650.         '''
  651.        Calculates the theoretical profit/loss given:
  652.  
  653.        - date 'YYYY-MM-DD'
  654.        - underlying price (S)
  655.        - implied volatility (v)
  656.        '''
  657.         return round(self.theoPrice(date, S, v) - self.V, 2)
  658.  
  659.     def impliedPrices(self, show):
  660.         '''
  661.        Returns a tuple containing two lists.
  662.  
  663.        - list[0] = dates from tomorrow until expiration
  664.        - list[1] = price on each date
  665.  
  666.        - show = True  | plots prices over time
  667.        - show = False | does not plot prices over time
  668.  
  669.        If implied volatility is 20% for an option expiring in 1 year, this means that
  670.        the market is implying 1 year from now, there is roughly a 68% chance the underlying
  671.        will be 20% higher or lower than it currently is.
  672.  
  673.        We can scale this annual number to any timeframe of interest according to v*sqrt(days/365).
  674.        The denominator is 365 because calendar days are used for simplicity.
  675.  
  676.        If only trading days were to be taken into account, the denominator would be 252.
  677.        '''
  678.         S = self.S
  679.         v = self.IV
  680.         t = self.t
  681.  
  682.         days = [i + 1 for i in range(int(t*365))]
  683.  
  684.         if self.opt_type == 'call':
  685.             prices = [round(S + v*m.sqrt(day / 365)*S, 2) for day in days]
  686.  
  687.         elif self.opt_type == 'put':
  688.             prices = [round(S - v*m.sqrt(day / 365)*S, 2) for day in days]
  689.  
  690.         today = dt.datetime.now()
  691.         dates = [(today + dt.timedelta(days=day)).date() for day in days]
  692.  
  693.         if show == True:
  694.             plt.title('Implied moves according to a:\n{}\nLast price: ${:.2f} | IV: {:.2f}%'.format(
  695.                 self.__repr__(), self.V, 100*self.IV))
  696.  
  697.             plt.xlabel('Date')
  698.             plt.ylabel('Spot price ($)')
  699.             plt.plot(dates, prices)
  700.             plt.show(block=True)
  701.  
  702.         return dates, prices
  703.  
  704.  
  705. '''
  706. Plotting
  707. '''
  708.  
  709.  
  710. def date_time_input():
  711.     '''
  712.    Get either current time or user specified time.
  713.    It's a lot of code and used for both plot mode and single option mode.
  714.  
  715.    returns current_date 'YYYY-MM-DD', current_time 'HH:MM'
  716.  
  717.    Note: this is done better in single_option_input() for the expiration but I don't feel like refactoring right now
  718.    '''
  719.     # Print description of the date and time to be input
  720.     print('\n"Time" refers to the time to use for the time to expiration calculation.')
  721.     print('Example: if it is currently the weekend, and you want to see the metrics')
  722.     print('based on EOD Friday (which is what the prices will be from), enter "1",')
  723.     print('and enter the date of the most recent Friday, with 16:00 as the time (4pm).\n')
  724.  
  725.     # Get date
  726.     which_datetime_string = 'Enter 0 to use current date/time, 1 to specify date/time: '
  727.     which_datetime = input(which_datetime_string)
  728.  
  729.     datetime_options = ['0', '1']
  730.  
  731.     # If incorrect input is supplied, loop until the input is correct
  732.     while which_datetime not in datetime_options:
  733.         which_datetime = input(which_datetime_string)
  734.  
  735.     # If the current date/time is to be used, get the current date/time
  736.     if which_datetime == '0':
  737.         now = dt.datetime.now()
  738.         current_date = str(now.date())
  739.         current_time = '{}:{}'.format(now.time().hour, now.time().minute)
  740.  
  741.     # If the date/time is to be specified
  742.     elif which_datetime == '1':
  743.         # Get current date
  744.         current_date_string = 'Enter current date [YYYY-MM-DD]: '
  745.         current_date = input(current_date_string)
  746.  
  747.         try:
  748.             # Check if the date is in the correct format
  749.             year, month, day = [int(x) for x in current_date.split('-')]
  750.             dt.datetime(year, month, day)
  751.  
  752.         except:
  753.             # If not, loop until the format is correct
  754.             stop_loop = 0
  755.  
  756.             while stop_loop == 0:
  757.                 current_date = input(current_date_string)
  758.  
  759.                 try:
  760.                     year, month, day = [int(x)
  761.                                         for x in current_date.split('-')]
  762.                     dt.datetime(year, month, day)
  763.                     stop_loop = 1
  764.  
  765.                 except:
  766.                     stop_loop = 0
  767.         # Get current time
  768.         current_time_string = 'Enter current 24H time [HH:MM]: '
  769.         current_time = input(current_time_string)
  770.  
  771.         try:
  772.             # Check if the time is in the correct format
  773.             hour, minute = [int(x) for x in current_time.split(':')]
  774.             dt.datetime(year, month, day, hour, minute)
  775.  
  776.         except:
  777.             # If not, loop until the format is correct
  778.             stop_loop = 0
  779.  
  780.             while stop_loop == 0:
  781.                 current_time = input(current_time_string)
  782.  
  783.                 try:
  784.                     hour, minute = [int(x) for x in current_time.split(':')]
  785.                     dt.datetime(year, month, day, hour, minute)
  786.                     stop_loop = 1
  787.  
  788.                 except:
  789.                     stop_loop = 0
  790.  
  791.     return current_date, current_time
  792.  
  793.  
  794. def multi_plot_input():
  795.     '''
  796.    User input for:
  797.        - ticker
  798.        - params
  799.        - price_type
  800.        - opt_type
  801.        - current_date
  802.        - current_time
  803.        - r
  804.    Returns each of the parameters in a tuple
  805.    '''
  806.     # Get ticker
  807.     ticker_string = '\nEnter ticker symbol: '
  808.     ticker = input(ticker_string).upper()
  809.  
  810.     try:
  811.         # Try getting the first options expiration of the ticker
  812.         # If this succeeds we know it is a valid, optionable ticker symbol
  813.         yf.Ticker(ticker).options[0]
  814.  
  815.     except:
  816.         # Run an input loop until a valid, optionable ticker is input
  817.         stop_loop = 0
  818.  
  819.         while stop_loop == 0:
  820.             print('Ticker symbol is either invalid or not optionable.')
  821.             ticker = input(ticker_string).upper()
  822.  
  823.             try:
  824.                 yf.Ticker(ticker).options[0]
  825.                 stop_loop = 1
  826.  
  827.             except:
  828.                 stop_loop = 0
  829.  
  830.     # Print out a description of what can be plotted
  831.     print('\nStandard Parameters     |     Nonstandard Parameters')
  832.     print('    last or mid price   |       rho    [dV/dr]')
  833.     print('    IV                  |       charm  [ddelta/dt]')
  834.     print('    delta               |       veta   [dvega/dt]')
  835.     print('    theta               |       color  [dgamma/dt]')
  836.     print('    volume              |       speed  [dgamma/dS]')
  837.     print('    vega                |       vanna  [ddelta/dv]')
  838.     print('    gamma               |       vomma  [dvega/dv]')
  839.     print('    Open Interest       |       zomma  [dgamma/dv]\n')
  840.  
  841.     # Get parameters to plot
  842.     param_string = 'Enter 0 for standard parameters, 1 for nonstandard: '
  843.     param_type = input(param_string)
  844.  
  845.     # If incorrect input is supplied, loop until the input is correct
  846.     while param_type not in ['0', '1']:
  847.         param_type = input(param_string)
  848.  
  849.     # Set parameters
  850.     if param_type == '0':
  851.         params = [
  852.             'V',
  853.             'IV',
  854.             'delta',
  855.             'theta',
  856.             'volume',
  857.             'vega',
  858.             'gamma',
  859.             'openInterest'
  860.         ]
  861.  
  862.     elif param_type == '1':
  863.         print('\n**Warning: accuracy of higher order greeks has not been verified**\n')
  864.         params = [
  865.             'rho',
  866.             'charm',
  867.             'veta',
  868.             'color',
  869.             'speed',
  870.             'vanna',
  871.             'vomma',
  872.             'zomma'
  873.         ]
  874.  
  875.     # Get price type
  876.     price_type_string = 'Enter price to use for calcs [mid or last]: '
  877.     price_type = input(price_type_string)
  878.  
  879.     # If incorrect input is supplied, loop until the input is correct
  880.     while price_type not in ['mid', 'last']:
  881.         price_type = input(price_type_string)
  882.  
  883.     # Get option type
  884.     opt_type_string = 'Enter option type [calls, puts, or both]: '
  885.     opt_type = input(opt_type_string)
  886.  
  887.     # If incorrect input is supplied, loop until the input is correct
  888.     while opt_type not in ['calls', 'puts', 'both']:
  889.         opt_type = input(opt_type_string)
  890.  
  891.     # Get moneyness of options to plot
  892.     moneyess_string = 'Enter the moneyness to plot [itm, otm, or both]: '
  893.     moneyness = input(moneyess_string).lower()
  894.  
  895.     # If incorrect input is supplied, loop until the input is correct
  896.     while moneyness not in ['itm', 'otm', 'both']:
  897.         moneyness = input(moneyess_string).lower()
  898.  
  899.     # Get risk free rate
  900.     # If incorrect input is supplied, loop until the input is correct
  901.  
  902.     stop_loop = 0
  903.  
  904.     while stop_loop == 0:
  905.         r = input('Enter the risk-free rate: ')
  906.  
  907.         try:
  908.             r = float(r)
  909.             stop_loop = 1
  910.  
  911.         except:
  912.             continue
  913.  
  914.     # Get date/time
  915.     current_date, current_time = date_time_input()
  916.  
  917.     #  Return the parameters
  918.     return ticker, params, price_type, opt_type, moneyness, current_date, current_time, r
  919.  
  920.  
  921. def single_option_input():
  922.     # Get option type
  923.     opt_type_string = 'Enter option type [put or call]: '
  924.     opt_type = input(opt_type_string)
  925.  
  926.     while opt_type not in ['put', 'call']:
  927.         opt_type = input(opt_type_string)
  928.  
  929.     # Get expiration date
  930.     exp_string = 'Enter expiration date [YYYY-MM-DD]: '
  931.     exp = input(exp_string)
  932.     stop_loop = 0
  933.  
  934.     while stop_loop == 0:
  935.  
  936.         try:
  937.             year, month, day = [int(x) for x in exp.split('-')]
  938.             dt.datetime(year, month, day)
  939.             stop_loop = 1
  940.  
  941.         except:
  942.             exp = input(exp_string)
  943.  
  944.     # Get option price
  945.     V_string = 'Enter option price: '
  946.     V = input(V_string)
  947.     stop_loop = 0
  948.  
  949.     while stop_loop == 0:
  950.  
  951.         try:
  952.             V = float(V)
  953.             stop_loop = 1
  954.  
  955.         except:
  956.             V = input(V_string)
  957.  
  958.     # Get stock price
  959.     S_string = 'Enter the stock price: '
  960.     S = input(S_string)
  961.     stop_loop = 0
  962.  
  963.     while stop_loop == 0:
  964.  
  965.         try:
  966.             S = float(S)
  967.             stop_loop = 1
  968.  
  969.         except:
  970.             S = input(S_string)
  971.  
  972.     # Get strike price
  973.     K_string = 'Enter the strike price: '
  974.     K = input(K_string)
  975.     stop_loop = 0
  976.  
  977.     while stop_loop == 0:
  978.  
  979.         try:
  980.             K = float(K)
  981.             stop_loop = 1
  982.  
  983.         except:
  984.             K = input(K_string)
  985.  
  986.     #  Get risk-free rate
  987.     r_string = 'Enter the risk free rate: '
  988.     r = input(r_string)
  989.     stop_loop = 0
  990.  
  991.     while stop_loop == 0:
  992.  
  993.         try:
  994.             r = float(r)
  995.             stop_loop = 1
  996.  
  997.         except:
  998.             r = input(r_string)
  999.  
  1000.     # Get date/time
  1001.     current_date, current_time = date_time_input()
  1002.  
  1003.     return current_date, current_time, opt_type, exp, V, S, K, r
  1004.  
  1005.  
  1006. def get_options(current_date, current_time, ticker, opt_type, price_type, r):
  1007.     '''
  1008.    See the "Option" class for an explanation of the inputs.
  1009.  
  1010.    Returns a dictionary of calls, puts, or both.
  1011.    Each option type is itself a dictionary of lists.
  1012.  
  1013.    Example:
  1014.        main_dict[calls] = calls_dict
  1015.        calls_dict['2020-06-19 call options'] = list of call option objects expiring Jun19 2020
  1016.    '''
  1017.     # Get ticker object
  1018.     ticker = yf.Ticker(ticker)
  1019.  
  1020.     S = ticker.info['regularMarketPrice']
  1021.  
  1022.     # Get exps
  1023.     exps = ticker.options
  1024.  
  1025.     # This dict will hold all the objects across dates
  1026.     call_objects = {}
  1027.     put_objects = {}
  1028.  
  1029.     # Get option chains
  1030.     for exp in exps:
  1031.         # For some reason the expiration dates that Yahoo! returns are a day early
  1032.         # This just adds a day to correct it
  1033.         year, month, day = [int(x) for x in exp.split('-')]
  1034.         corrected_exp = str(
  1035.             (dt.datetime(year, month, day) + dt.timedelta(days=1)).date())
  1036.  
  1037.         print('Getting data for options expiring {}...'.format(corrected_exp))
  1038.  
  1039.         # API call to Yahoo! for the option chain
  1040.         option_chain = ticker.option_chain(exp)
  1041.  
  1042.         # Split off the puts and calls dataframes
  1043.         calls = option_chain.calls.fillna(0)
  1044.         puts = option_chain.puts.fillna(0)
  1045.  
  1046.         # Delete the original combined chain to save memory
  1047.         del option_chain
  1048.  
  1049.         # Make a temporary list to hold the option objects
  1050.         single_call_chain = []
  1051.         single_put_chain = []
  1052.  
  1053.         # Loop through call strikes and create option objects for each contract
  1054.         for i in range(len(calls)):
  1055.             # Get parameters
  1056.             K = calls['strike'].iloc[i]
  1057.             volume = calls['volume'].iloc[i]
  1058.             openInterest = calls['openInterest'].iloc[i]
  1059.             bid = calls['bid'].iloc[i]
  1060.             ask = calls['ask'].iloc[i]
  1061.             last = calls['lastPrice'].iloc[i]
  1062.  
  1063.             # Get mid or last price
  1064.             if price_type == 'mid':
  1065.                 V = max(round((bid + ask) / 2, 2), 0.01)
  1066.  
  1067.             else:
  1068.                 V = max(last, 0.01)
  1069.  
  1070.             # Create and add the call object to the temp list
  1071.             single_call_chain.append(Option(current_date,
  1072.                                             current_time,
  1073.                                             'call',
  1074.                                             corrected_exp,
  1075.                                             V, S, K, r,
  1076.                                             volume,
  1077.                                             openInterest,
  1078.                                             bid, ask))
  1079.  
  1080.         # Loop through put strikes and create option objects for each contract
  1081.         for i in range(len(puts)):
  1082.             # Get parameters
  1083.             K = puts['strike'].iloc[i]
  1084.             volume = puts['volume'].iloc[i]
  1085.             openInterest = puts['openInterest'].iloc[i]
  1086.             bid = puts['bid'].iloc[i]
  1087.             ask = puts['ask'].iloc[i]
  1088.             last = puts['lastPrice'].iloc[i]
  1089.  
  1090.             # Get mid or last price
  1091.             if price_type == 'mid':
  1092.                 V = max(round((bid + ask) / 2, 2), 0.01)
  1093.  
  1094.             else:
  1095.                 V = max(last, 0.01)
  1096.  
  1097.             # Create and add the call object to the temp list
  1098.             single_put_chain.append(Option(current_date,
  1099.                                            current_time,
  1100.                                            'put',
  1101.                                            corrected_exp,
  1102.                                            V, S, K, r,
  1103.                                            volume,
  1104.                                            openInterest,
  1105.                                            bid, ask))
  1106.  
  1107.         # Add the call and put temp lists to the dictionary
  1108.         # Indexed by expiration date
  1109.         call_objects['{} {} options'.format(
  1110.             corrected_exp, 'call')] = single_call_chain
  1111.  
  1112.         put_objects['{} {} options'.format(
  1113.             corrected_exp, 'put')] = single_put_chain
  1114.  
  1115.         # Delete puts and calls
  1116.         del calls
  1117.         del puts
  1118.  
  1119.     # Return the requested option type(s)
  1120.     if opt_type == 'calls':
  1121.         return {'calls': call_objects}
  1122.  
  1123.     elif opt_type == 'puts':
  1124.         return {'puts': put_objects}
  1125.  
  1126.     else:
  1127.         return {'calls': call_objects, 'puts': put_objects}
  1128.  
  1129.  
  1130. def generate_plots(ticker, options, moneyness, params, price_type, current_date, current_time):
  1131.     # Set chart layout
  1132.     layout = [2, 4]
  1133.     rows = layout[0]
  1134.     cols = layout[1]
  1135.     fig = plt.figure()
  1136.  
  1137.     # Make subplot titles from parameters
  1138.     titles = [x.capitalize() if x != 'IV' else x for x in params]
  1139.  
  1140.     if 'V' in titles:
  1141.         titles[params.index('V')] = price_type.capitalize() + ' Price'
  1142.         titles[params.index('openInterest')] = 'Open Interest'
  1143.  
  1144.     # See which option types are to be plotted
  1145.     opt_types = list(options.keys())
  1146.  
  1147.     # Gets set to False after the very first iteration of parameters
  1148.     # For initially setting up plots
  1149.     first_iter = True
  1150.  
  1151.     # Initialize list of expirations for the y-axis labels
  1152.     exps = []
  1153.     # Counter to make sure that all expirations are added to the list once
  1154.     k = 0
  1155.  
  1156.     # The main title depends on which option types are plotted
  1157.     # The calls and puts portions will be appended as needed
  1158.     main_title = []
  1159.  
  1160.     # Loop through calls and/or puts
  1161.     for opt_type in opt_types:
  1162.         # For debugging
  1163.         # print(opt_type)
  1164.  
  1165.         # Set colors and create title string portions
  1166.         if opt_type == 'calls':
  1167.             itm_color = 'green'
  1168.             otm_color = 'blue'
  1169.  
  1170.             if moneyness == 'itm':
  1171.                 main_title.append('\nITM calls={}'.format(itm_color))
  1172.  
  1173.             elif moneyness == 'otm':
  1174.                 main_title.append('\nOTM calls={}'.format(otm_color))
  1175.  
  1176.             elif moneyness == 'both':
  1177.                 main_title.append(
  1178.                     '\nITM calls={}, OTM calls={}'.format(itm_color, otm_color))
  1179.  
  1180.         else:
  1181.             itm_color = 'red'
  1182.             otm_color = 'purple'
  1183.  
  1184.             if moneyness == 'itm':
  1185.                 main_title.append('\nITM puts={}'.format(itm_color))
  1186.  
  1187.             elif moneyness == 'otm':
  1188.                 main_title.append('\nOTM puts={}'.format(otm_color))
  1189.  
  1190.             elif moneyness == 'both':
  1191.                 main_title.append(
  1192.                     '\nITM puts={}, OTM puts={}'.format(itm_color, otm_color))
  1193.  
  1194.         # Get all the expirations of the current type
  1195.         option_chains = options[opt_type]
  1196.  
  1197.         # Get the names of the chains in the current type
  1198.         single_chains = list(option_chains.keys())
  1199.  
  1200.         # Counter for the y-axis (dates)
  1201.         # Dates are initially plotted as integers
  1202.         # After the whole plot is done, map the date labels to the integers
  1203.         j = 0
  1204.  
  1205.         # Loop through expirations
  1206.         for chain in single_chains:
  1207.             # For debugging
  1208.             # print(chain)
  1209.  
  1210.             # Only add expirations during the loop of the first option type
  1211.             if k == 0:
  1212.                 exp = chain.split(' ')[0]
  1213.                 exps.append(exp[0:])
  1214.  
  1215.             # Pull a single expiration's option chain
  1216.             single_exp_options = option_chains[chain]
  1217.             # Set/reset counter for the subplots (parameters)
  1218.             i = 1
  1219.  
  1220.             # Loop through parameters
  1221.             for param in params:
  1222.                 # Initialize lists for itm and otm data
  1223.                 x_itm = []
  1224.                 y_itm = []
  1225.                 z_itm = []
  1226.  
  1227.                 x_otm = []
  1228.                 y_otm = []
  1229.                 z_otm = []
  1230.  
  1231.                 # If this is the first time parameters are being looped through,
  1232.                 # set up a subplot for each parameter
  1233.                 if first_iter == True:
  1234.                     # For debugging
  1235.                     # print('Create axis {}:{} {} {}'.format(
  1236.                     #     i, exp, opt_type[0:-1], param))
  1237.                     exec("ax{} = fig.add_subplot({}{}{}, projection='3d')".format(i,
  1238.                                                                                   rows,
  1239.                                                                                   cols,
  1240.                                                                                   i))
  1241.                     eval("ax{}.set_title(titles[{}])".format(i, i - 1))
  1242.                     eval("ax{}.set_xlabel('strike')".format(i))
  1243.                     eval("ax{}.view_init(45, -65)".format(i))
  1244.  
  1245.                 else:
  1246.                     # For debugging
  1247.                     # print('Plot on axis {}:{} {} {}'.format(
  1248.                     #     i, exp, opt_type[0:-1], param))
  1249.                     pass
  1250.  
  1251.                 # Loop through each individual option in the expiration
  1252.                 for option in single_exp_options:
  1253.                     # For debugging
  1254.                     # print(option)
  1255.                     # Only need S once for the title string, but oh well
  1256.                     S = option.S
  1257.                     itm = option.itm
  1258.  
  1259.                     # Add itm and otm data to the appropriate lists
  1260.                     if itm == True:
  1261.                         x_itm.append(option.K)
  1262.                         y_itm.append(j)
  1263.                         eval('z_itm.append(option.{})'.format(param))
  1264.  
  1265.                     else:
  1266.                         x_otm.append(option.K)
  1267.                         y_otm.append(j)
  1268.                         eval('z_otm.append(option.{})'.format(param))
  1269.  
  1270.                 # Plot itm and/or otm data for a single expiration
  1271.                 if moneyness == 'itm':
  1272.                     eval("ax{}.plot(x_itm, y_itm, z_itm, '{}')".format(
  1273.                         i, itm_color))
  1274.  
  1275.                 elif moneyness == 'otm':
  1276.                     eval("ax{}.plot(x_otm, y_otm, z_otm, '{}')".format(
  1277.                         i, otm_color))
  1278.  
  1279.                 elif moneyness == 'both':
  1280.                     eval("ax{}.plot(x_itm, y_itm, z_itm, '{}')".format(
  1281.                         i, itm_color))
  1282.                     eval("ax{}.plot(x_otm, y_otm, z_otm, '{}')".format(
  1283.                         i, otm_color))
  1284.  
  1285.                 # Increment the axis (parameter) counter
  1286.                 i += 1
  1287.  
  1288.             # Set to false after first iteration
  1289.             # Only need to set up the subplots once
  1290.             first_iter = False
  1291.  
  1292.             # Increment the y(date)-axis counter
  1293.             j += 1
  1294.  
  1295.         # If a second option type is looped through, increment k
  1296.         # This prevents duplicate expirations from being added
  1297.         k += 1
  1298.  
  1299.     # Create ticks for the y(date)-axis
  1300.     # 13 dates seems to be the limit of readability with an 8-pt font
  1301.     # If there are more than 13 expirations, only label every other one
  1302.     if j <= 13:
  1303.         ticks = [x for x in range(j)]
  1304.  
  1305.     else:
  1306.         ticks = [x for x in range(1, j, 2)]
  1307.         exps = [exps[x] for x in ticks]
  1308.  
  1309.     # Create a list of the index of the subplots
  1310.     axs = [x for x in range(1, i)]
  1311.  
  1312.     # Update the y(date)-axis with date labels instead of integers
  1313.     # Do this for each subplot
  1314.     for ax in axs:
  1315.         eval("ax{}.yaxis.set_ticks(ticks)".format(ax))
  1316.         eval("ax{}.yaxis.set_ticklabels(exps, fontsize=8, verticalalignment='baseline', horizontalalignment='center', rotation=-20)".format(ax))
  1317.  
  1318.     # Create the main title
  1319.     main_title.insert(0, 'ATM {}: ${:.2f} [{} {}]'.format(
  1320.         ticker, S, current_date, current_time))
  1321.     fig.suptitle(''.join(main_title))
  1322.  
  1323.     # Make view adjustments
  1324.     fig.tight_layout(h_pad=1, w_pad=0.001)
  1325.     plt.subplots_adjust(wspace=0.001, hspace=0.1)
  1326.  
  1327.     # Show the plot
  1328.     plt.show(block=True)
  1329.  
  1330.     pass
  1331.  
  1332.  
  1333. def main():
  1334.     stop_main_loop = '0'
  1335.  
  1336.     # Print the read me
  1337.     print('\n---------------------------------------READ ME---------------------------------------------\n')
  1338.     print('| Data is pulled from Yahoo! finance with the yfinance python package. There is a rate    |')
  1339.     print('| limit of 2000 request per hour to the Yahoo! API, and each expiration in an option      |')
  1340.     print('| chain requires 1 API call. So if you are running this a lot, you may get odd behavior.  |')
  1341.  
  1342.     print('| Yahoo! also seems to "reset" around around 12-2am, so prices may all be 0 at that time. |')
  1343.  
  1344.     print('\n| Yahoo! does provide implied volatility numbers, but in the spirit of this exercise,     |')
  1345.     print('| IVs are manually calculated from the option price using a bisection algorithm.          |')
  1346.  
  1347.     print('\n| Bisection is slower than something like Newton-Raphson, but is guaranteed to converge   |')
  1348.     print('| given that the answer is between the initial guesses. The initial guesses for IV in     |')
  1349.     print('| this script are 0% and 2000%.                                                           |')
  1350.  
  1351.     print('\n| There are no filters with respect to open interest or volume. Whatever bid/ask/last is  |')
  1352.     print('| present will be used.                                                                   |')
  1353.  
  1354.     print('\n| For tickers with more than 13 expirations, only every other expiration will be labeled  |')
  1355.     print('| on the plots. Otherwise the labels get crowded and hard to read.                        |')
  1356.  
  1357.     print('\n------------------------------------------------------------------------------------------\n')
  1358.     input('Press any key to continue...\n')
  1359.  
  1360.     while stop_main_loop == '0':
  1361.  
  1362.         # Get mode
  1363.         mode = input(
  1364.             'Press 0 to make plots, 1 to calculate the standard greeks for a single option: ')
  1365.         while mode not in ['0', '1']:
  1366.             mode = input(
  1367.                 'Press 0 to make plots, 1 to calculate the standard greeks for a single option: ')
  1368.  
  1369.         #  If plot mode
  1370.         if mode == '0':
  1371.             # Get inputs
  1372.             ticker, params, price_type, opt_type, moneyness, current_date, current_time, r = multi_plot_input()
  1373.  
  1374.             print('Connecting to Yahoo!...')
  1375.  
  1376.             # Get option objects
  1377.             options = get_options(current_date, current_time,
  1378.                                   ticker, opt_type, price_type, r)
  1379.  
  1380.             # Make plots
  1381.             generate_plots(ticker, options, moneyness, params, price_type,
  1382.                            current_date, current_time)
  1383.  
  1384.             # Stop or continue
  1385.             stop_main_loop = input(
  1386.                 'Enter 0 to continue, anything else to quit: ')
  1387.  
  1388.         # If single option mode
  1389.         elif mode == '1':
  1390.  
  1391.             # Get inputs
  1392.             current_date, current_time, opt_type, exp, V, S, K, r = single_option_input()
  1393.  
  1394.             # Create option object
  1395.             try:
  1396.                 option = Option(current_date, current_time,
  1397.                                 opt_type, exp, V, S, K, r, 0, 0, 0, 0)
  1398.  
  1399.                 # Names of things to print
  1400.                 names = [
  1401.                     'IV',
  1402.                     'delta',
  1403.                     'gamma',
  1404.                     'vega',
  1405.                     'theta',
  1406.                     'rho'
  1407.                 ]
  1408.  
  1409.                 # Print the things
  1410.                 print('\n{}'.format(option))
  1411.  
  1412.                 for name in names:
  1413.                     thing = eval('option.{}'.format(name))
  1414.                     print('{}: {}'.format(name, thing))
  1415.  
  1416.                 print('')
  1417.  
  1418.             except:
  1419.                 print('\nSomething went wrong.')
  1420.                 print('Check your input values\n')
  1421.  
  1422.     pass
  1423.  
  1424.  
  1425. if __name__ == '__main__':
  1426.     # Main loop
  1427.     main()
Add Comment
Please, Sign In to add comment