├── .editorconfig
├── .env.sample
├── .gitignore
├── LICENSE
├── README.md
├── optlib
├── __init__.py
├── api.py
├── gbs.py
└── instruments.py
├── setup.py
└── tests
└── test_gbs.py
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | TDA_API_KEY=
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | build/
3 | dist/
4 | *.egg-info/
5 |
6 | .env
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Davis W. Edwards
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # optlib
2 | A library to fetch financial option chains and price options using closed-form solutions written in Python. Original code written by Davis Edwards, packaged by Daniel Rojas. MIT License.
3 |
4 | **Changelog**
5 |
6 | * 1/1/2017 Davis Edwards, Created GBS and Asian option formulas
7 | * 3/2/2017 Davis Edwards, added TeX formulas to describe calculations
8 | * 4/9/2017 Davis Edwards, added spread option (Kirk's approximation)
9 | * 5/10/2017 Davis Edwards, added graphics for sensitivity analysis
10 | * 5/18/2017 Davis Edwards, added Bjerksund-Stensland (2002) approximation for American Options
11 | * 5/19/2017 Davis Edwards, added implied volatility calculations
12 | * 6/7/2017 Davis Edwards, expanded sensitivity tests for American option approximation.
13 | * 6/21/2017 Davis Edwards, added documentation for Bjerksund-Stensland models
14 | * 7/21/2017 Davis Edwards, refactored all of the functions to match the parameter order to Haug's "The Complete Guide to Option Pricing Formulas".
15 | * 08/31/2020 Daniel Rojas, Added functionality to fetch option chains using the TDAmeritrade API.
16 | * 09/05/2020 Daniel Rojas, Added functionality to fetch historical data using the TDAmeritrade API.
17 | * 04/10/2021 Daniel Rojas, Write classes to represent API responses
18 |
19 | **TODO List**
20 |
21 | 1. Since the Asian Option valuation uses an approximation, need to determine the range of acceptable stresses that can be applied to the volatility input
22 | 2. Sub-class the custom assertions in this module to work with "unittest"
23 | 3. Update the greek calculations for American Options - currently the Greeks are approximated by the greeks from GBS model.
24 | 4. Add a bibliography referencing the academic papers used as sources
25 | 5. Finish writing documentation formulas for Close-Form approximation for American Options
26 | 6. Refactor the order of parameters for the function calls to replicate the order of parameters in academic literature
27 | 7. Add figures to README.md
28 |
29 | ## Installation
30 |
31 | To install `optlib` clone the repository
32 | ```
33 | git clone https://github.com/BartolomeD/optlib.git
34 | ```
35 |
36 | Create a virtual environment
37 | ```
38 | python3 -m venv env
39 | source env/bin/activate
40 | ```
41 |
42 | and in the `optlib` directory execute
43 | ```
44 | python setup.py install
45 | ```
46 |
47 | The `optlib.api` module requires a TDAmeritrade API key. Create a developer account on https://developer.tdameritrade.com/ to get one.
48 |
49 | ## Purpose
50 |
51 | The software in this model is intended to price particular types of financial products called "options". These are a common type of financial product and fall into the category of "financial derivative". This documentation assumes that the reader is already familiar with options terminology. The models are largely variations of the Black Scholes Merton option framework (collectively called "Black Scholes Genre" or "Generalized Black Scholes") that are used to price European options (options that can only be exercised at one point in time). This library also includes approximations to value American options (options that can be exercised prior to the expiration date) and implied volatility calculators.
52 |
53 | Pricing Formulas
54 | 1. `black_scholes()` Stock Options (no dividend yield)
55 | 2. `merton()` Assets with continuous dividend yield (Index Options)
56 | 3. `black_76()` Commodity Options
57 | 4. `garman_kohlhagen()` FX Options
58 | 5. `asian_76()` Asian Options on Commodities
59 | 6. `kirks_76()` Spread Options (Kirk's Approximation)
60 | 7. `american()` American options
61 | 8. `american_76()` American Commodity Options
62 |
63 | Implied Volatility Formulas
64 |
65 | 9. `euro_implied_vol()` Implied volatility calculator for European options
66 | 10. `euro_implied_vol_76()` Implied volatility calculator for European commodity options
67 | 11. `amer_implied_vol()` Implied volatility calculator for American options
68 | 12. `amer_implied_vol_76()` Implied volatility calculator for American commodity options
69 |
70 | Note:
71 | In honor of the `black_76` model, the `_76` on the end of functions indicates a commodity option.
72 |
73 | ## Scope
74 | This model is built to price financial option contracts on a wide variety of financial commodities. These options are widely used and represent the benchmark to which other (more complicated) models are compared. While those more complicated models may outperform these models in specific areas, out-performance is relatively uncommon. By an large, these models have taken on all challengers and remain the de-facto industry standard.
75 |
76 | ## TDAmeritrade Option Chain API
77 | This library provides the ability to fetch up-to-date data using TDAmeritrade'sAPI. In order to use this functionality, the user is required to get his own API key. This can be done by creating a developer account on https://developer.tdameritrade.com/.
78 |
79 | ### Historical Data
80 | The historical data API accepts the following query parameters.
81 | * `apikey`. Required. Application consumer key on TDAmeritrade platform.
82 | * `symbol`. Required. Symbol to get option chain for.
83 | * `periodType`. The type of period to show. Valid values are `day`, `month`, `year`, or `ytd` (year to date). Default is `day`.
84 | * `period`. The number of periods to show.
85 |
86 | Example: For a 2 day / 1 min chart, the values would be:
87 | * period: 2
88 | * periodType: `day`
89 | * frequency: 1
90 | * frequencyType: `minute`
91 |
92 | Valid `periods` by `periodType` (defaults marked with an asterisk):
93 | * `day`: 1, 2, 3, 4, 5, 10*
94 | * `month`: 1*, 2, 3, 6
95 | * `year`: 1*, 2, 3, 5, 10, 15, 20
96 | * `ytd`: 1*
97 | * `frequencyType`. The type of frequency with which a new candle is formed.
98 |
99 | Valid frequencyTypes by `periodType` (defaults marked with an asterisk):
100 | * `day`: `minute`*
101 | * `month`: `daily`, `weekly`*
102 | * `year`: `daily`, `weekly`, `monthly`*
103 | * `ytd`: `daily`, `weekly`*
104 | * `frequency`. The type of frequency with which a new candle is formed.
105 | Valid `frequencyType`s by `periodType` (defaults marked with an asterisk):
106 | * `day`: `minute`*
107 | * `month`: `daily`, `weekly`*
108 | * `year`: `daily`, `weekly`, `monthly`*
109 | * `ytd`: `daily`, `weekly`*
110 | * `startDate`. End date as milliseconds since epoch. If startDate and endDate are provided, period should not be provided. Default is previous trading day.
111 | * `endDate`. Start date as milliseconds since epoch. If startDate and endDate are provided, period should not be provided.
112 | * `needExtendedHoursData`. `true` to return extended hours data, `false` for regular market hours only. Default is `true`
113 |
114 | ### Option Chains
115 | The option chain API accepts the following query parameters.
116 | * `apikey`. Required. Application consumer key on TDAmeritrade platform.
117 | * `symbol`. Required. Symbol to get option chain for.
118 | * `contractType`. Type of contracts to return in the chain. Can be `CALL`, `PUT`, or `ALL`. Default is `ALL`.
119 | * `strikeCount`. The number of strikes to return above and below the at-the-money price.
120 | * `includeQuotes`. Include quotes for options in the option chain. Can be `TRUE` or `FALSE`. Default is `FALSE`.
121 | * `strategy`. Passing a value returns a Strategy Chain. For more information about strategies refer to https://www.optionsplaybook.com/option-strategies/. Default is `SINGLE`. Possible values are:
122 | * `SINGLE`
123 | * `ANALYTICAL`: Allows use of the `volatility`, `underlyingPrice`, `interestRate`, and `daysToExpiration` params to calculate theoretical values.
124 | * `COVERED`
125 | * `VERTICAL`
126 | * `CALENDAR`
127 | * `STRANGLE`
128 | * `STRADDLE`
129 | * `BUTTERFLY`
130 | * `CONDOR`
131 | * `DIAGONAL`
132 | * `COLLAR`
133 | * `ROLL`
134 | * `interval`. Strike interval for spread strategy chains (see `strategy` param).
135 | * `strike`. Provide a strike price to return options only at that strike price.
136 | * `range`. Returns options for the given range. Default is `ALL`. Possible values are:
137 | * `ITM`: In-the-money
138 | * `NTM`: Near-the-money
139 | * `OTM`: Out-of-the-money
140 | * `SAK`: Strikes Above Market
141 | * `SBK`: Strikes Below Market
142 | * `SNK`: Strikes Near Market
143 | * `ALL`: All Strikes
144 | * `fromDate`. Only return expirations after this date. For strategies, expiration refers to the nearest term expiration in the strategy. Valid ISO-8601 formats are: `yyyy-MM-dd` and `yyyy-MM-dd'T'HH:mm:ssz`.
145 | * `toDate`: Only return expirations before this date. For strategies, expiration refers to the nearest term expiration in the strategy. Valid ISO-8601 formats are: `yyyy-MM-dd` and `yyyy-MM-dd'T'HH:mm:ssz`.
146 | * `volatility`. Volatility to use in calculations. Applies only to `ANALYTICAL` strategy chains (see strategy param).
147 | * `underlyingPrice`. Underlying price to use in calculations. Applies only to ANALYTICAL strategy chains (see `strategy` param).
148 | * `interestRate`. Interest rate to use in calculations. Applies only to `ANALYTICAL` strategy chains (see `strategy` param).
149 | * `daysToExpiration`. Days to expiration to use in calculations. Applies only to `ANALYTICAL` strategy chains (see `strategy` param).
150 | * `expMonth`. Return only options expiring in the specified month. Month is given in the three character format. Example: `JAN`. Default is `ALL`.
151 | * `optionType`. Type of contracts to return. Default is `ALL`. Possible values are:
152 | * `S`: Standard contracts
153 | * `NS`: Non-standard contracts
154 | * `ALL`: All contracts
155 |
156 | ## Theory
157 |
158 | ### Generalized Black Scholes
159 | Black Scholes genre option models widely used to value European options. The original “Black Scholes” model was published in 1973 for non-dividend paying stocks. This created a revolution in quantitative finance and opened up option trading to the general population. Since that time, a wide variety of extensions to the original Black Scholes model have been created. Collectively, these are referred to as "Black Scholes genre” option models. Modifications of the formula are used to price other financial instruments like dividend paying stocks, commodity futures, and FX forwards. Mathematically, these formulas are nearly identical. The primary difference between these models is whether the asset has a carrying cost (if the asset has a cost or benefit associated with holding it) and how the asset gets present valued. To illustrate this relationship, a “generalized” form of the Black Scholes equation is shown below.
160 |
161 | The Black Scholes model is based on number of assumptions about how financial markets operate. Black Scholes style models assume:
162 |
163 | 1. **Arbitrage Free Markets**. Black Scholes formulas assume that traders try to maximize their personal profits and don’t allow arbitrage opportunities (riskless opportunities to make a profit) to persist.
164 | 2. **Frictionless, Continuous Markets**. This assumption of frictionless markets assumes that it is possible to buy and sell any amount of the underlying at any time without transaction costs.
165 | 3. **Risk Free Rates**. It is possible to borrow and lend money at a risk-free interest rate
166 | 4. **Log-normally Distributed Price Movements**. Prices are log-normally distributed and described by Geometric Brownian Motion
167 | 5. **Constant Volatility**. The Black Scholes genre options formulas assume that volatility is constant across the life of the option contract.
168 |
169 | In practice, these assumptions are not particularly limiting. The primary limitation imposed by these models is that it is possible to (reasonably) describe the dispersion of prices at some point in the future in a mathematical equation.
170 |
171 | In the traditional Black Scholes model intended to price stock options, the underlying assumption is that the stock is traded at its present value and that prices will follow a random walk diffusion style process over time. Prices are assumed to start at the spot price and, on the average, to drift upwards over time at the risk free rate. The Merton formula modifies the basic Black Scholes equation by introducing an additional term to incorporate dividends or holding costs. The Black 76 formula modifies the assumption so that the underlying starts at some forward price rather than a spot price. A fourth variation, the Garman Kohlhagen model, is used to value foreign exchange (FX) options. In the GK model, each currency in the currency pair is discounted based on its own interest rate.
172 |
173 | 1. **Black Scholes (Stocks)**. In the traditional Black Scholes model, the option is based on common stock - an instrument that is traded at its present value. The stock price does not get present valued – it starts at its present value (a ‘spot price’) and drifts upwards over time at the risk free rate.
174 | 2. **Merton (Stocks with continuous dividend yield)**. The Merton model is a variation of the Black Scholes model for assets that pay dividends to shareholders. Dividends reduce the value of the option because the option owner does not own the right to dividends until the option is exercised.
175 | 3. **Black 76 (Commodity Futures)**. The Black 76 model is for an option where the underlying commodity is traded based on a future price rather than a spot price. Instead of dealing with a spot price that drifts upwards at the risk free rate, this model deals with a forward price that needs to be present valued.
176 | 4. **Garman-Kohlhagen (FX Futures)**. The Garman Kohlhagen model is used to value foreign exchange (FX) options. In the GK model, each currency in the currency pair is discounted based on its own interest rate.
177 |
178 | An important concept of Black Scholes models is that the actual way that the underlying asset drifts over time isn't important to the valuation. Since European options can only be exercised when the contract expires, it is only the distribution of possible prices on that date that matters - the path that the underlying took to that point doesn't affect the value of the option. This is why the primary limitation of the model is being able to describe the dispersion of prices at some point in the future, not that the dispersion process is simplistic.
179 |
180 | The generalized Black Scholes formula can found below (see Figure 1). While these formulas may look complicated at first glance, most of the terms can be found as part of an options contract or are prices readily available in the market. The only term that is difficult to calculate is the implied volatility (σ). Implied volatility is typically calculated using prices of other options that have recently been traded.
181 |
182 | *Call Price*
183 |
T}&space;N(D_1)&space;-&space;Xe^{-rT}&space;N(D_2))
184 |
185 | *Put Price*
186 | &space;-&space;Fe^{(b-r)T}&space;N(-D_1))
187 |
188 | with the following intermediate calculations
189 | T}{V*\sqrt{T}})
190 |
191 | 
192 |
193 | and the following inputs
194 |
195 | | Symbol | Meaning |
196 | |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
197 | | F or S | **Underlying Price**. The price of the underlying asset on the valuation date. S is used commonly used to represent a spot price, F a forward price |
198 | | X | **Strike Price**. The strike, or exercise, price of the option. |
199 | | T | **Time to expiration**. The time to expiration in years. This can be calculated by comparing the time between the expiration date and the valuation date. T = (t_1 - t_0) / 365 |
200 | | t_0 | **Valuation Date**. The date on which the option is being valued. For example, it might be today’s date if the option we being valued today. |
201 | | t_1 | **Expiration Date**. The date on which the option must be exercised. |
202 | | V | **Volatility**. The volatility of the underlying security. This factor usually cannot be directly observed in the market. It is most often calculated by looking at the prices for recent option transactions and back-solving a Black-Scholes style equation to find the volatility that would result in the observed price. This is commonly abbreviated with the Greek letter sigma, σ, although V is used here for consistency with the code below. |
203 | | q | **Continuous Yield**. Used in the Merton model, this is the continuous yield of the underlying security. Option holders are typically not paid dividends or other payments until they exercise the option. As a result, this factor decreases the value of an option. |
204 | | r | **Risk Free Rate**. This is expected return on a risk-free investment. This is commonly a approximated by the yield on a low-risk government bond or the rate that large banks borrow between themselves (LIBOR). The rate depends on tenor of the cash flow. For example, a 10-year risk-free bond is likely to have a different rate than a 20-year risk-free bond. |
205 | | rf | **Foreign Risk Free Rate**. Used in the Garman-Kohlhagen model, this is the risk free rate of the foreign currency. Each currency will have a risk free rate. |
206 |
207 | **Figure 1.** Generalized Black Scholes Formula.
208 |
209 | The correction term, b, varies by formula – it differentiates the various Black Scholes formula from one another (see Figure 2). The cost of carry refers to the cost of “carrying” or holding a position. For example, holding a bond may result in earnings from interest, holding a stock may result in stock dividends, or the like. Those payments are made to the owner of the underlying asset and not the owner of the option. As a result, they reduce the value of the option.
210 |
211 | | | Model | Cost of Carry (b) |
212 | | ---- | ------------------ | ----------------- |
213 | | 1. | `black_scholes` | b = r |
214 | | 2. | `merton` | b = r - q |
215 | | 3. | `black_76` | b = 0 |
216 | | 4. | `garman_kohlhagen` | b = r - rf |
217 | | 5. | `asian_76` | b = 0, modified V |
218 |
219 | **Figure 2.** Generalized Black Scholes Cost of Carry Adjustment.
220 |
221 | ### Asian Volatility Adjustment
222 |
223 | An Asian option is an option whose payoff is calculated using the average price of the underlying over some period of time rather than the price on the expiration date. As a result, Asian options are also called average price options. The reason that traders use Asian options is that averaging a settlement price over a period of time reduces the affect of manipulation or unusual price movements on the expiration date on the value of the option. As a result, Asian options are often found on strategically important commodities, like crude oil or in markets with intermittent trading.
224 |
225 | The average of a set of random numbers (prices in this case) will have a lower dispersion (a lower volatility) than the dispersion of prices observed on any single day. As a result, the implied volatility used to price Asian options will usually be slightly lower than the implied volatility on a comparable European option. From a mathematical perspective, valuing an Asian option is slightly complicated since the average of a set of log-normal distributions is not itself log-normally distributed. However, a reasonably good approximation of the correct answer is not too difficult to obtain.
226 |
227 | In the case of Asian options on futures, it is possible to use a modified Black-76 formula that replaces the implied volatility term with an adjusted implied volatility of the average price. As long as the first day of the averaging period is in the future, the following formula can be used to value Asian options (see Figure 3).
228 |
229 | *Asian Adjusted Volatility*
230 | }{T}})
231 |
232 | with the intermediate calculation
233 | ![M = \frac{2e^{V^2T} - 2e^{V^2T}[1+V^2(T-t)]}{V^4(T-t)^2}](https://latex.codecogs.com/svg.latex?M&space;=&space;\frac{2e^{V^2T}&space;-&space;2e^{V^2T}[1+V^2(T-t)]}{V^4(T-t)^2})
234 |
235 | | Symbol | Meaning |
236 | |--------|-----------------------------------------------------------------------------------------------------------------|
237 | | Va | **Asian Adjusted Volatility**, This will replace the volatility (V) term in the GBS equations shown previously. |
238 | | T | **Time to expiration**. The time to expiration of the option (measured in years). |
239 | | t_a | **Time to start of averaging period**. The time to the start of the averaging period (measured in years). |
240 |
241 | **Figure 3.** Asian Option Formula.
242 |
243 |
244 | ### Spread Option (Kirk's Approximation) Calculation
245 |
246 | Spread options are based on the spread between two commodity prices. They are commonly used to model physical investments as "real options" or to mark-to-market contracts that hedge physical assets. For example, a natural gas fueled electrical generation unit can be used to convert fuel (natural gas) into electricity. Whenever this conversion is profitable, it would be rational to operate the unit. This type of conversion is readily modeled by a spread option. When the spread of (electricity prices - fuel costs) is greater than the conversion cost, then the unit would operate. In this example, the conversion cost, which might be called the *Variable Operations and Maintenance* (VOM) for a generation unit, would represent the strike price.
247 |
248 | Analytic formulas similar to the Black Scholes equation are commonly used to value commodity spread options. One such formula is called *Kirk’s approximation*. While an exact closed form solution does not exist to value spread options, approximate solutions can give reasonably accurate results. Kirk’s approximation uses a Black Scholes style framework to analyze the joint distribution that results from the ratio of two log-normal distributions.
249 |
250 | In a Black Scholes equation, the distribution of price returns is assumed to be normally distributed on the expiration date. Kirk’s approximation builds on the Black Scholes framework by taking advantage of the fact that the ratio of two log-normal distributions is approximately normally distributed. By modeling a ratio of two prices rather than the spread between the prices, Kirk’s approximation can use the same formulas designed for options based on a single underlying. In other words, Kirk’s approximation uses an algebraic transformation to fit the spread option into the Black Scholes framework.
251 |
252 | The payoff of a spread option is show in Figure 4.
253 |
254 | **Figure 4.** Spread Option Payoff.
255 |
256 | ![C = max[F_1 - F_2 - X, 0]](https://latex.codecogs.com/svg.latex?C&space;=&space;max[F_1&space;-&space;F_2&space;-&space;X,&space;0])
257 |
258 | ![P = max[X - (F_1 - F_2), 0]](https://latex.codecogs.com/svg.latex?P&space;=&space;max[X&space;-&space;(F_1&space;-&space;F_2),&space;0])
259 |
260 | Where
261 |
262 | | Symbol | Meaning |
263 | |--------|----------------------------------------------------|
264 | | F_1 | **Price of Asset 1**, The prices of the first asset. |
265 | | F_2 | **Price of Asset 2**. The price of the second asset. |
266 |
267 |
268 | **Figure 4.** Spread Option Payoff.
269 |
270 | This can be algebraically manipulated as shown in Figure 5.
271 |
272 | ](https://latex.codecogs.com/svg.latex?C&space;=&space;max&space;\biggl[\frac{F_1}{F_2+X}-1,0&space;\biggr](F_2&space;+&space;X))
273 |
274 | ](https://latex.codecogs.com/svg.latex?P&space;=&space;max&space;\biggl[1-\frac{F_1}{F_2+X},0&space;\biggr](F_2&space;+&space;X))
275 |
276 | **Figure 5.** - Spread Option Payoff, Manipulated.
277 |
278 | This allows Kirk’s approximation to model the distribution of the spread as the ratio of the price of asset 1 over the price of asset 2 plus the strike price. This ratio can then be converted into a formula very similar to the Generalized Black Scholes formulas. In fact, this is the Black Scholes formula shown above with the addition of a (F_2 + X) term (see Figure 6).
279 |
280 | *Ratio of prices*
281 | 
282 |
283 | The ratio implies that the option is profitable to exercise (*in the money*) whenever the ratio of prices (F in the formula above) is greater than 1. This occurs the cost of the finished product (F_1) exceeds total cost of the raw materials (F_2) and the conversion cost (X). This requires a modification to the Call/Put Price formulas and to the D_1 formula. Because the option is in the money when F>1, the "strike" price used in inner square brackets of the Call/Put Price formulas and the D1 formula is set to 1.
284 |
285 | *Spread Option Call Price*
286 | ![C = (F_2 + X)\biggl[Fe^{(b-r)T} N(D_1) - e^{-rT} N(D_2)\biggr]](https://latex.codecogs.com/svg.latex?C&space;=&space;(F_2&space;+&space;X)\biggl[Fe^{(b-r)T}&space;N(D_1)&space;-&space;e^{-rT}&space;N(D_2)\biggr])
287 |
288 | *Spread Option Put Price*
289 | ![P = (F_2 + X)\biggl[e^{-rT} N(-D_2) - Fe^{(b-r)T} N(-D_1)\biggr]](https://latex.codecogs.com/svg.latex?P&space;=&space;(F_2&space;+&space;X)\biggl[e^{-rT}&space;N(-D_2)&space;-&space;Fe^{(b-r)T}&space;N(-D_1)\biggr])
290 |
291 | &space;+&space;(b+\frac{V^2}{2})T}{V*\sqrt{T}})
292 |
293 | 
294 |
295 | **Figure 6.** Kirk's Approximation Ratio.
296 |
297 | The key complexity is determining the appropriate volatility that needs to be used in the equation. The “approximation” which defines Kirk’s approximation is the assumption that the ratio of two log-normal distributions is normally distributed. That assumption makes it possible to estimate the volatility needed for the modified Black Scholes style equation (see Figure 7).
298 |
299 | ![V = \sqrt{ V_1^{2}+ \biggl[V_2\frac{F_2}{F_2+X}\biggr]^2 - 2\rho V_1 V_2 \frac{F_2}{F_2+X} }](https://latex.codecogs.com/svg.latex?V&space;=&space;\sqrt{&space;V_1^{2}+&space;\biggl[V_2\frac{F_2}{F_2+X}\biggr]^2&space;-&space;2\rho&space;V_1&space;V_2&space;\frac{F_2}{F_2+X}&space;})
300 |
301 | | Symbol | Meaning |
302 | |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
303 | | V | **Volatility**. The Kirk's approximation volatility that will be placed into the formula shown in Figure 6 |
304 | | V1 | **Volatility of Asset 1**. The strike, or exercise, price of the option. |
305 | | V2 | **Volatility of Asset 2**. The volatility of the second asset |
306 | | ρ | **Correlation**. The correlation between price of asset 1 and the price of asset 2. |
307 |
308 | **Figure 7.** Kirk's Approximation (Volatility).
309 |
310 | A second complexity is that the prices of two assets (F1 and F2) have to be in the same units. For example, in a heat rate option, the option represents the ability to convert fuel (natural gas) into electricity. The price of the first asset, electricity, might be quoted in US dollars per megawatt-hour or USD/MWH. However, the price of the second asset might be quoted in USD/MMBTU. To use the approximation, it is necessary to convert the price of the second asset into the units of the first asset (See *Example 1 - a Heat Rate Option*). This conversion rate will typically be specified as part of the contract.
311 |
312 | Example: A 10 MMBTU/MWH heat rate call option
313 |
314 | * F1 = price of electricity = USD 35/MWH
315 | * F2* = price of natural gas = USD 3.40/MMBTU; *This is not the price to plug into the model!*
316 | * V1 = volatility of electricity forward prices = 35%
317 | * V2 = volatility of natural gas forward price = 35%
318 | * Rho = correlation between electricity and natural gas forward prices = 90%
319 | * VOM = variable operation and maintenance cost (the conversion cost) = USD 3/MWH
320 |
321 | Before being placed into a spread option model, the price of natural gas would need to be converted into the correct units.
322 | * F2 = Heat Rate * Fuel Cost = (10 MMBTU/MWH)(USD 3.40/MMBTU) = USD 34/MWH
323 |
324 | The strike price would be set equal to the conversion cost
325 | * X = VOM costs = USD 3/MWH
326 |
327 | **Example 1.** A Heat Rate Call Option.
328 |
329 | Another important consideration (not discussed in this write-up) is that volatility and correlation need to be matched to the tenor of the underlying assets. This means that it is necessary to measure the volatility of forward prices rather than spot prices. It may also be necessary to match the volatility and correlation to the correct month. For example, power prices in August may behave very differently than power prices in October or May in certain regions.
330 |
331 | Like any model, spread options are subject to the "garbage in = garbage out" problem. However, the relative complexity of modeling commodity prices (the typical underlying for spread options) makes calibrating inputs a key part of the model.
332 |
333 |
334 | ### American Options
335 | American options differ from European options because they can be exercised at any time. If there is a possibility that it will be more profitable to exercise the option than sell it, an American option will have more value than a corresponding European option. Early exercise typically occurs only when an option is *in the money*. If an option is out of the money, there is usually no reason to exercise early - it would be better to sell the option (in the case of a put option, to sell the option and the underlying asset).
336 |
337 | The decision of whether to exercise early is primarily a question of interest rates and carrying costs. Carrying costs, or *cost of carry*, is a term that means an intermediate cash flows that is the result of holding an asset. For example, dividends on stocks are a positive cost of carry (owning the asset gives the owner a cash flow). A commodity might have a negative cost of carry. For example, a commodity that requires its owner to pay for storage would cause the owner of the physical commodity to pay cash to hold the asset. (**Note:** Commodity options are typically written on forwards or futures which have zero cost of carry instead of the actual underlying commodity). Cost of carry is cash flow that affects the owner of the underlying commodity and not the owner of the option. For example, when a stock pays a dividend, the owner of a call option does not receive the dividend - just the owner of the stock. For the perspective for the owner of a call option on a stock, the cost of carry will be the interest received from holding cash (r) less any dividends paid to owners of the stock (q).
338 |
339 | Since an option has some value (the *extrinsic value*) that would be given up by exercising the option, exercising an option prior to maturity is a trade off between the option's extrinsic value (the remaining optionality) and the relative benefit of holding cash (time value of money) versus the benefit of holding the asset (carrying costs).
340 |
341 | The early exercise feature of **American equity put options** may have value when:
342 | * The cost of carry on the asset is low - preferably zero or negative.
343 | * Interest rates are high
344 | * The option is in the money
345 |
346 | The early exercise feature of **American equity call options** may have value when:
347 | * The cost of carry on the asset is positive
348 | * Interest rates are low or negative
349 | * The option is in the money
350 |
351 | With commodities, things are a slightly different. There is typically no cost of carry since the underlying is a forward or a futures contract. It does not cost any money to enter an at-the-money commodity forward, swap, or futures contract. Also, these contracts don't have any intermediate cash flows. As a result, the primary benefit of early exercise is to get cash immediately (exercising an in-the-money option) rather than cash in the future. In high interest rate environments, the money received immediately from immediate execution may exceed the extrinsic value of the contract. This is due to strike price not being present valued in immediate execution (it is specified in the contract and a fixed number) but the payoff of a European option is discounted (forward price - strike price).
352 |
353 | The overall result is that early exercise is fairly uncommon for most commodity options. Typically, it only occurs when interest rates are high. Generally, interest rates have to be higher than 15%-20% for American commodity options to differ substantially in value from European options with the same terms.
354 |
355 | The early exercise feature of **American commodity options** has value when:
356 | * Interest rates are high
357 | * Volatility is low (this makes selling the option less of a good option)
358 | * The option is in the money
359 |
360 | There is no exact closed-form solution for American options. However, there are many approximations that are reasonably close to prices produced by open-form solutions (like binomial tree models). Two models are shown below, both created by Bjerksund and Stensland. The first was produced in 1993 and the second in 2002. The second model is a refinement of the first model, adding more complexity, in exchange for better accuracy.
361 |
362 | #### Put-Call Parity
363 | Because of Put/Call parity, it is possible to use a call valuation formula to calculate the value of a put option.
364 |
365 | &space;=&space;C(X,S,T,r-b,-b,V))
366 |
367 | or using the order of parameters used in this library:
368 |
369 | &space;=&space;C(S,X,T,-b,r-b,V))
370 |
371 | #### Bjerksund Stensland 1993 (BS1993)
372 | There is no closed form solution for American options, and there are multiple people who have developed closed-form approximations to value American options. This is one such approximation. However, this approximation is no longer in active use by the public interface. It is primarily included as a secondary test on the BS2002 calculation. This function uses a numerical approximation to estimate the value of an American option. It does this by estimating a early exercise boundary and analytically estimating the probability of hitting that boundary. This uses the same inputs as a Generalized Black Scholes model:
373 |
374 | ```
375 | FS = Forward or spot price (abbreviated FS in code, F in formulas below)
376 | X = Strike Price
377 | T = time to expiration
378 | r = risk free rate
379 | b = cost of carry
380 | V = volatility
381 | ```
382 |
383 | _Intermediate Calculations_. To be consistent with the Bjerksund Stensland paper, this write-up uses similar notation. Please note that both a capital B (B_0 and B_Infinity), a lower case b, and the Greek symbol Beta are all being used. B_0 and B_infinity represent that optimal exercise boundaries in edge cases (for call options where T=0 and T=infinity respectively), lower case b is the cost of carry (passed in as an input), and Beta is an intermediate calculations.
384 |
385 | ![\begin{array}{lcl} \beta & = & (0.5 - \frac{b}{V^2}) + \sqrt{(\frac{b}{V^2} - 0.5)^2 + 2 \frac{r}{V^2}} \\ B_\infty & = & \frac{\beta}{\beta-1} X \\ B_0 & = & max\biggl[X, (\frac{r}{r-b}) X\biggr] \\ h_1 & = & - b T + 2 V \sqrt{T} \frac{B_0}{B_\infty-B_0} \\ \end{array}](https://latex.codecogs.com/svg.latex?\begin{array}{lcl}&space;\beta&space;&&space;=&space;&&space;(0.5&space;-&space;\frac{b}{V^2})&space;+&space;\sqrt{(\frac{b}{V^2}&space;-&space;0.5)^2&space;+&space;2&space;\frac{r}{V^2}}&space;\\&space;B_\infty&space;&&space;=&space;&&space;\frac{\beta}{\beta-1}&space;X&space;\\&space;B_0&space;&&space;=&space;&&space;max\biggl[X,&space;(\frac{r}{r-b})&space;X\biggr]&space;\\&space;h_1&space;&&space;=&space;&&space;-&space;b&space;T&space;+&space;2&space;V&space;\sqrt{T}&space;\frac{B_0}{B_\infty-B_0}&space;\\&space;\end{array})
386 |
387 | _Calculate the Early Exercise Boundary (i)_. The lower case i represents the early exercise boundary. Alpha is an intermediate calculation.
388 |
389 | (1&space;-&space;e^{h_1}&space;)&space;\\&space;\alpha&space;&&space;=&space;&&space;(i-X)&space;i^{-\beta}&space;\end{array})
390 |
391 | Check for immediate exercise.
392 |
393 | 
394 |
395 | If no immediate exercise, approximate the early exercise price.
396 |
397 | &space;\\&space;&&space;+&space;&&space;\phi(F,T,1,i,i,r,b,V)&space;\\&space;&&space;-&space;&&space;\phi(F,T,1,X,i,r,b,V)&space;\\&space;&&space;-&space;&&space;X&space;*&space;\phi(F,T,0,i,i,r,b,V)&space;\\&space;&&space;+&space;&&space;X&space;*&space;\phi(F,T,0,X,i,r,b,V)&space;\end{array})
398 |
399 | _Compare to European Value_.
400 | Due to the approximation, it is sometime possible to get a value slightly smaller than the European value. If so, set the value equal to the European value estimated using Generalized Black Scholes.
401 | ![Value_{BS1993} = Max \biggl[ Value, Value_{GBS} \biggr]](https://latex.codecogs.com/svg.latex?Value_{BS1993}&space;=&space;Max&space;\biggl[&space;Value,&space;Value_{GBS}&space;\biggr])
402 |
403 | #### Bjerksund Stensland 2002 (BS2002)
404 | Source: https://www.researchgate.net/publication/228801918
405 |
406 | ```
407 | FS = Forward or spot price (abbreviated FS in code, F in formulas below)
408 | X = Strike Price
409 | T = time to expiration
410 | r = risk free rate
411 | b = cost of carry
412 | V = volatility
413 | ```
414 |
415 | #### Psi
416 | Psi is an intermediate calculation used by the Bjerksund Stensland 2002 approximation.
417 | )
418 |
419 | _Intermediate calculations_.
420 | The Psi function has a large number of intermediate calculations. For clarity, these are loosely organized into groups with each group used to simplify the next set of intermediate calculations.
421 | ![\begin{array}{lcl} A_1 & = & V \ln(t_1) \\ A_2 & = & V \ln(t_2) \\ B_1 & = & \biggl[ b+(\gamma-0.5) V^2 \biggr] t_1 \\ B_2 & = & \biggl[ b+(\gamma-0.5) V^2 \biggr] t_2 \end{array}](https://latex.codecogs.com/svg.latex?\begin{array}{lcl}&space;A_1&space;&&space;=&space;&&space;V&space;\ln(t_1)&space;\\&space;A_2&space;&&space;=&space;&&space;V&space;\ln(t_2)&space;\\&space;B_1&space;&&space;=&space;&&space;\biggl[&space;b+(\gamma-0.5)&space;V^2&space;\biggr]&space;t_1&space;\\&space;B_2&space;&&space;=&space;&&space;\biggl[&space;b+(\gamma-0.5)&space;V^2&space;\biggr]&space;t_2&space;\end{array})
422 |
423 | More Intermediate calculations
424 | &space;+&space;B_1}{A_1}&space;\\&space;d_2&space;&&space;=&space;&&space;\frac{ln(\frac{I_2^2}{F&space;I_1})&space;+&space;B_1}{A_1}&space;\\&space;d_3&space;&&space;=&space;&&space;\frac{ln(\frac{F}{I_1})&space;-&space;B_1}{A_1}&space;\\&space;d_4&space;&&space;=&space;&&space;\frac{ln(\frac{I_2^2}{F&space;I_1})&space;-&space;B_1}{A_1}&space;\\&space;e_1&space;&&space;=&space;&&space;\frac{ln(\frac{F}{H})&space;+&space;B_2}{A_2}&space;\\&space;e_2&space;&&space;=&space;&&space;\frac{ln(\frac{I_2^2}{F&space;H})&space;+&space;B_2}{A_2}&space;\\&space;e_3&space;&&space;=&space;&&space;\frac{ln(\frac{I_1^2}{F&space;H})&space;+&space;B_2}{A_2}&space;\\&space;e_4&space;&&space;=&space;&&space;\frac{ln(\frac{F&space;I_1^2}{H&space;I_2^2})&space;+&space;B_2}{A_2}&space;\end{array})
425 |
426 | Even More Intermediate calculations
427 | &space;V^2&space;\\&space;\kappa&space;&&space;=&space;&&space;\frac{2b}{V^2}&space;+(2&space;\gamma&space;-&space;1)&space;\end{array})
428 |
429 | _The calculation of Psi_.
430 | This is the actual calculation of the Psi function. In the function below, M() represents the cumulative bivariate normal distribution (described a couple of paragraphs below this section). The abbreviation M() is used instead of CBND() in this section to make the equation a bit more readable and to match the naming convention used in Haug's book "The Complete Guide to Option Pricing Formulas".
431 | &space;\\&space;&&space;-&space;&&space;\frac{I_2}{F}^\kappa&space;M(-d_2,&space;-e_2,&space;\tau)&space;\\&space;&&space;-&space;&&space;\frac{I_1}{F}^\kappa&space;M(-d_3,&space;-e_3,&space;-\tau)&space;\\&space;&&space;+&space;&&space;\frac{I_1}{I_2}^\kappa&space;M(-d_4,&space;-e_4,&space;-\tau))&space;\end{array})
432 |
433 | #### Phi
434 | Phi is an intermediate calculation used by both the Bjerksun Stensland 1993 and 2002 approximations. Many of the parameters are the same as the GBS model.
435 | )
436 |
437 | ```
438 | FS = Forward or spot price (abbreviated FS in code, F in formulas below).
439 | T = time to expiration.
440 | I = trigger price (as calculated in either BS1993 or BS2002 formulas
441 | gamma = modifier to T, calculated in BS1993 or BS2002 formula
442 | r = risk free rate.
443 | b = cost of carry.
444 | V = volatility.
445 | ```
446 |
447 | Internally, the `Phi()` function is implemented as follows:
448 | ![d_1 = -\frac{ln(\frac{F}{h}) + \biggl[b+(\gamma-0.5) V^2 \biggr] T}{V \sqrt{T}}](https://latex.codecogs.com/svg.latex?d_1&space;=&space;-\frac{ln(\frac{F}{h})&space;+&space;\biggl[b+(\gamma-0.5)&space;V^2&space;\biggr]&space;T}{V&space;\sqrt{T}})
449 |
450 | }{V&space;\sqrt(T)})
451 |
452 | &space;V^2)
453 |
454 | )
455 |
456 | ![\phi = e^{\lambda T} F^{\gamma} \biggl[ N(d_1)-\frac{I}{F}^{\kappa} N(d_2) \biggr]](https://latex.codecogs.com/svg.latex?\phi&space;=&space;e^{\lambda&space;T}&space;F^{\gamma}&space;\biggl[&space;N(d_1)-\frac{I}{F}^{\kappa}&space;N(d_2)&space;\biggr])
457 |
458 | ##### Normal Cumulative Density Function (N)
459 | This is the normal cumulative density function. It can be found described in a variety of statistical textbooks and/or Wikipedia. It is part of the standard `scipy.stats` distribution and imported using the `from scipy.stats import norm` command.
460 |
461 | Example:
462 | )
463 |
464 | #### Cumulative bivariate normal distribution (CBND)
465 | The bivariate normal density function (BNDF) is given below (see Figure 8):
466 | ![BNDF(x, y) = \frac{1}{2 \pi \sqrt{1-p^2}} exp \biggl[-\frac{x^2-2pxy+y^2}{2(1-p^2)}\biggr]](https://latex.codecogs.com/svg.latex?BNDF(x,&space;y)&space;=&space;\frac{1}{2&space;\pi&space;\sqrt{1-p^2}}&space;exp&space;\biggl[-\frac{x^2-2pxy+y^2}{2(1-p^2)}\biggr])
467 |
468 | **Figure 8.** Bivariate Normal Density Function (BNDF).
469 |
470 | This can be integrated over x and y to calculate the joint probability that x < a and y < b. This is called the cumulative bivariate normal distribution (CBND; see Figure 9):
471 | ![CBND(a, b, p) = \frac{1}{2 \pi \sqrt{1-p^2}} \int_{-\infty}^{a} \int_{-\infty}^{b} exp \biggl[-\frac{x^2-2pxy+y^2}{2(1-p^2)}\biggr] d_x d_y](https://latex.codecogs.com/svg.latex?CBND(a,&space;b,&space;p)&space;=&space;\frac{1}{2&space;\pi&space;\sqrt{1-p^2}}&space;\int_{-\infty}^{a}&space;\int_{-\infty}^{b}&space;exp&space;\biggl[-\frac{x^2-2pxy+y^2}{2(1-p^2)}\biggr]&space;d_x&space;d_y)
472 |
473 | **Figure 9.** Cumulative Bivariate Normal Distribution (CBND).
474 |
475 | Where
476 | * x = the first variable
477 | * y = the second variable
478 | * a = upper bound for first variable
479 | * b = upper bound for second variable
480 | * p = correlation between first and second variables
481 |
482 | There is no closed-form solution for this equation. However, several approximations have been developed and are included in the `numpy` library distributed with Anaconda. The Genz 2004 model was chosen for implementation. Alternative models include those developed by Drezner and Wesolowsky (1990) and Drezner (1978). The Genz model improves these other model by going to an accuracy of 14 decimal points (from approximately 8 decimal points and 6 decimal points respectively).
483 |
484 | ## Limitations
485 | These functions have been tested for accuracy within an allowable range of inputs (see "Model Input" section below). However, general modeling advice applies to the use of the model. These models depend on a number of assumptions. In plain English, these models assume that the distribution of future prices can be described by variables like implied volatility. To get good results from the model, the model should only be used with reliable inputs.
486 |
487 | The following limitations are also in effect:
488 |
489 | 1. The Asian Option approximation shouldn't be used for Asian options that are into the Asian option calculation period.
490 | 2. The American and American76 approximations break down when `r` < -20%. The limits are set wider in this example for testing purposes, but production code should probably limit interest rates to values between -20% and 100%. In practice, negative interest rates should be extremely rare.
491 | 3. No Greeks are produced for spread options
492 | 4. These models assume a constant volatility term structure. This has no effect on European options. However, options that are likely to be exercise early (certain American options) and Asian options may be more affected.
493 |
494 | ## Model Inputs
495 | This section describes the function calls an inputs needed to call this model:
496 |
497 | These functions encapsulate the most commonly encountered option pricing formulas. These function primarily figure out the cost-of-carry term (b) and then call the generic version of the function. All of these functions return an array containing the premium and the Greeks.
498 |
499 | #### Public Functions in the Library
500 |
501 | Pricing Formulas
502 | 1. `black_scholes(option_type, fs, x, t, r, v)`
503 | 2. `merton(option_type, fs, x, t, r, q, v)`
504 | 3. `black_76(option_type, fs, x, t, r, v)`
505 | 4. `garman_kohlhagen(option_type, fs, x, t, b, r, rf, v)`
506 | 5. `asian_76(option_type, fs, x, t, t_a, r, v)`
507 | 6. `kirks_76(option_type, f1, f2, x, t, r, v1, v2, corr)`
508 | 7. `american(option_type, fs, x, t, r, q, v)`
509 | 8. `american_76(option_type, fs, x, t, r, v)`
510 |
511 | Implied Volatility Formulas
512 |
513 | 9. `euro_implied_vol(option_type, fs, x, t, r, q, cp)`
514 | 10. `euro_implied_vol_76(option_type, fs, x, t, r, cp)`
515 | 11. `amer_implied_vol(option_type, fs, x, t, r, q, cp)`
516 | 12. `amer_implied_vol_76(option_type, fs, x, t, r, cp)`
517 |
518 | #### Inputs used by all models
519 | | **Parameter** | **Description** |
520 | | ------------- | ------------------------------------------------------------ |
521 | | `option_type` | **Put/Call Indicator** Single character, `"c"` indicates a call; `"p"` a put |
522 | | `fs` | **Price of Underlying** `fs` is generically used, but for specific models, the following abbreviations may be used: F = Forward Price, S = Spot Price) |
523 | | `x` | **Strike Price** |
524 | | `t` | **Time to Maturity** This is in years (1.0 = 1 year, 0.5 = six months, etc) |
525 | | `r` | **Risk Free Interest Rate** Interest rates (0.10 = 10% interest rate) |
526 | | `v` | **Implied Volatility** Annualized implied volatility (1 = 100% annual volatility, 0.34 = 34% annual volatility) |
527 |
528 | #### Inputs used by some models
529 | | **Parameter** | **Description** |
530 | |---------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
531 | | `b` | **Cost of Carry** This is only found in internal implementations, but is identical to the cost of carry (`b`) term commonly found in academic option pricing literature |
532 | | `q` | **Continuous Dividend** Used in Merton and American models; Internally, this is converted into cost of carry, `b`, with formula `b = r - q` |
533 | | `rf` | **Foreign Interest Rate** Only used GK model; this functions similarly to `q` |
534 | | `t_a` | **Asian Start** Used for Asian options; This is the time that starts the averaging period (`t_a`=0 means that averaging starts immediately). As `t_a` approaches `t`, the Asian value should become very close to the Black76 Value |
535 | | `cp` | **Option Price** Used in the implied vol calculations; This is the price of the call or put observed in the market |
536 |
537 |
538 | #### Outputs
539 | All of the option pricing functions return an array. The first element of the array is the value of the option, the other elements are the Greeks which measure the sensitivity of the option to changes in inputs. The Greeks are used primarily for risk-management purposes.
540 |
541 | | **Output** | **Description** |
542 | |------------|-------------------------------------------------------------------------------------------------------------------|
543 | | [0] | **Value** |
544 | | [1] | **Delta** Sensitivity of Value to changes in price |
545 | | [2] | **Gamma** Sensitivity of Delta to changes in price |
546 | | [3] | **Theta** Sensitivity of Value to changes in time to expiration (annualized). To get a daily Theta, divide by 365 |
547 | | [4] | **Vega** Sensitivity of Value to changes in Volatility |
548 | | [5] | **Rho** Sensitivity of Value to changes in risk-free rates. |
549 |
550 | The implied volatility functions return a single value (the implied volatility).
551 |
552 | #### Acceptable Range for inputs
553 | All of the inputs are bounded. While many of these functions will work with inputs outside of these bounds, they haven't been tested and are generally believed to be uncommon. The pricer will return an exception to the caller if an out-of-bounds input is used. If that was a valid input, the code below will need to be modified to allow wider inputs and the testing section updated to test that the models work under the widened inputs.
554 |
--------------------------------------------------------------------------------
/optlib/__init__.py:
--------------------------------------------------------------------------------
1 | from .instruments import Pricehistory
2 | from .instruments import OptionChain
3 |
--------------------------------------------------------------------------------
/optlib/api.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import requests
3 | import os
4 |
5 | import logging
6 |
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 | # ------------------------------
11 | # This class defines the Exception that gets thrown when the api input is bad.
12 | class InputError(Exception):
13 | def __init__(self, msg):
14 | Exception.__init__(self, msg)
15 |
16 | # ------------------------------
17 | # This function gets the API key from env, throws exception if not found.
18 | def _get_env(varname):
19 | if (apikey := os.environ.get(varname)): return apikey
20 | raise InputError(f"'{varname}' not found in environment")
21 |
22 | # ------------------------------
23 | # This function formats a datetime.datetime object into a compatible date string.
24 | def _format_date(date):
25 | if not date:
26 | return None
27 | elif type(date) == datetime.date:
28 | return date.strftime("%Y-%m-%d")
29 | elif type(date) == datetime.datetime:
30 | return date.strftime("%Y-%m-%d'T'%H:%M:%S%z")
31 | else:
32 | raise InputError(f"Date must be of type `datetime.datetime` or `datetime`")
33 |
34 | # ------------------------------
35 | # This function sends the request to the specified API endpoint.
36 | def _get(endpoint, params={}):
37 | if "apikey" not in params:
38 | params.update({"apikey": _get_env("TDA_API_KEY")})
39 | r = requests.get(endpoint, params=params)
40 | r.raise_for_status()
41 | return r.json()
42 |
43 | # ------------------------------
44 | # This function gets data from the /chains endpoint.
45 | def get_chain(
46 | symbol: str,
47 | contract_type: str = "ALL",
48 | strike_count: int = None,
49 | include_quotes: str = "FALSE",
50 | strategy: str = "SINGLE",
51 | interval: int = None,
52 | strike: int = None,
53 | range: str = "ALL",
54 | from_date: str = None,
55 | to_date: str = None,
56 | volatility: float = None,
57 | underlying_price: float = None,
58 | interest_rate: float = None,
59 | days_to_expiration: int = None,
60 | exp_month: str = "ALL",
61 | option_type: str = "ALL",
62 | apikey: str = None
63 | ):
64 | """Request an option chain from TDAmeritrade's API.
65 |
66 | Args:
67 | symbol (str): Stock symbol to get the option chain for (required).
68 | contract_type (str): Type of contracts to return in the chain. Can be CALL, PUT, or ALL. Default is ALL.
69 | strike_count (int): The number of strikes to return above and below the at-the-money price.
70 | include_quotes (str): Include quotes for options in the option chain. Can be TRUE or FALSE. Default is FALSE.
71 | strategy (str): Passing a value returns a Strategy Chain. Default is SINGLE.
72 | interval (int): Strike interval for spread strategy chains.
73 | strike (int): Provide a strike price to return options only at that strike price.
74 | range (str): Returns options for the given range. Default is ALL.
75 | from_date (str): Only return expirations after this date.
76 | to_date (str): Only return expirations before this date.
77 | volatility (float): Volatility to use in calculations. Applies only to ANALYTICAL strategy chains.
78 | underlying_price (float): Underlying price to use in calculations. Applies only to ANALYTICAL strategy chains.
79 | interest_rate (float): Interest rate to use in calculations. Applies only to ANALYTICAL strategy chains.
80 | days_to_expiration (int): Days to expiration to use in calculations. Applies only to ANALYTICAL strategy chains.
81 | exp_month (str): Return only options expiring in the specified month. Example: JAN. Default is ALL.
82 | option_type (str): Type of contracts to return. Default is ALL.
83 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
84 |
85 | Returns:
86 | chain (OptionChain): API response with option chain.
87 | """
88 | params = {
89 | "symbol": symbol,
90 | "contractType": contract_type,
91 | "strikeCount": strike_count,
92 | "includeQuotes": include_quotes,
93 | "strategy": strategy,
94 | "interval": interval,
95 | "strike": strike,
96 | "range": range,
97 | "fromDate": _format_date(from_date),
98 | "toDate": _format_date(to_date),
99 | "volatility": volatility,
100 | "underlyingPrice": underlying_price,
101 | "interestRate": interest_rate,
102 | "daysToExpiration": days_to_expiration,
103 | "expMonth": exp_month,
104 | "optionType": option_type,
105 | "apikey": apikey
106 | }
107 |
108 | return _get(
109 | endpoint="https://api.tdameritrade.com/v1/marketdata/chains",
110 | params={ k: v for k, v in params.items() if v }
111 | )
112 |
113 | # ------------------------------
114 | # This function gets data from the /pricehistory endpoint.
115 | def get_pricehistory(
116 | symbol: str,
117 | period_type: str = "year",
118 | period: int = 1,
119 | frequency_type: str = "daily",
120 | frequency: int = 1,
121 | start_date: datetime.datetime = None,
122 | end_date: datetime.datetime = None,
123 | need_extended_hours_data: bool = False,
124 | apikey: str = None
125 | ):
126 | """Request pricehistory data from TDAmeritrade's API.
127 |
128 | Args:
129 | symbol (str): Stock symbol to get the option chain for (required).
130 | period_type (str): The type of period to show. Can be day, month, year, or ytd.
131 | period (int): The number of periods to show.
132 | frequency_type (str): The type of frequency with which a new candle is formed. Can be minute, daily, weekly, monthly.
133 | frequency (str): The number of the frequencyType to be included in each candle.
134 | start_date (int): Start date as milliseconds since epoch.
135 | end_date (int): End date as milliseconds since epoch.
136 | need_extended_hours_data (str): true to return extended hours data, false for regular market hours only.
137 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
138 |
139 | Returns:
140 | response (dict): API response with historical price data.
141 | """
142 | params = {
143 | "periodType": period_type,
144 | "period": period,
145 | "frequencyType": frequency_type,
146 | "frequency": frequency,
147 | "startDate": _format_date(start_date),
148 | "endDate": _format_date(end_date),
149 | "need_extended_hours_data": "true" if need_extended_hours_data else "false",
150 | "apikey": apikey
151 | }
152 |
153 | return _get(
154 | endpoint=f"https://api.tdameritrade.com/v1/marketdata/{symbol}/pricehistory",
155 | params={ k: v for k, v in params.items() if v }
156 | )
157 |
158 | # ------------------------------
159 | # This function gets data from the /instruments endpoint.
160 | def get_instrument(
161 | symbol: str,
162 | apikey: str = None
163 | ):
164 | """Retrieve instrument fundamental data.
165 |
166 | Args:
167 | symbol (str): Stock symbol to get the option chain for (required).
168 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
169 |
170 | Returns:
171 | response (dict): API response with fundamentals data.
172 | """
173 | params = {
174 | "apikey": apikey
175 | }
176 |
177 | return _get(
178 | endpoint=f"https://api.tdameritrade.com/v1/instruments/{symbol}",
179 | params={ k: v for k, v in params.items() if v }
180 | )
181 |
182 | # ------------------------------
183 | # This function gets data from the /instruments endpoint.
184 | def search_instrument(
185 | symbol: str,
186 | projection: str = "symbol-search",
187 | apikey: str = None
188 | ):
189 | """Search instruments.
190 |
191 | The following search methods are available:
192 |
193 | symbol-search: Retrieve instrument data of a specific symbol or cusip
194 |
195 | symbol-regex: Retrieve instrument data for all symbols matching regex.
196 | Example: symbol=XYZ.* will return all symbols beginning with XYZ
197 |
198 | desc-search: Retrieve instrument data for instruments whose description
199 | contains the word supplied. Example: symbol=FakeCompany will return
200 | all instruments with FakeCompany in the description.
201 |
202 | desc-regex: Search description with full regex support. Example: symbol=XYZ.[A-C]
203 | returns all instruments whose descriptions contain a word beginning with XYZ
204 | followed by a character A through C.
205 |
206 | fundamental: Returns fundamental data for a single instrument specified
207 | by exact symbol.
208 |
209 | Args:
210 | symbol (str): Stock symbol to get the option chain for (required).
211 | projection (str): One of symbol-search, symbol-regex, desc-search, desc-regex, fundamental.
212 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
213 |
214 | Returns:
215 | response (dict): API response with fundamentals data.
216 | """
217 | params = {
218 | "apikey": apikey,
219 | "symbol": symbol,
220 | "projection": projection
221 | }
222 |
223 | return _get(
224 | endpoint="https://api.tdameritrade.com/v1/instruments",
225 | params={ k: v for k, v in params.items() if v }
226 | )
227 |
228 | # ------------------------------
229 | # This function gets data from the /quotes endpoint.
230 | def get_quote(
231 | symbol: str,
232 | apikey: str = None
233 | ):
234 | """Get quote for a symbol.
235 |
236 | Args:
237 | symbol (str): Stock symbol to get the option chain for (required).
238 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
239 |
240 | Returns:
241 | response (dict): API response with price quote.
242 | """
243 | params = {
244 | "symbol": symbol,
245 | "apikey": apikey
246 | }
247 |
248 | return _get(
249 | endpoint=f"https://api.tdameritrade.com/v1/marketdata/{symbol}/quotes",
250 | params={ k: v for k, v in params.items() if v }
251 | )
252 |
253 | # ------------------------------
254 | # This function gets data from the /movers endpoint.
255 | def get_movers(
256 | index: str,
257 | direction: str = "up",
258 | change: str = "percent",
259 | apikey: str = None
260 | ):
261 | """Top 10 (up or down) movers by value or percent for a particular market.
262 |
263 | Args:
264 | index (str): The index symbol to get movers from. Can be $COMPX, $DJI, $SPX.X (required).
265 | direction (str): To return movers with the specified directions of `up` or `down`. Default: `up`.
266 | change (str): To return movers with the specified change types of `percent` or `value`. Default: `percent`.
267 | apikey (str): API key to api.tdameritrade.com (required, can be set in env: TDA_API_KEY).
268 |
269 | Returns:
270 | response (dict): API response with price quote.
271 | """
272 | params = {
273 | "direction": direction,
274 | "change": change,
275 | "apikey": apikey
276 | }
277 |
278 | return _get(
279 | endpoint=f"https://api.tdameritrade.com/v1/marketdata/{index}/movers",
280 | params={ k: v for k, v in params.items() if v }
281 | )
282 |
--------------------------------------------------------------------------------
/optlib/gbs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # File Contains: Python code containing closed-form solutions for the valuation of European Options,
5 | # American Options, Asian Options, Spread Options, Heat Rate Options, and Implied Volatility
6 | #
7 | # This document demonstrates a Python implementation of some option models described in books written by Davis
8 | # Edwards: "Energy Trading and Investing", "Risk Management in Trading", "Energy Investing Demystified".
9 | #
10 | # for backward compatability with Python 2.7
11 | from __future__ import division
12 |
13 | # import necessary libaries
14 | import math
15 | import numpy as np
16 | from scipy.stats import mvn, norm
17 |
18 | import logging
19 |
20 | logger = logging.getLogger(__name__)
21 |
22 |
23 | # This class contains the limits on inputs for GBS models
24 | # It is not intended to be part of this module's public interface
25 | class _GBS_Limits:
26 | # An GBS model will return an error if an out-of-bound input is input
27 | MAX32 = 2147483248.0
28 |
29 | MIN_T = 1.0 / 1000.0 # requires some time left before expiration
30 | MIN_X = 0.01
31 | MIN_FS = 0.01
32 |
33 | # Volatility smaller than 0.5% causes American Options calculations
34 | # to fail (Number to large errors).
35 | # GBS() should be OK with any positive number. Since vols less
36 | # than 0.5% are expected to be extremely rare, and most likely bad inputs,
37 | # _gbs() is assigned this limit too
38 | MIN_V = 0.005
39 |
40 | MAX_T = 100
41 | MAX_X = MAX32
42 | MAX_FS = MAX32
43 |
44 | # Asian Option limits
45 | # maximum TA is time to expiration for the option
46 | MIN_TA = 0
47 |
48 | # This model will work with higher values for b, r, and V. However, such values are extremely uncommon.
49 | # To catch some common errors, interest rates and volatility is capped to 200%
50 | # This reason for 2 (200%) is mostly to cause the library to throw an exceptions
51 | # if a value like 15% is entered as 15 rather than 0.15)
52 | MIN_b = -1
53 | MIN_r = -1
54 |
55 | MAX_b = 1
56 | MAX_r = 2
57 | MAX_V = 2
58 |
59 |
60 | # This class defines the Exception that gets thrown when invalid input is placed into the GBS function
61 | class GBS_InputError(Exception):
62 | def __init__(self, mismatch):
63 | Exception.__init__(self, mismatch)
64 |
65 |
66 | # This class defines the Exception that gets thrown when there is a calculation error
67 | class GBS_CalculationError(Exception):
68 | def __init__(self, mismatch):
69 | Exception.__init__(self, mismatch)
70 |
71 |
72 | # ------------------------
73 | # ## Model Implementation
74 | # These functions encapsulate a generic version of the pricing formulas. They are primarily intended to be called by the
75 | # other functions within this libary. The following functions will have a fixed interface so that they can be called
76 | # directly for academic applicaitons that use the cost-of-carry (b) notation:
77 | #
78 | # _GBS() A generalized European option model
79 | # _American() A generalized American option model
80 | # _GBS_ImpliedVol() A generalized European option implied vol calculator
81 | # _American_ImpliedVol() A generalized American option implied vol calculator
82 | #
83 | # The other functions in this libary are called by the four main functions and are not expected to be interface safe (the
84 | # implementation and interface may change over time).
85 |
86 | # ### Implementation: European Options
87 | # These functions implement the generalized Black Scholes (GBS) formula for European options. The main function is _gbs().
88 |
89 | # ------------------------------
90 | # This function verifies that the Call/Put indicator is correctly entered
91 | def _test_option_type(option_type):
92 | if (option_type != "c") and (option_type != "p"):
93 | raise GBS_InputError("Invalid Input option_type ({0}). Acceptable value are: c, p".format(option_type))
94 |
95 |
96 | # ------------------------------
97 | # This function makes sure inputs are OK
98 | # It throws an exception if there is a failure
99 | def _gbs_test_inputs(option_type, fs, x, t, r, b, v):
100 | # -----------
101 | # Test inputs are reasonable
102 | _test_option_type(option_type)
103 |
104 | if (x < _GBS_Limits.MIN_X) or (x > _GBS_Limits.MAX_X):
105 | raise GBS_InputError(
106 | "Invalid Input Strike Price (X). Acceptable range for inputs is {1} to {2}".format(x, _GBS_Limits.MIN_X,
107 | _GBS_Limits.MAX_X))
108 |
109 | if (fs < _GBS_Limits.MIN_FS) or (fs > _GBS_Limits.MAX_FS):
110 | raise GBS_InputError(
111 | "Invalid Input Forward/Spot Price (FS). Acceptable range for inputs is {1} to {2}".format(fs,
112 | _GBS_Limits.MIN_FS,
113 | _GBS_Limits.MAX_FS))
114 |
115 | if (t < _GBS_Limits.MIN_T) or (t > _GBS_Limits.MAX_T):
116 | raise GBS_InputError(
117 | "Invalid Input Time (T = {0}). Acceptable range for inputs is {1} to {2}".format(t, _GBS_Limits.MIN_T,
118 | _GBS_Limits.MAX_T))
119 |
120 | if (b < _GBS_Limits.MIN_b) or (b > _GBS_Limits.MAX_b):
121 | raise GBS_InputError(
122 | "Invalid Input Cost of Carry (b = {0}). Acceptable range for inputs is {1} to {2}".format(b,
123 | _GBS_Limits.MIN_b,
124 | _GBS_Limits.MAX_b))
125 |
126 | if (r < _GBS_Limits.MIN_r) or (r > _GBS_Limits.MAX_r):
127 | raise GBS_InputError(
128 | "Invalid Input Risk Free Rate (r = {0}). Acceptable range for inputs is {1} to {2}".format(r,
129 | _GBS_Limits.MIN_r,
130 | _GBS_Limits.MAX_r))
131 |
132 | if (v < _GBS_Limits.MIN_V) or (v > _GBS_Limits.MAX_V):
133 | raise GBS_InputError(
134 | "Invalid Input Implied Volatility (V = {0}). Acceptable range for inputs is {1} to {2}".format(v,
135 | _GBS_Limits.MIN_V,
136 | _GBS_Limits.MAX_V))
137 |
138 |
139 | # The primary class for calculating Generalized Black Scholes option prices and deltas
140 | # It is not intended to be part of this module's public interface
141 |
142 | # Inputs: option_type = "p" or "c", fs = price of underlying, x = strike, t = time to expiration, r = risk free rate
143 | # b = cost of carry, v = implied volatility
144 | # Outputs: value, delta, gamma, theta, vega, rho
145 | def _gbs(option_type, fs, x, t, r, b, v):
146 | logger.debug("Debugging Information: _gbs()")
147 | # -----------
148 | # Test Inputs (throwing an exception on failure)
149 | _gbs_test_inputs(option_type, fs, x, t, r, b, v)
150 |
151 | # -----------
152 | # Create preliminary calculations
153 | t__sqrt = math.sqrt(t)
154 | d1 = (math.log(fs / x) + (b + (v * v) / 2) * t) / (v * t__sqrt)
155 | d2 = d1 - v * t__sqrt
156 |
157 | if option_type == "c":
158 | # it's a call
159 | logger.debug(" Call Option")
160 | value = fs * math.exp((b - r) * t) * norm.cdf(d1) - x * math.exp(-r * t) * norm.cdf(d2)
161 | delta = math.exp((b - r) * t) * norm.cdf(d1)
162 | gamma = math.exp((b - r) * t) * norm.pdf(d1) / (fs * v * t__sqrt)
163 | theta = -(fs * v * math.exp((b - r) * t) * norm.pdf(d1)) / (2 * t__sqrt) - (b - r) * fs * math.exp(
164 | (b - r) * t) * norm.cdf(d1) - r * x * math.exp(-r * t) * norm.cdf(d2)
165 | vega = math.exp((b - r) * t) * fs * t__sqrt * norm.pdf(d1)
166 | rho = x * t * math.exp(-r * t) * norm.cdf(d2)
167 | else:
168 | # it's a put
169 | logger.debug(" Put Option")
170 | value = x * math.exp(-r * t) * norm.cdf(-d2) - (fs * math.exp((b - r) * t) * norm.cdf(-d1))
171 | delta = -math.exp((b - r) * t) * norm.cdf(-d1)
172 | gamma = math.exp((b - r) * t) * norm.pdf(d1) / (fs * v * t__sqrt)
173 | theta = -(fs * v * math.exp((b - r) * t) * norm.pdf(d1)) / (2 * t__sqrt) + (b - r) * fs * math.exp(
174 | (b - r) * t) * norm.cdf(-d1) + r * x * math.exp(-r * t) * norm.cdf(-d2)
175 | vega = math.exp((b - r) * t) * fs * t__sqrt * norm.pdf(d1)
176 | rho = -x * t * math.exp(-r * t) * norm.cdf(-d2)
177 |
178 | logger.debug(" d1= {0}\n d2 = {1}".format(d1, d2))
179 | logger.debug(" delta = {0}\n gamma = {1}\n theta = {2}\n vega = {3}\n rho={4}".format(delta, gamma,
180 | theta, vega,
181 | rho))
182 |
183 | return value, delta, gamma, theta, vega, rho
184 |
185 |
186 | # ### Implementation: American Options
187 | # This section contains the code necessary to price American options. The main function is _American().
188 | # The other functions are called from the main function.
189 |
190 | # -----------
191 | # Generalized American Option Pricer
192 | # This is a wrapper to check inputs and route to the current "best" American option model
193 | def _american_option(option_type, fs, x, t, r, b, v):
194 | # -----------
195 | # Test Inputs (throwing an exception on failure)
196 | logger.debug("Debugging Information: _american_option()")
197 | _gbs_test_inputs(option_type, fs, x, t, r, b, v)
198 |
199 | # -----------
200 | if option_type == "c":
201 | # Call Option
202 | logger.debug(" Call Option")
203 | return _bjerksund_stensland_2002(fs, x, t, r, b, v)
204 | else:
205 | # Put Option
206 | logger.debug(" Put Option")
207 |
208 | # Using the put-call transformation: P(X, FS, T, r, b, V) = C(FS, X, T, -b, r-b, V)
209 | # WARNING - When reconciling this code back to the B&S paper, the order of variables is different
210 |
211 | put__x = fs
212 | put_fs = x
213 | put_b = -b
214 | put_r = r - b
215 |
216 | # pass updated values into the Call Valuation formula
217 | return _bjerksund_stensland_2002(put_fs, put__x, t, put_r, put_b, v)
218 |
219 |
220 | # -----------
221 | # American Call Option (Bjerksund Stensland 1993 approximation)
222 | # This is primarily here for testing purposes; 2002 model has superseded this one
223 | def _bjerksund_stensland_1993(fs, x, t, r, b, v):
224 | # -----------
225 | # initialize output
226 | # using GBS greeks (TO DO: update greek calculations)
227 | my_output = _gbs("c", fs, x, t, r, b, v)
228 |
229 | e_value = my_output[0]
230 | delta = my_output[1]
231 | gamma = my_output[2]
232 | theta = my_output[3]
233 | vega = my_output[4]
234 | rho = my_output[5]
235 |
236 | # debugging for calculations
237 | logger.debug("-----")
238 | logger.debug("Debug Information: _Bjerksund_Stensland_1993())")
239 |
240 | # if b >= r, it is never optimal to exercise before maturity
241 | # so we can return the GBS value
242 | if b >= r:
243 | logger.debug(" b >= r, early exercise never optimal, returning GBS value")
244 | return e_value, delta, gamma, theta, vega, rho
245 |
246 | # Intermediate Calculations
247 | v2 = v ** 2
248 | sqrt_t = math.sqrt(t)
249 |
250 | beta = (0.5 - b / v2) + math.sqrt(((b / v2 - 0.5) ** 2) + 2 * r / v2)
251 | b_infinity = (beta / (beta - 1)) * x
252 | b_zero = max(x, (r / (r - b)) * x)
253 |
254 | h1 = -(b * t + 2 * v * sqrt_t) * (b_zero / (b_infinity - b_zero))
255 | i = b_zero + (b_infinity - b_zero) * (1 - math.exp(h1))
256 | alpha = (i - x) * (i ** (-beta))
257 |
258 | # debugging for calculations
259 | logger.debug(" b = {0}".format(b))
260 | logger.debug(" v2 = {0}".format(v2))
261 | logger.debug(" beta = {0}".format(beta))
262 | logger.debug(" b_infinity = {0}".format(b_infinity))
263 | logger.debug(" b_zero = {0}".format(b_zero))
264 | logger.debug(" h1 = {0}".format(h1))
265 | logger.debug(" i = {0}".format(i))
266 | logger.debug(" alpha = {0}".format(alpha))
267 |
268 | # Check for immediate exercise
269 | if fs >= i:
270 | logger.debug(" Immediate Exercise")
271 | value = fs - x
272 | else:
273 | logger.debug(" American Exercise")
274 | value = (alpha * (fs ** beta)
275 | - alpha * _phi(fs, t, beta, i, i, r, b, v)
276 | + _phi(fs, t, 1, i, i, r, b, v)
277 | - _phi(fs, t, 1, x, i, r, b, v)
278 | - x * _phi(fs, t, 0, i, i, r, b, v)
279 | + x * _phi(fs, t, 0, x, i, r, b, v))
280 |
281 | # The approximation can break down in boundary conditions
282 | # make sure the value is at least equal to the European value
283 | value = max(value, e_value)
284 | return value, delta, gamma, theta, vega, rho
285 |
286 |
287 | # -----------
288 | # American Call Option (Bjerksund Stensland 2002 approximation)
289 | def _bjerksund_stensland_2002(fs, x, t, r, b, v):
290 | # -----------
291 | # initialize output
292 | # using GBS greeks (TO DO: update greek calculations)
293 | my_output = _gbs("c", fs, x, t, r, b, v)
294 |
295 | e_value = my_output[0]
296 | delta = my_output[1]
297 | gamma = my_output[2]
298 | theta = my_output[3]
299 | vega = my_output[4]
300 | rho = my_output[5]
301 |
302 | # debugging for calculations
303 | logger.debug("-----")
304 | logger.debug("Debug Information: _Bjerksund_Stensland_2002())")
305 |
306 | # If b >= r, it is never optimal to exercise before maturity
307 | # so we can return the GBS value
308 | if b >= r:
309 | logger.debug(" Returning GBS value")
310 | return e_value, delta, gamma, theta, vega, rho
311 |
312 | # -----------
313 | # Create preliminary calculations
314 | v2 = v ** 2
315 | t1 = 0.5 * (math.sqrt(5) - 1) * t
316 | t2 = t
317 |
318 | beta_inside = ((b / v2 - 0.5) ** 2) + 2 * r / v2
319 | # forcing the inside of the sqrt to be a positive number
320 | beta_inside = abs(beta_inside)
321 | beta = (0.5 - b / v2) + math.sqrt(beta_inside)
322 | b_infinity = (beta / (beta - 1)) * x
323 | b_zero = max(x, (r / (r - b)) * x)
324 |
325 | h1 = -(b * t1 + 2 * v * math.sqrt(t1)) * ((x ** 2) / ((b_infinity - b_zero) * b_zero))
326 | h2 = -(b * t2 + 2 * v * math.sqrt(t2)) * ((x ** 2) / ((b_infinity - b_zero) * b_zero))
327 |
328 | i1 = b_zero + (b_infinity - b_zero) * (1 - math.exp(h1))
329 | i2 = b_zero + (b_infinity - b_zero) * (1 - math.exp(h2))
330 |
331 | alpha1 = (i1 - x) * (i1 ** (-beta))
332 | alpha2 = (i2 - x) * (i2 ** (-beta))
333 |
334 | # debugging for calculations
335 | logger.debug(" t1 = {0}".format(t1))
336 | logger.debug(" beta = {0}".format(beta))
337 | logger.debug(" b_infinity = {0}".format(b_infinity))
338 | logger.debug(" b_zero = {0}".format(b_zero))
339 | logger.debug(" h1 = {0}".format(h1))
340 | logger.debug(" h2 = {0}".format(h2))
341 | logger.debug(" i1 = {0}".format(i1))
342 | logger.debug(" i2 = {0}".format(i2))
343 | logger.debug(" alpha1 = {0}".format(alpha1))
344 | logger.debug(" alpha2 = {0}".format(alpha2))
345 |
346 | # check for immediate exercise
347 | if fs >= i2:
348 | value = fs - x
349 | else:
350 | # Perform the main calculation
351 | value = (alpha2 * (fs ** beta)
352 | - alpha2 * _phi(fs, t1, beta, i2, i2, r, b, v)
353 | + _phi(fs, t1, 1, i2, i2, r, b, v)
354 | - _phi(fs, t1, 1, i1, i2, r, b, v)
355 | - x * _phi(fs, t1, 0, i2, i2, r, b, v)
356 | + x * _phi(fs, t1, 0, i1, i2, r, b, v)
357 | + alpha1 * _phi(fs, t1, beta, i1, i2, r, b, v)
358 | - alpha1 * _psi(fs, t2, beta, i1, i2, i1, t1, r, b, v)
359 | + _psi(fs, t2, 1, i1, i2, i1, t1, r, b, v)
360 | - _psi(fs, t2, 1, x, i2, i1, t1, r, b, v)
361 | - x * _psi(fs, t2, 0, i1, i2, i1, t1, r, b, v)
362 | + x * _psi(fs, t2, 0, x, i2, i1, t1, r, b, v))
363 |
364 | # in boundary conditions, this approximation can break down
365 | # Make sure option value is greater than or equal to European value
366 | value = max(value, e_value)
367 |
368 | # -----------
369 | # Return Data
370 | return value, delta, gamma, theta, vega, rho
371 |
372 |
373 | # ---------------------------
374 | # American Option Intermediate Calculations
375 |
376 | # -----------
377 | # The Psi() function used by _Bjerksund_Stensland_2002 model
378 | def _psi(fs, t2, gamma, h, i2, i1, t1, r, b, v):
379 | vsqrt_t1 = v * math.sqrt(t1)
380 | vsqrt_t2 = v * math.sqrt(t2)
381 |
382 | bgamma_t1 = (b + (gamma - 0.5) * (v ** 2)) * t1
383 | bgamma_t2 = (b + (gamma - 0.5) * (v ** 2)) * t2
384 |
385 | d1 = (math.log(fs / i1) + bgamma_t1) / vsqrt_t1
386 | d3 = (math.log(fs / i1) - bgamma_t1) / vsqrt_t1
387 |
388 | d2 = (math.log((i2 ** 2) / (fs * i1)) + bgamma_t1) / vsqrt_t1
389 | d4 = (math.log((i2 ** 2) / (fs * i1)) - bgamma_t1) / vsqrt_t1
390 |
391 | e1 = (math.log(fs / h) + bgamma_t2) / vsqrt_t2
392 | e2 = (math.log((i2 ** 2) / (fs * h)) + bgamma_t2) / vsqrt_t2
393 | e3 = (math.log((i1 ** 2) / (fs * h)) + bgamma_t2) / vsqrt_t2
394 | e4 = (math.log((fs * (i1 ** 2)) / (h * (i2 ** 2))) + bgamma_t2) / vsqrt_t2
395 |
396 | tau = math.sqrt(t1 / t2)
397 | lambda1 = (-r + gamma * b + 0.5 * gamma * (gamma - 1) * (v ** 2))
398 | kappa = (2 * b) / (v ** 2) + (2 * gamma - 1)
399 |
400 | psi = math.exp(lambda1 * t2) * (fs ** gamma) * (_cbnd(-d1, -e1, tau)
401 | - ((i2 / fs) ** kappa) * _cbnd(-d2, -e2, tau)
402 | - ((i1 / fs) ** kappa) * _cbnd(-d3, -e3, -tau)
403 | + ((i1 / i2) ** kappa) * _cbnd(-d4, -e4, -tau))
404 | return psi
405 |
406 |
407 | # -----------
408 | # The Phi() function used by _Bjerksund_Stensland_2002 model and the _Bjerksund_Stensland_1993 model
409 | def _phi(fs, t, gamma, h, i, r, b, v):
410 | d1 = -(math.log(fs / h) + (b + (gamma - 0.5) * (v ** 2)) * t) / (v * math.sqrt(t))
411 | d2 = d1 - 2 * math.log(i / fs) / (v * math.sqrt(t))
412 |
413 | lambda1 = (-r + gamma * b + 0.5 * gamma * (gamma - 1) * (v ** 2))
414 | kappa = (2 * b) / (v ** 2) + (2 * gamma - 1)
415 |
416 | phi = math.exp(lambda1 * t) * (fs ** gamma) * (norm.cdf(d1) - ((i / fs) ** kappa) * norm.cdf(d2))
417 |
418 | logger.debug("-----")
419 | logger.debug("Debug info for: _phi()")
420 | logger.debug(" d1={0}".format(d1))
421 | logger.debug(" d2={0}".format(d2))
422 | logger.debug(" lambda={0}".format(lambda1))
423 | logger.debug(" kappa={0}".format(kappa))
424 | logger.debug(" phi={0}".format(phi))
425 | return phi
426 |
427 |
428 | # -----------
429 | # Cumulative Bivariate Normal Distribution
430 | # Primarily called by Psi() function, part of the _Bjerksund_Stensland_2002 model
431 | def _cbnd(a, b, rho):
432 | # This distribution uses the Genz multi-variate normal distribution
433 | # code found as part of the standard SciPy distribution
434 | lower = np.array([0, 0])
435 | upper = np.array([a, b])
436 | infin = np.array([0, 0])
437 | correl = rho
438 | error, value, inform = mvn.mvndst(lower, upper, infin, correl)
439 | return value
440 |
441 |
442 | # ### Implementation: Implied Vol
443 | # This section implements implied volatility calculations. It contains 3 main models:
444 | # 1. **At-the-Money approximation.** This is a very fast approximation for implied volatility. It is used
445 | # to estimate a starting point for the search functions.
446 | # 2. **Newton-Raphson Search.** This is a fast implied volatility search that can be used when there is a
447 | # reliable estimate of Vega (i.e., European options)
448 | # 3. **Bisection Search.** An implied volatility search (not quite as fast as a Newton search) that can be
449 | # used where there is no reliable Vega estimate (i.e., American options).
450 | #
451 |
452 | # ----------
453 | # Inputs (not all functions use all inputs)
454 | # fs = forward/spot price
455 | # x = Strike
456 | # t = Time (in years)
457 | # r = risk free rate
458 | # b = cost of carry
459 | # cp = Call or Put price
460 | # precision = (optional) precision at stopping point
461 | # max_steps = (optional) maximum number of steps
462 |
463 | # ----------
464 | # Approximate Implied Volatility
465 | #
466 | # This function is used to choose a starting point for the
467 | # search functions (Newton and bisection searches).
468 | # Brenner & Subrahmanyam (1988), Feinstein (1988)
469 | def _approx_implied_vol(option_type, fs, x, t, r, b, cp):
470 | _test_option_type(option_type)
471 |
472 | ebrt = math.exp((b - r) * t)
473 | ert = math.exp(-r * t)
474 |
475 | a = math.sqrt(2 * math.pi) / (fs * ebrt + x * ert)
476 |
477 | if option_type == "c":
478 | payoff = fs * ebrt - x * ert
479 | else:
480 | payoff = x * ert - fs * ebrt
481 |
482 | b = cp - payoff / 2
483 | c = (payoff ** 2) / math.pi
484 |
485 | v = (a * (b + math.sqrt(b ** 2 + c))) / math.sqrt(t)
486 |
487 | return v
488 |
489 |
490 | # ----------
491 | # Find the Implied Volatility of an European (GBS) Option given a price
492 | # using Newton-Raphson method for greater speed since Vega is available
493 | def _gbs_implied_vol(option_type, fs, x, t, r, b, cp, precision=.00001, max_steps=100):
494 | return _newton_implied_vol(_gbs, option_type, x, fs, t, b, r, cp, precision, max_steps)
495 |
496 |
497 | # ----------
498 | # Find the Implied Volatility of an American Option given a price
499 | # Using bisection method since Vega is difficult to estimate for Americans
500 | def _american_implied_vol(option_type, fs, x, t, r, b, cp, precision=.00001, max_steps=100):
501 | return _bisection_implied_vol(_american_option, option_type, fs, x, t, r, b, cp, precision, max_steps)
502 |
503 |
504 | # ----------
505 | # Calculate Implied Volatility with a Newton Raphson search
506 | def _newton_implied_vol(val_fn, option_type, x, fs, t, b, r, cp, precision=.00001, max_steps=100):
507 | # make sure a valid option type was entered
508 | _test_option_type(option_type)
509 |
510 | # Estimate starting Vol, making sure it is allowable range
511 | v = _approx_implied_vol(option_type, fs, x, t, r, b, cp)
512 | v = max(_GBS_Limits.MIN_V, v)
513 | v = min(_GBS_Limits.MAX_V, v)
514 |
515 | # Calculate the value at the estimated vol
516 | value, delta, gamma, theta, vega, rho = val_fn(option_type, fs, x, t, r, b, v)
517 | min_diff = abs(cp - value)
518 |
519 | logger.debug("-----")
520 | logger.debug("Debug info for: _Newton_ImpliedVol()")
521 | logger.debug(" Vinitial={0}".format(v))
522 |
523 | # Newton-Raphson Search
524 | countr = 0
525 | while precision <= abs(cp - value) <= min_diff and countr < max_steps:
526 |
527 | v = v - (value - cp) / vega
528 | if (v > _GBS_Limits.MAX_V) or (v < _GBS_Limits.MIN_V):
529 | logger.debug(" Volatility out of bounds")
530 | break
531 |
532 | value, delta, gamma, theta, vega, rho = val_fn(option_type, fs, x, t, r, b, v)
533 | min_diff = min(abs(cp - value), min_diff)
534 |
535 | # keep track of how many loops
536 | countr += 1
537 | logger.debug(" IVOL STEP {0}. v={1}".format(countr, v))
538 |
539 |
540 | # check if function converged and return a value
541 | if abs(cp - value) < precision:
542 | # the search function converged
543 | return v
544 | else:
545 | # if the search function didn't converge, try a bisection search
546 | return _bisection_implied_vol(val_fn, option_type, fs, x, t, r, b, cp, precision, max_steps)
547 |
548 |
549 | # ----------
550 | # Find the Implied Volatility using a Bisection search
551 | def _bisection_implied_vol(val_fn, option_type, fs, x, t, r, b, cp, precision=.00001, max_steps=100):
552 | logger.debug("-----")
553 | logger.debug("Debug info for: _bisection_implied_vol()")
554 |
555 | # Estimate Upper and Lower bounds on volatility
556 | # Assume American Implied vol is within +/- 50% of the GBS Implied Vol
557 | v_mid = _approx_implied_vol(option_type, fs, x, t, r, b, cp)
558 |
559 | if (v_mid <= _GBS_Limits.MIN_V) or (v_mid >= _GBS_Limits.MAX_V):
560 | # if the volatility estimate is out of bounds, search entire allowed vol space
561 | v_low = _GBS_Limits.MIN_V
562 | v_high = _GBS_Limits.MAX_V
563 | v_mid = (v_low + v_high) / 2
564 | else:
565 | # reduce the size of the vol space
566 | v_low = max(_GBS_Limits.MIN_V, v_mid * .5)
567 | v_high = min(_GBS_Limits.MAX_V, v_mid * 1.5)
568 |
569 | # Estimate the high/low bounds on price
570 | cp_mid = val_fn(option_type, fs, x, t, r, b, v_mid)[0]
571 |
572 | # initialize bisection loop
573 | current_step = 0
574 | diff = abs(cp - cp_mid)
575 |
576 | logger.debug(" American IVOL starting conditions: CP={0} cp_mid={1}".format(cp, cp_mid))
577 | logger.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
578 |
579 | # Keep bisection volatility until correct price is found
580 | while (diff > precision) and (current_step < max_steps):
581 | current_step += 1
582 |
583 | # Cut the search area in half
584 | if cp_mid < cp:
585 | v_low = v_mid
586 | else:
587 | v_high = v_mid
588 |
589 | cp_low = val_fn(option_type, fs, x, t, r, b, v_low)[0]
590 | cp_high = val_fn(option_type, fs, x, t, r, b, v_high)[0]
591 |
592 | v_mid = v_low + (cp - cp_low) * (v_high - v_low) / (cp_high - cp_low)
593 | v_mid = max(_GBS_Limits.MIN_V, v_mid) # enforce high/low bounds
594 | v_mid = min(_GBS_Limits.MAX_V, v_mid) # enforce high/low bounds
595 |
596 | cp_mid = val_fn(option_type, fs, x, t, r, b, v_mid)[0]
597 | diff = abs(cp - cp_mid)
598 |
599 | logger.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
600 |
601 | # return output
602 | if abs(cp - cp_mid) < precision:
603 | return v_mid
604 | else:
605 | raise GBS_CalculationError(
606 | "Implied Vol did not converge. Best Guess={0}, Price diff={1}, Required Precision={2}".format(v_mid, diff,
607 | precision))
608 |
609 |
610 | # --------------------
611 | # ### Public Interface for valuation functions
612 | # This section encapsulates the functions that user will call to value certain options. These function primarily
613 | # figure out the cost-of-carry term (b) and then call the generic version of the function (like _GBS() or _American).
614 | # All of these functions return an array containg the premium and the greeks.
615 |
616 | # This is the public interface for European Options
617 | # Each call does a little bit of processing and then calls the calculations located in the _gbs module
618 |
619 | # ---------------------------
620 | # Black Scholes: stock Options (no dividend yield)
621 | def black_scholes(option_type, fs, x, t, r, v):
622 | """ Generalized Black-Scholes formula for option pricing.
623 |
624 | In the traditional Black Scholes model, the option is based on common stock - an
625 | instrument that is traded at its present value. The stock price does not get present
626 | valued – it starts at its present value (a ‘spot price’) and drifts upwards over
627 | time at the risk free rate.
628 |
629 | Args:
630 | option_type (str): Type of the option. "p" for put and "c" for call options.
631 | fs (float): Price of underlying asset.
632 | x (float): Strike price.
633 | t (float): Time to expiration in years. 1 for one year, 0.5 for 6 months.
634 | r (float): Risk free rate.
635 | v (float): Implied volatility of underlying asset.
636 |
637 | Returns:
638 | value (float): Price of the option.
639 | delta (float): First derivative of value with respect to price of underlying.
640 | gamma (float): Second derivative of value w.r.t price of underlying.
641 | theta (float): First derivative of value w.r.t. time to expiration.
642 | vega (float): First derivative of value w.r.t. implied volatility.
643 | rho (float): First derivative of value w.r.t. risk free rates.
644 | """
645 | b = r
646 | return _gbs(option_type, fs, x, t, r, b, v)
647 |
648 | # ---------------------------
649 | # Merton Model: Stocks Index, stocks with a continuous dividend yields
650 | def merton(option_type, fs, x, t, r, q, v):
651 | """Pricing stock options with continuous dividend yields.
652 |
653 | The Merton model is a variation of the Black Scholes model for assets that pay
654 | dividends to shareholders. Dividends reduce the value of the option because the
655 | option owner does not own the right to dividends until the option is exercised.
656 |
657 | Args:
658 | option_type (str): Type of the option. "p" for put and "c" for call options.
659 | fs (float): Price of underlying asset.
660 | x (float): Strike price.
661 | t (float): Time to expiration in years. 1 for one year, 0.5 for 6 months.
662 | r (float): Risk free rate.
663 | q (float): Continuous yield of the underlying security.
664 | v (float): Implied volatility of underlying asset.
665 |
666 | Returns:
667 | value (float): Price of the option.
668 | delta (float): First derivative of value with respect to price of underlying.
669 | gamma (float): Second derivative of value w.r.t price of underlying.
670 | theta (float): First derivative of value w.r.t. time to expiration.
671 | vega (float): First derivative of value w.r.t. implied volatility.
672 | rho (float): First derivative of value w.r.t. risk free rates.
673 | """
674 | b = r - q
675 | return _gbs(option_type, fs, x, t, r, b, v)
676 |
677 |
678 | # ---------------------------
679 | # Commodities
680 | def black_76(option_type, fs, x, t, r, v):
681 | """Commodity option pricing.
682 |
683 | The Black 76 model is for an option where the underlying commodity is traded
684 | based on a future price rather than a spot price. Instead of dealing with a
685 | spot price that drifts upwards at the risk free rate, this model deals with
686 | a forward price that needs to be present valued.
687 |
688 | Args:
689 | option_type (str): Type of the option. "p" for put and "c" for call options.
690 | fs (float): Price of underlying asset.
691 | x (float): Strike price.
692 | t (float): Time to expiration in years. 1 for one year, 0.5 for 6 months.
693 | r (float): Risk free rate.
694 | v (float): Implied volatility of underlying asset.
695 |
696 | Returns:
697 | value (float): Price of the option.
698 | delta (float): First derivative of value with respect to price of underlying.
699 | gamma (float): Second derivative of value w.r.t price of underlying.
700 | theta (float): First derivative of value w.r.t. time to expiration.
701 | vega (float): First derivative of value w.r.t. implied volatility.
702 | rho (float): First derivative of value w.r.t. risk free rates.
703 | """
704 | b = 0
705 | return _gbs(option_type, fs, x, t, r, b, v)
706 |
707 |
708 | # ---------------------------
709 | # FX Options
710 | def garman_kohlhagen(option_type, fs, x, t, r, rf, v):
711 | """Foreign Exchange (FX) option pricing.
712 |
713 | The Garman Kohlhagen model is used to value foreign exchange (FX) options. In the
714 | Garman Kohlhagen model, each currency in the currency pair is discounted based on
715 | its own interest rate.
716 |
717 | Args:
718 | option_type (str): Type of the option. "p" for put and "c" for call options.
719 | fs (float): Price of underlying asset.
720 | x (float): Strike price.
721 | t (float): Time to expiration in years. 1 for one year, 0.5 for 6 months.
722 | r (float): Risk free rate.
723 | rf (float): Risk free rate of the foreign currency.
724 | v (float): Implied volatility of underlying asset.
725 |
726 | Returns:
727 | value (float): Price of the option.
728 | delta (float): First derivative of value with respect to price of underlying.
729 | gamma (float): Second derivative of value w.r.t price of underlying.
730 | theta (float): First derivative of value w.r.t. time to expiration.
731 | vega (float): First derivative of value w.r.t. implied volatility.
732 | rho (float): First derivative of value w.r.t. risk free rates.
733 | """
734 | b = r - rf
735 | return _gbs(option_type, fs, x, t, r, b, v)
736 |
737 |
738 | # ---------------------------
739 | # Average Price option on commodities
740 | def asian_76(option_type, fs, x, t, t_a, r, v):
741 | """Pricing function for average price options on commodities.
742 |
743 | Args:
744 | option_type (str): Type of the option. "p" for put and "c" for call options.
745 | fs (float): Price of underlying asset.
746 | x (float): Strike price.
747 | t (float): Time to expiration in years. 1 for one year, 0.5 for 6 months.
748 | t_a (float): The time to the start of the averaging period (measured in years).
749 | r (float): Risk free rate.
750 | v (float): Implied volatility of underlying asset.
751 |
752 | Returns:
753 | value (float): Price of the option.
754 | delta (float): First derivative of value with respect to price of underlying.
755 | gamma (float): Second derivative of value w.r.t price of underlying.
756 | theta (float): First derivative of value w.r.t. time to expiration.
757 | vega (float): First derivative of value w.r.t. implied volatility.
758 | rho (float): First derivative of value w.r.t. risk free rates.
759 | """
760 | # Check that TA is reasonable
761 | if (t_a < _GBS_Limits.MIN_TA) or (t_a > t):
762 | raise GBS_InputError(
763 | "Invalid Input Averaging Time (TA = {0}). Acceptable range for inputs is {1} to