From d8607fb0c707f950c3de1b3b10d44dc5714f2eb3 Mon Sep 17 00:00:00 2001 From: rgaveiga Date: Wed, 23 Apr 2025 22:27:28 -0300 Subject: [PATCH 1/4] Add documentation using pdoc --- .gitignore | 1 + CHANGELOG.md | 9 + docs/index.html | 7 + docs/optionlab.html | 673 +++++ docs/optionlab.png | Bin 0 -> 29413 bytes docs/optionlab/black_scholes.html | 1608 ++++++++++++ docs/optionlab/engine.html | 764 ++++++ docs/optionlab/models.html | 3837 +++++++++++++++++++++++++++++ docs/optionlab/optionlab.png | Bin 0 -> 29413 bytes docs/optionlab/plot.html | 640 +++++ docs/optionlab/price_array.html | 434 ++++ docs/optionlab/support.html | 1219 +++++++++ docs/optionlab/utils.html | 542 ++++ docs/search.js | 46 + optionlab/__init__.py | 327 ++- optionlab/black_scholes.py | 286 +-- optionlab/engine.py | 18 +- optionlab/models.py | 645 +++-- optionlab/plot.py | 10 +- optionlab/price_array.py | 27 +- optionlab/support.py | 253 +- optionlab/utils.py | 45 +- pyproject.toml | 2 +- tests/test_core.py | 2 +- tests/test_misc.py | 2 +- 25 files changed, 10685 insertions(+), 712 deletions(-) create mode 100644 docs/index.html create mode 100644 docs/optionlab.html create mode 100644 docs/optionlab.png create mode 100644 docs/optionlab/black_scholes.html create mode 100644 docs/optionlab/engine.html create mode 100644 docs/optionlab/models.html create mode 100644 docs/optionlab/optionlab.png create mode 100644 docs/optionlab/plot.html create mode 100644 docs/optionlab/price_array.html create mode 100644 docs/optionlab/support.html create mode 100644 docs/optionlab/utils.html create mode 100644 docs/search.js diff --git a/.gitignore b/.gitignore index 441ed83..facce31 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__ dist run_check *.old +runpdoc.bat diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e7276..0e53304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## 1.4.3 (2025-04-14) + +- Updated docstrings. +- Added documentation with `pdoc`. +- Changed __init__.py for compatibility with `pdoc` autodocumentation. +- Removed `BaseLeg` from models.py. +- Changed `StrategyType` to `StrategyLegType` in models.py for clarity. +- Removed "normal" as an alias for "black-scholes" to avoid confusion with Bachelier model. + ## 1.4.2 (2025-01-25) - Removed `expected_profit` and `expected_loss` calculation from `_get_pop_bs` in support.py; implementation was not correct, giving wrong results when compared with Monte Carlo simulations diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..ec14bbf --- /dev/null +++ b/docs/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/optionlab.html b/docs/optionlab.html new file mode 100644 index 0000000..c11ceb0 --- /dev/null +++ b/docs/optionlab.html @@ -0,0 +1,673 @@ + + + + + + + optionlab API documentation + + + + + + + + + +
+
+

+optionlab

+ +

OptionLab is...

+ +

... a Python library designed as a research tool for quickly evaluating options +strategy ideas. It is intended for a wide range of users, from individuals learning +about options trading to developers of quantitative strategies.

+ +

OptionLab calculations can produce a number of useful outputs:

+ +
    +
  • the profit/loss profile of the strategy on a user-defined target date,

  • +
  • the range of stock prices for which the strategy is profitable,

  • +
  • the Greeks associated with each leg of the strategy,

  • +
  • the resulting debit or credit on the trading account,

  • +
  • the maximum and minimum returns within a specified lower and higher price range +of the underlying asset,

  • +
  • the expected profit and expected loss, and

  • +
  • an estimate of the strategy's probability of profit.

  • +
+ +

The probability of profit (PoP) of the strategy on the user-defined target date
+is calculated by default using the Black-Scholes model. The user can alternatively +provide an array of underlying asset prices obtained elsewhere (e.g. from the +Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) +to be used in the calculations instead of the Black-Scholes model. This allows +OptionLab to function as a calculator using custom models.

+ +

An advanced feature of OptionLab that provides great flexibility in building +complex dynamic strategies is the ability to include previously created positions +as legs in a new strategy. Popular strategies that can benefit from this feature +include the Wheel and Covered Call strategies.

+ +

OptionLab is not...

+ +

... a platform for direct order execution. This capability has not been and +probably will not be implemented.

+ +

Backtesting and trade simulation using Monte Carlo have also not (yet) been +implemented in the API.

+ +

That being said, nothing prevents OptionLab from being integrated into an +options quant trader's workflow alongside other tools.

+ +

Installation

+ +

The easiest way to install OptionLab is using pip:

+ +
pip install optionlab
+
+ +

Quickstart

+ +

OptionLab is designed with ease of use in mind. An options strategy can be +defined and evaluated with just a few lines of Python code. The API is streamlined, +and the learning curve is minimal.

+ +

The evaluation of a strategy is done by calling the optionlab.engine.run_strategy +function provided by the library. This function receives the input data either +as a dictionary or an optionlab.models.Inputs object.

+ +

For example, let's say we wanted to calculate the probability of profit for naked +calls on Apple stocks expiring on December 17, 2021. The strategy setup consisted +of selling 100 175.00 strike calls for 1.15 each on November 22, 2021.

+ +

The input data for this strategy can be provided in a dictionary as follows:

+ +
+
input_data = {
+    "stock_price": 164.04,
+    "start_date": "2021-11-22",
+    "target_date": "2021-12-17",
+    "volatility": 0.272,
+    "interest_rate": 0.0002,
+    "min_stock": 120,
+    "max_stock": 200,
+    "strategy": [
+        {
+            "type": "call",
+            "strike": 175.0,
+            "premium": 1.15,
+            "n": 100,
+            "action":"sell"
+        }
+    ],
+}
+
+
+ +

Alternatively, the input data could be defined as the optionlab.models.Inputs +object below:

+ +
+
from optionlab import Inputs
+
+input_data = Inputs(
+    stock_price = 164.04,
+    start_date = "2021-11-22",
+    target_date = "2021-12-17",
+    volatility = 0.272,
+    interest_rate = 0.0002,
+    min_stock = 120,
+    max_stock = 200,
+    strategy = [
+        {
+            "type": "call",
+            "strike": 175.0,
+            "premium": 1.15,
+            "n": 100,
+            "action":"sell"
+        }
+    ],
+)
+
+
+ +

In both cases, the strategy itself is a list of dictionaries, where each dictionary +defines a leg in the strategy. The fields in a leg, depending on the type of the +leg, are described in optionlab.models.Stock, optionlab.models.Option, and +optionlab.models.ClosedPosition.

+ +

After defining the input data, we pass it to the run_strategy function as shown +below:

+ +
+
from optionlab import run_strategy, plot_pl
+
+out = run_strategy(input_data)
+
+print(out)
+
+plot_pl(out)
+
+
+ +

The variable out is an optionlab.models.Outputs object that contains the +results from the calculations. By calling print with out as an argument, +these results are displayed on screen.

+ +

The optionlab.plot.plot_pl function, in turn, takes an optionlab.models.Outputs +object as its argument and plots the profit/loss diagram for the strategy.

+ +

Examples

+ +

Examples for a number of popular options trading strategies can be found as +Jupyter notebooks in the examples +directory.

+
+ + + + + +
  1"""
+  2## OptionLab is...
+  3
+  4... a Python library designed as a research tool for quickly evaluating options 
+  5strategy ideas. It is intended for a wide range of users, from individuals learning 
+  6about options trading to developers of quantitative strategies.
+  7
+  8**OptionLab** calculations can produce a number of useful outputs:
+  9
+ 10- the profit/loss profile of the strategy on a user-defined target date,
+ 11
+ 12- the range of stock prices for which the strategy is profitable,
+ 13
+ 14- the Greeks associated with each leg of the strategy,
+ 15
+ 16- the resulting debit or credit on the trading account,
+ 17
+ 18- the maximum and minimum returns within a specified lower and higher price range 
+ 19of the underlying asset, 
+ 20
+ 21- the expected profit and expected loss, and
+ 22
+ 23- an estimate of the strategy's probability of profit.
+ 24
+ 25The probability of profit (PoP) of the strategy on the user-defined target date  
+ 26is calculated by default using the Black-Scholes model. The user can alternatively 
+ 27provide an array of underlying asset prices obtained elsewhere (e.g. from the 
+ 28Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) 
+ 29to be used in the calculations instead of the Black-Scholes model. This allows 
+ 30**OptionLab** to function as a calculator using custom models.
+ 31
+ 32An advanced feature of **OptionLab** that provides great flexibility in building 
+ 33complex dynamic strategies is the ability to include previously created positions 
+ 34as legs in a new strategy. Popular strategies that can benefit from this feature 
+ 35include the Wheel and Covered Call strategies.
+ 36
+ 37## OptionLab is not...
+ 38
+ 39... a platform for direct order execution. This capability has not been and 
+ 40probably will not be implemented.
+ 41
+ 42Backtesting and trade simulation using Monte Carlo have also not (yet) been 
+ 43implemented in the API.
+ 44
+ 45That being said, nothing prevents **OptionLab** from being integrated into an 
+ 46options quant trader's workflow alongside other tools.
+ 47
+ 48## Installation
+ 49
+ 50The easiest way to install **OptionLab** is using **pip**:
+ 51
+ 52```
+ 53pip install optionlab
+ 54```
+ 55
+ 56## Quickstart
+ 57
+ 58**OptionLab** is designed with ease of use in mind. An options strategy can be 
+ 59defined and evaluated with just a few lines of Python code. The API is streamlined, 
+ 60and the learning curve is minimal.
+ 61
+ 62The evaluation of a strategy is done by calling the `optionlab.engine.run_strategy` 
+ 63function provided by the library. This function receives the input data either 
+ 64as a dictionary or an `optionlab.models.Inputs` object.
+ 65
+ 66For example, let's say we wanted to calculate the probability of profit for naked 
+ 67calls on Apple stocks expiring on December 17, 2021. The strategy setup consisted 
+ 68of selling 100 175.00 strike calls for 1.15 each on November 22, 2021.
+ 69
+ 70The input data for this strategy can be provided in a dictionary as follows:
+ 71
+ 72```python
+ 73input_data = {
+ 74    "stock_price": 164.04,
+ 75    "start_date": "2021-11-22",
+ 76    "target_date": "2021-12-17",
+ 77    "volatility": 0.272,
+ 78    "interest_rate": 0.0002,
+ 79    "min_stock": 120,
+ 80    "max_stock": 200,
+ 81    "strategy": [
+ 82        {
+ 83            "type": "call",
+ 84            "strike": 175.0,
+ 85            "premium": 1.15,
+ 86            "n": 100,
+ 87            "action":"sell"
+ 88        }
+ 89    ],
+ 90}
+ 91```
+ 92
+ 93Alternatively, the input data could be defined as the `optionlab.models.Inputs` 
+ 94object below:
+ 95    
+ 96```python
+ 97from optionlab import Inputs
+ 98
+ 99input_data = Inputs(
+100    stock_price = 164.04,
+101    start_date = "2021-11-22",
+102    target_date = "2021-12-17",
+103    volatility = 0.272,
+104    interest_rate = 0.0002,
+105    min_stock = 120,
+106    max_stock = 200,
+107    strategy = [
+108        {
+109            "type": "call",
+110            "strike": 175.0,
+111            "premium": 1.15,
+112            "n": 100,
+113            "action":"sell"
+114        }
+115    ],
+116)
+117```
+118
+119In both cases, the strategy itself is a list of dictionaries, where each dictionary
+120defines a leg in the strategy. The fields in a leg, depending on the type of the
+121leg, are described in `optionlab.models.Stock`, `optionlab.models.Option`, and
+122`optionlab.models.ClosedPosition`.
+123
+124After defining the input data, we pass it to the `run_strategy` function as shown 
+125below:
+126
+127```python
+128from optionlab import run_strategy, plot_pl
+129
+130out = run_strategy(input_data)
+131
+132print(out)
+133
+134plot_pl(out)
+135```
+136
+137The variable `out` is an `optionlab.models.Outputs` object that contains the 
+138results from the calculations. By calling `print` with `out` as an argument, 
+139these results are displayed on screen. 
+140
+141The `optionlab.plot.plot_pl` function, in turn, takes an `optionlab.models.Outputs` 
+142object as its argument and plots the profit/loss diagram for the strategy.
+143
+144## Examples
+145
+146Examples for a number of popular options trading strategies can be found as 
+147Jupyter notebooks in the [examples](https://github.com/rgaveiga/optionlab/tree/main/examples) 
+148directory.
+149"""
+150
+151from .models import (
+152    Inputs,
+153    OptionType,
+154    Option,
+155    Outputs,
+156    ClosedPosition,
+157    ArrayInputs,
+158    TheoreticalModelInputs,
+159    BlackScholesModelInputs,
+160    LaplaceInputs,
+161    BlackScholesInfo,
+162    TheoreticalModel,
+163    FloatOrNdarray,
+164    StrategyLeg,
+165    StrategyLegType,
+166    Stock,
+167    Action,
+168)
+169from .black_scholes import (
+170    get_itm_probability,
+171    get_implied_vol,
+172    get_option_price,
+173    get_d1,
+174    get_d2,
+175    get_bs_info,
+176    get_vega,
+177    get_delta,
+178    get_gamma,
+179    get_theta,
+180    get_rho,
+181)
+182from .engine import run_strategy
+183from .plot import plot_pl
+184from .price_array import create_price_array
+185from .support import (
+186    get_pl_profile,
+187    get_pl_profile_stock,
+188    get_pl_profile_bs,
+189    create_price_seq,
+190    get_pop,
+191)
+192from .utils import (
+193    get_nonbusiness_days,
+194    get_pl,
+195    pl_to_csv,
+196)
+197
+198
+199VERSION = "1.4.3"
+200
+201__docformat__ = "markdown"
+202__version__ = VERSION
+203
+204
+205ALL = (
+206    # models
+207    "Inputs",
+208    "OptionType",
+209    "Option",
+210    "Outputs",
+211    "ClosedPosition",
+212    "ArrayInputs",
+213    "TheoreticalModelInputs",
+214    "BlackScholesModelInputs",
+215    "LaplaceInputs",
+216    "BlackScholesInfo",
+217    "TheoreticalModel",
+218    "FloatOrNdarray",
+219    "StrategyLeg",
+220    "StrategyLegType",
+221    "Stock",
+222    "Action",
+223    # engine
+224    "run_strategy",
+225    # support
+226    "get_pl_profile",
+227    "get_pl_profile_stock",
+228    "get_pl_profile_bs",
+229    "create_price_seq",
+230    "get_pop",
+231    # black_scholes
+232    "get_d1",
+233    "get_d2",
+234    "get_option_price",
+235    "get_itm_probability",
+236    "get_implied_vol",
+237    "get_bs_info",
+238    "get_vega",
+239    "get_delta",
+240    "get_gamma",
+241    "get_theta",
+242    "get_rho",
+243    # plot
+244    "plot_pl",
+245    # price_array
+246    "create_price_array",
+247    # utils
+248    "get_nonbusiness_days",
+249    "get_pl",
+250    "pl_to_csv",
+251)
+252"""@private"""
+253
+254
+255def __dir__() -> "list[str]":
+256    return list(ALL)
+
+ + +
+
+
+ VERSION = +'1.4.3' + + +
+ + + + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab.png b/docs/optionlab.png new file mode 100644 index 0000000000000000000000000000000000000000..bea8efff70a53066869aeda1932f4ef469205c2a GIT binary patch literal 29413 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 W+A22a^3jwnoE%*3_m1`X?tcI|d(8y^ literal 0 HcmV?d00001 diff --git a/docs/optionlab/black_scholes.html b/docs/optionlab/black_scholes.html new file mode 100644 index 0000000..eddd3d4 --- /dev/null +++ b/docs/optionlab/black_scholes.html @@ -0,0 +1,1608 @@ + + + + + + + optionlab.black_scholes API documentation + + + + + + + + + +
+
+

+optionlab.black_scholes

+ +

This module defines functions that calculate quantities, such as option prices +and the Greeks, related to the Black-Scholes model.

+
+ + + + + +
  1"""
+  2This module defines functions that calculate quantities, such as option prices
+  3and the Greeks, related to the Black-Scholes model.
+  4"""
+  5
+  6from __future__ import division
+  7
+  8from scipy import stats
+  9from numpy import exp, round, arange, abs, argmin, pi
+ 10from numpy.lib.scimath import log, sqrt
+ 11
+ 12from optionlab.models import BlackScholesInfo, OptionType, FloatOrNdarray
+ 13
+ 14
+ 15def get_bs_info(
+ 16    s: float,
+ 17    x: FloatOrNdarray,
+ 18    r: float,
+ 19    vol: float,
+ 20    years_to_maturity: float,
+ 21    y: float = 0.0,
+ 22) -> BlackScholesInfo:
+ 23    """
+ 24    Provides information about call and put options calculated using the Black-Scholes
+ 25    formula.
+ 26
+ 27    Parameters
+ 28    ----------
+ 29    `s`: stock price.
+ 30
+ 31    `x`: strike price(s).
+ 32
+ 33    `r`: annualized risk-free interest rate.
+ 34
+ 35    `vol`: annualized volatility.
+ 36
+ 37    `years_to_maturity`: time remaining to maturity, in years.
+ 38
+ 39    `y`: annualized dividend yield.
+ 40
+ 41    Returns
+ 42    -------
+ 43    Information calculated using the Black-Scholes formula.
+ 44    """
+ 45
+ 46    d1 = get_d1(s, x, r, vol, years_to_maturity, y)
+ 47    d2 = get_d2(s, x, r, vol, years_to_maturity, y)
+ 48    call_price = get_option_price("call", s, x, r, years_to_maturity, d1, d2, y)
+ 49    put_price = get_option_price("put", s, x, r, years_to_maturity, d1, d2, y)
+ 50    call_delta = get_delta("call", d1, years_to_maturity, y)
+ 51    put_delta = get_delta("put", d1, years_to_maturity, y)
+ 52    call_theta = get_theta("call", s, x, r, vol, years_to_maturity, d1, d2, y)
+ 53    put_theta = get_theta("put", s, x, r, vol, years_to_maturity, d1, d2, y)
+ 54    gamma = get_gamma(s, vol, years_to_maturity, d1, y)
+ 55    vega = get_vega(s, years_to_maturity, d1, y)
+ 56    call_rho = get_rho("call", x, r, years_to_maturity, d2)
+ 57    put_rho = get_rho("put", x, r, years_to_maturity, d2)
+ 58    call_itm_prob = get_itm_probability("call", d2, years_to_maturity, y)
+ 59    put_itm_prob = get_itm_probability("put", d2, years_to_maturity, y)
+ 60
+ 61    return BlackScholesInfo(
+ 62        call_price=call_price,
+ 63        put_price=put_price,
+ 64        call_delta=call_delta,
+ 65        put_delta=put_delta,
+ 66        call_theta=call_theta,
+ 67        put_theta=put_theta,
+ 68        gamma=gamma,
+ 69        vega=vega,
+ 70        call_rho=call_rho,
+ 71        put_rho=put_rho,
+ 72        call_itm_prob=call_itm_prob,
+ 73        put_itm_prob=put_itm_prob,
+ 74    )
+ 75
+ 76
+ 77def get_option_price(
+ 78    option_type: OptionType,
+ 79    s0: FloatOrNdarray,
+ 80    x: FloatOrNdarray,
+ 81    r: float,
+ 82    years_to_maturity: float,
+ 83    d1: FloatOrNdarray,
+ 84    d2: FloatOrNdarray,
+ 85    y: float = 0.0,
+ 86) -> FloatOrNdarray:
+ 87    """
+ 88    Returns the price of an option.
+ 89
+ 90    Parameters
+ 91    ----------
+ 92    `option_type`: either *'call'* or *'put'*.
+ 93
+ 94    `s0`: spot price(s) of the underlying asset.
+ 95
+ 96    `x`: strike price(s).
+ 97
+ 98    `r`: annualize risk-free interest rate.
+ 99
+100    `years_to_maturity`: time remaining to maturity, in years.
+101
+102    `d1`: `d1` in Black-Scholes formula.
+103
+104    `d2`: `d2` in Black-Scholes formula.
+105
+106    `y`: annualized dividend yield.
+107
+108    Returns
+109    -------
+110    Option price(s).
+111    """
+112
+113    s = s0 * exp(-y * years_to_maturity)
+114
+115    if option_type == "call":
+116        return round(
+117            s * stats.norm.cdf(d1)
+118            - x * exp(-r * years_to_maturity) * stats.norm.cdf(d2),
+119            2,
+120        )
+121    elif option_type == "put":
+122        return round(
+123            x * exp(-r * years_to_maturity) * stats.norm.cdf(-d2)
+124            - s * stats.norm.cdf(-d1),
+125            2,
+126        )
+127    else:
+128        raise ValueError("Option type must be either 'call' or 'put'!")
+129
+130
+131def get_delta(
+132    option_type: OptionType,
+133    d1: FloatOrNdarray,
+134    years_to_maturity: float,
+135    y: float = 0.0,
+136) -> FloatOrNdarray:
+137    """
+138    Returns the option's Greek Delta.
+139
+140    Parameters
+141    ----------
+142    `option_type`: either *'call'* or *'put'*.
+143
+144    `d1`: `d1` in Black-Scholes formula.
+145
+146    `years_to_maturity`: time remaining to maturity, in years.
+147
+148    `y`: annualized dividend yield.
+149
+150    Returns
+151    -------
+152    Option's Greek Delta.
+153    """
+154
+155    yfac = exp(-y * years_to_maturity)
+156
+157    if option_type == "call":
+158        return yfac * stats.norm.cdf(d1)
+159    elif option_type == "put":
+160        return yfac * (stats.norm.cdf(d1) - 1.0)
+161    else:
+162        raise ValueError("Option must be either 'call' or 'put'!")
+163
+164
+165def get_gamma(
+166    s0: float,
+167    vol: float,
+168    years_to_maturity: float,
+169    d1: FloatOrNdarray,
+170    y: float = 0.0,
+171) -> FloatOrNdarray:
+172    """
+173    Returns the option's Greek Gamma.
+174
+175    Parameters
+176    ----------
+177    `s0`: spot price of the underlying asset.
+178
+179    `vol`: annualized volatitily.
+180
+181    `years_to_maturity`: time remaining to maturity, in years.
+182
+183    `d1`: `d1` in Black-Scholes formula.
+184
+185    `y`: annualized divident yield.
+186
+187    Returns
+188    -------
+189    Option's Greek Gamma.
+190    """
+191
+192    yfac = exp(-y * years_to_maturity)
+193
+194    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+195
+196    return yfac * cdf_d1_prime / (s0 * vol * sqrt(years_to_maturity))
+197
+198
+199def get_theta(
+200    option_type: OptionType,
+201    s0: float,
+202    x: FloatOrNdarray,
+203    r: float,
+204    vol: float,
+205    years_to_maturity: float,
+206    d1: FloatOrNdarray,
+207    d2: FloatOrNdarray,
+208    y: float = 0.0,
+209) -> FloatOrNdarray:
+210    """
+211    Returns the option's Greek Theta.
+212
+213    Parameters
+214    ----------
+215    `option_type`: either *'call'* or *'put'*.
+216
+217    `s0`: spot price of the underlying asset.
+218
+219    `x`: strike price(s).
+220
+221    `r`: annualized risk-free interest rate.
+222
+223    `vol`: annualized volatility.
+224
+225    `years_to_maturity`: time remaining to maturity, in years.
+226
+227    `d1`: `d1` in Black-Scholes formula.
+228
+229    `d2`: `d2` in Black-Scholes formula.
+230
+231    `y`: annualized dividend yield.
+232
+233    Returns
+234    -------
+235    Option's Greek Theta.
+236    """
+237
+238    s = s0 * exp(-y * years_to_maturity)
+239
+240    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+241
+242    if option_type == "call":
+243        return -(
+244            s * vol * cdf_d1_prime / (2.0 * sqrt(years_to_maturity))
+245            + r * x * exp(-r * years_to_maturity) * stats.norm.cdf(d2)
+246            - y * s * stats.norm.cdf(d1)
+247        )
+248    elif option_type == "put":
+249        return -(
+250            s * vol * cdf_d1_prime / (2.0 * sqrt(years_to_maturity))
+251            - r * x * exp(-r * years_to_maturity) * stats.norm.cdf(-d2)
+252            + y * s * stats.norm.cdf(-d1)
+253        )
+254    else:
+255        raise ValueError("Option type must be either 'call' or 'put'!")
+256
+257
+258def get_vega(
+259    s0: float,
+260    years_to_maturity: float,
+261    d1: FloatOrNdarray,
+262    y: float = 0.0,
+263) -> FloatOrNdarray:
+264    """
+265    Returns the option's Greek Vega.
+266
+267    Parameters
+268    ----------
+269    `s0`: spot price of the underlying asset.
+270
+271    `years_to_maturity`: time remaining to maturity, in years.
+272
+273    `d1`: `d1` in Black-Scholes formula.
+274
+275    `y`: annualized dividend yield.
+276
+277    Returns
+278    -------
+279    Option's Greek Vega.
+280    """
+281
+282    s = s0 * exp(-y * years_to_maturity)
+283
+284    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+285
+286    return s * cdf_d1_prime * sqrt(years_to_maturity) / 100
+287
+288
+289def get_rho(
+290    option_type: OptionType,
+291    x: FloatOrNdarray,
+292    r: float,
+293    years_to_maturity: float,
+294    d2: FloatOrNdarray,
+295) -> FloatOrNdarray:
+296    """
+297    Returns the option's Greek Rho.
+298
+299    Parameters
+300    ----------
+301    `option_type`: either *'call'* or *'put'*.
+302
+303    `x`: strike price(s).
+304
+305    `r`: annualized risk-free interest rate.
+306
+307    `years_to_maturity`: time remaining to maturity, in years.
+308
+309    `d2`: `d2` in Black-Scholes formula.
+310
+311    Returns
+312    -------
+313    Option's Greek Rho.
+314    """
+315
+316    if option_type == "call":
+317        return (
+318            x
+319            * years_to_maturity
+320            * exp(-r * years_to_maturity)
+321            * stats.norm.cdf(d2)
+322            / 100
+323        )
+324    elif option_type == "put":
+325        return (
+326            -x
+327            * years_to_maturity
+328            * exp(-r * years_to_maturity)
+329            * stats.norm.cdf(-d2)
+330            / 100
+331        )
+332    else:
+333        raise ValueError("Option must be either 'call' or 'put'!")
+334
+335
+336def get_d1(
+337    s0: FloatOrNdarray,
+338    x: FloatOrNdarray,
+339    r: float,
+340    vol: FloatOrNdarray,
+341    years_to_maturity: float,
+342    y: float = 0.0,
+343) -> FloatOrNdarray:
+344    """
+345    Returns `d1` used in Black-Scholes formula.
+346
+347    Parameters
+348    ----------
+349    `s0`: spot price(s) of the underlying asset.
+350
+351    `x`: strike price(s).
+352
+353    `r`: annualized risk-free interest rate.
+354
+355    `vol`: annualized volatility(ies).
+356
+357    `years_to_maturity`: time remaining to maturity, in years.
+358
+359    `y`: annualized divident yield.
+360
+361    Returns
+362    -------
+363    `d1` in Black-Scholes formula.
+364    """
+365
+366    return (log(s0 / x) + (r - y + vol * vol / 2.0) * years_to_maturity) / (
+367        vol * sqrt(years_to_maturity)
+368    )
+369
+370
+371def get_d2(
+372    s0: FloatOrNdarray,
+373    x: FloatOrNdarray,
+374    r: float,
+375    vol: FloatOrNdarray,
+376    years_to_maturity: float,
+377    y: float = 0.0,
+378) -> FloatOrNdarray:
+379    """
+380    Returns `d2` used in Black-Scholes formula.
+381
+382    Parameters
+383    ----------
+384    `s0`: spot price(s) of the underlying asset.
+385
+386    `x`: strike price(s).
+387
+388    `r`: annualized risk-free interest rate.
+389
+390    `vol`: annualized volatility(ies).
+391
+392    `years_to_maturity`: time remaining to maturity, in years.
+393
+394    `y`: annualized divident yield.
+395
+396    Returns
+397    -------
+398    `d2` in Black-Scholes formula.
+399    """
+400
+401    return (log(s0 / x) + (r - y - vol * vol / 2.0) * years_to_maturity) / (
+402        vol * sqrt(years_to_maturity)
+403    )
+404
+405
+406def get_implied_vol(
+407    option_type: OptionType,
+408    oprice: float,
+409    s0: float,
+410    x: float,
+411    r: float,
+412    years_to_maturity: float,
+413    y: float = 0.0,
+414) -> float:
+415    """
+416    Returns the implied volatility of an option.
+417
+418    Parameters
+419    ----------
+420    `option_type`: either *'call'* or *'put'*.
+421
+422    `oprice`: market price of an option.
+423
+424    `s0`: spot price of the underlying asset.
+425
+426    `x`: strike price.
+427
+428    `r`: annualized risk-free interest rate.
+429
+430    `years_to_maturity`: time remaining to maturity, in years.
+431
+432    `y`: annualized dividend yield.
+433
+434    Returns
+435    -------
+436    Option's implied volatility.
+437    """
+438
+439    vol = 0.001 * arange(1, 1001)
+440    d1 = get_d1(s0, x, r, vol, years_to_maturity, y)
+441    d2 = get_d2(s0, x, r, vol, years_to_maturity, y)
+442    dopt = abs(
+443        get_option_price(option_type, s0, x, r, years_to_maturity, d1, d2, y) - oprice
+444    )
+445
+446    return vol[argmin(dopt)]
+447
+448
+449def get_itm_probability(
+450    option_type: OptionType,
+451    d2: FloatOrNdarray,
+452    years_to_maturity: float,
+453    y: float = 0.0,
+454) -> FloatOrNdarray:
+455    """
+456    Returns the probability(ies) that the option(s) will expire in-the-money (ITM).
+457
+458    Parameters
+459    ----------
+460    `option_type`: either *'call'* or *'put'*.
+461
+462    `d2`: `d2` in Black-Scholes formula.
+463
+464    `years_to_maturity`: time remaining to maturity, in years.
+465
+466    `y`: annualized dividend yield.
+467
+468    Returns
+469    -------
+470    Probability(ies) that the option(s) will expire in-the-money (ITM).
+471    """
+472
+473    yfac = exp(-y * years_to_maturity)
+474
+475    if option_type == "call":
+476        return yfac * stats.norm.cdf(d2)
+477    elif option_type == "put":
+478        return yfac * stats.norm.cdf(-d2)
+479    else:
+480        raise ValueError("Option type must be either 'call' or 'put'!")
+
+ + +
+
+ +
+ + def + get_bs_info( s: float, x: float | numpy.ndarray, r: float, vol: float, years_to_maturity: float, y: float = 0.0) -> optionlab.models.BlackScholesInfo: + + + +
+ +
16def get_bs_info(
+17    s: float,
+18    x: FloatOrNdarray,
+19    r: float,
+20    vol: float,
+21    years_to_maturity: float,
+22    y: float = 0.0,
+23) -> BlackScholesInfo:
+24    """
+25    Provides information about call and put options calculated using the Black-Scholes
+26    formula.
+27
+28    Parameters
+29    ----------
+30    `s`: stock price.
+31
+32    `x`: strike price(s).
+33
+34    `r`: annualized risk-free interest rate.
+35
+36    `vol`: annualized volatility.
+37
+38    `years_to_maturity`: time remaining to maturity, in years.
+39
+40    `y`: annualized dividend yield.
+41
+42    Returns
+43    -------
+44    Information calculated using the Black-Scholes formula.
+45    """
+46
+47    d1 = get_d1(s, x, r, vol, years_to_maturity, y)
+48    d2 = get_d2(s, x, r, vol, years_to_maturity, y)
+49    call_price = get_option_price("call", s, x, r, years_to_maturity, d1, d2, y)
+50    put_price = get_option_price("put", s, x, r, years_to_maturity, d1, d2, y)
+51    call_delta = get_delta("call", d1, years_to_maturity, y)
+52    put_delta = get_delta("put", d1, years_to_maturity, y)
+53    call_theta = get_theta("call", s, x, r, vol, years_to_maturity, d1, d2, y)
+54    put_theta = get_theta("put", s, x, r, vol, years_to_maturity, d1, d2, y)
+55    gamma = get_gamma(s, vol, years_to_maturity, d1, y)
+56    vega = get_vega(s, years_to_maturity, d1, y)
+57    call_rho = get_rho("call", x, r, years_to_maturity, d2)
+58    put_rho = get_rho("put", x, r, years_to_maturity, d2)
+59    call_itm_prob = get_itm_probability("call", d2, years_to_maturity, y)
+60    put_itm_prob = get_itm_probability("put", d2, years_to_maturity, y)
+61
+62    return BlackScholesInfo(
+63        call_price=call_price,
+64        put_price=put_price,
+65        call_delta=call_delta,
+66        put_delta=put_delta,
+67        call_theta=call_theta,
+68        put_theta=put_theta,
+69        gamma=gamma,
+70        vega=vega,
+71        call_rho=call_rho,
+72        put_rho=put_rho,
+73        call_itm_prob=call_itm_prob,
+74        put_itm_prob=put_itm_prob,
+75    )
+
+ + +

Provides information about call and put options calculated using the Black-Scholes +formula.

+ +

Parameters

+ +

s: stock price.

+ +

x: strike price(s).

+ +

r: annualized risk-free interest rate.

+ +

vol: annualized volatility.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Information calculated using the Black-Scholes formula.

+
+ + +
+
+ +
+ + def + get_option_price( option_type: Literal['call', 'put'], s0: float | numpy.ndarray, x: float | numpy.ndarray, r: float, years_to_maturity: float, d1: float | numpy.ndarray, d2: float | numpy.ndarray, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
 78def get_option_price(
+ 79    option_type: OptionType,
+ 80    s0: FloatOrNdarray,
+ 81    x: FloatOrNdarray,
+ 82    r: float,
+ 83    years_to_maturity: float,
+ 84    d1: FloatOrNdarray,
+ 85    d2: FloatOrNdarray,
+ 86    y: float = 0.0,
+ 87) -> FloatOrNdarray:
+ 88    """
+ 89    Returns the price of an option.
+ 90
+ 91    Parameters
+ 92    ----------
+ 93    `option_type`: either *'call'* or *'put'*.
+ 94
+ 95    `s0`: spot price(s) of the underlying asset.
+ 96
+ 97    `x`: strike price(s).
+ 98
+ 99    `r`: annualize risk-free interest rate.
+100
+101    `years_to_maturity`: time remaining to maturity, in years.
+102
+103    `d1`: `d1` in Black-Scholes formula.
+104
+105    `d2`: `d2` in Black-Scholes formula.
+106
+107    `y`: annualized dividend yield.
+108
+109    Returns
+110    -------
+111    Option price(s).
+112    """
+113
+114    s = s0 * exp(-y * years_to_maturity)
+115
+116    if option_type == "call":
+117        return round(
+118            s * stats.norm.cdf(d1)
+119            - x * exp(-r * years_to_maturity) * stats.norm.cdf(d2),
+120            2,
+121        )
+122    elif option_type == "put":
+123        return round(
+124            x * exp(-r * years_to_maturity) * stats.norm.cdf(-d2)
+125            - s * stats.norm.cdf(-d1),
+126            2,
+127        )
+128    else:
+129        raise ValueError("Option type must be either 'call' or 'put'!")
+
+ + +

Returns the price of an option.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

s0: spot price(s) of the underlying asset.

+ +

x: strike price(s).

+ +

r: annualize risk-free interest rate.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

d1: d1 in Black-Scholes formula.

+ +

d2: d2 in Black-Scholes formula.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Option price(s).

+
+ + +
+
+ +
+ + def + get_delta( option_type: Literal['call', 'put'], d1: float | numpy.ndarray, years_to_maturity: float, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
132def get_delta(
+133    option_type: OptionType,
+134    d1: FloatOrNdarray,
+135    years_to_maturity: float,
+136    y: float = 0.0,
+137) -> FloatOrNdarray:
+138    """
+139    Returns the option's Greek Delta.
+140
+141    Parameters
+142    ----------
+143    `option_type`: either *'call'* or *'put'*.
+144
+145    `d1`: `d1` in Black-Scholes formula.
+146
+147    `years_to_maturity`: time remaining to maturity, in years.
+148
+149    `y`: annualized dividend yield.
+150
+151    Returns
+152    -------
+153    Option's Greek Delta.
+154    """
+155
+156    yfac = exp(-y * years_to_maturity)
+157
+158    if option_type == "call":
+159        return yfac * stats.norm.cdf(d1)
+160    elif option_type == "put":
+161        return yfac * (stats.norm.cdf(d1) - 1.0)
+162    else:
+163        raise ValueError("Option must be either 'call' or 'put'!")
+
+ + +

Returns the option's Greek Delta.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

d1: d1 in Black-Scholes formula.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Option's Greek Delta.

+
+ + +
+
+ +
+ + def + get_gamma( s0: float, vol: float, years_to_maturity: float, d1: float | numpy.ndarray, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
166def get_gamma(
+167    s0: float,
+168    vol: float,
+169    years_to_maturity: float,
+170    d1: FloatOrNdarray,
+171    y: float = 0.0,
+172) -> FloatOrNdarray:
+173    """
+174    Returns the option's Greek Gamma.
+175
+176    Parameters
+177    ----------
+178    `s0`: spot price of the underlying asset.
+179
+180    `vol`: annualized volatitily.
+181
+182    `years_to_maturity`: time remaining to maturity, in years.
+183
+184    `d1`: `d1` in Black-Scholes formula.
+185
+186    `y`: annualized divident yield.
+187
+188    Returns
+189    -------
+190    Option's Greek Gamma.
+191    """
+192
+193    yfac = exp(-y * years_to_maturity)
+194
+195    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+196
+197    return yfac * cdf_d1_prime / (s0 * vol * sqrt(years_to_maturity))
+
+ + +

Returns the option's Greek Gamma.

+ +

Parameters

+ +

s0: spot price of the underlying asset.

+ +

vol: annualized volatitily.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

d1: d1 in Black-Scholes formula.

+ +

y: annualized divident yield.

+ +

Returns

+ +

Option's Greek Gamma.

+
+ + +
+
+ +
+ + def + get_theta( option_type: Literal['call', 'put'], s0: float, x: float | numpy.ndarray, r: float, vol: float, years_to_maturity: float, d1: float | numpy.ndarray, d2: float | numpy.ndarray, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
200def get_theta(
+201    option_type: OptionType,
+202    s0: float,
+203    x: FloatOrNdarray,
+204    r: float,
+205    vol: float,
+206    years_to_maturity: float,
+207    d1: FloatOrNdarray,
+208    d2: FloatOrNdarray,
+209    y: float = 0.0,
+210) -> FloatOrNdarray:
+211    """
+212    Returns the option's Greek Theta.
+213
+214    Parameters
+215    ----------
+216    `option_type`: either *'call'* or *'put'*.
+217
+218    `s0`: spot price of the underlying asset.
+219
+220    `x`: strike price(s).
+221
+222    `r`: annualized risk-free interest rate.
+223
+224    `vol`: annualized volatility.
+225
+226    `years_to_maturity`: time remaining to maturity, in years.
+227
+228    `d1`: `d1` in Black-Scholes formula.
+229
+230    `d2`: `d2` in Black-Scholes formula.
+231
+232    `y`: annualized dividend yield.
+233
+234    Returns
+235    -------
+236    Option's Greek Theta.
+237    """
+238
+239    s = s0 * exp(-y * years_to_maturity)
+240
+241    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+242
+243    if option_type == "call":
+244        return -(
+245            s * vol * cdf_d1_prime / (2.0 * sqrt(years_to_maturity))
+246            + r * x * exp(-r * years_to_maturity) * stats.norm.cdf(d2)
+247            - y * s * stats.norm.cdf(d1)
+248        )
+249    elif option_type == "put":
+250        return -(
+251            s * vol * cdf_d1_prime / (2.0 * sqrt(years_to_maturity))
+252            - r * x * exp(-r * years_to_maturity) * stats.norm.cdf(-d2)
+253            + y * s * stats.norm.cdf(-d1)
+254        )
+255    else:
+256        raise ValueError("Option type must be either 'call' or 'put'!")
+
+ + +

Returns the option's Greek Theta.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

s0: spot price of the underlying asset.

+ +

x: strike price(s).

+ +

r: annualized risk-free interest rate.

+ +

vol: annualized volatility.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

d1: d1 in Black-Scholes formula.

+ +

d2: d2 in Black-Scholes formula.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Option's Greek Theta.

+
+ + +
+
+ +
+ + def + get_vega( s0: float, years_to_maturity: float, d1: float | numpy.ndarray, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
259def get_vega(
+260    s0: float,
+261    years_to_maturity: float,
+262    d1: FloatOrNdarray,
+263    y: float = 0.0,
+264) -> FloatOrNdarray:
+265    """
+266    Returns the option's Greek Vega.
+267
+268    Parameters
+269    ----------
+270    `s0`: spot price of the underlying asset.
+271
+272    `years_to_maturity`: time remaining to maturity, in years.
+273
+274    `d1`: `d1` in Black-Scholes formula.
+275
+276    `y`: annualized dividend yield.
+277
+278    Returns
+279    -------
+280    Option's Greek Vega.
+281    """
+282
+283    s = s0 * exp(-y * years_to_maturity)
+284
+285    cdf_d1_prime = exp(-0.5 * d1 * d1) / sqrt(2.0 * pi)
+286
+287    return s * cdf_d1_prime * sqrt(years_to_maturity) / 100
+
+ + +

Returns the option's Greek Vega.

+ +

Parameters

+ +

s0: spot price of the underlying asset.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

d1: d1 in Black-Scholes formula.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Option's Greek Vega.

+
+ + +
+
+ +
+ + def + get_rho( option_type: Literal['call', 'put'], x: float | numpy.ndarray, r: float, years_to_maturity: float, d2: float | numpy.ndarray) -> float | numpy.ndarray: + + + +
+ +
290def get_rho(
+291    option_type: OptionType,
+292    x: FloatOrNdarray,
+293    r: float,
+294    years_to_maturity: float,
+295    d2: FloatOrNdarray,
+296) -> FloatOrNdarray:
+297    """
+298    Returns the option's Greek Rho.
+299
+300    Parameters
+301    ----------
+302    `option_type`: either *'call'* or *'put'*.
+303
+304    `x`: strike price(s).
+305
+306    `r`: annualized risk-free interest rate.
+307
+308    `years_to_maturity`: time remaining to maturity, in years.
+309
+310    `d2`: `d2` in Black-Scholes formula.
+311
+312    Returns
+313    -------
+314    Option's Greek Rho.
+315    """
+316
+317    if option_type == "call":
+318        return (
+319            x
+320            * years_to_maturity
+321            * exp(-r * years_to_maturity)
+322            * stats.norm.cdf(d2)
+323            / 100
+324        )
+325    elif option_type == "put":
+326        return (
+327            -x
+328            * years_to_maturity
+329            * exp(-r * years_to_maturity)
+330            * stats.norm.cdf(-d2)
+331            / 100
+332        )
+333    else:
+334        raise ValueError("Option must be either 'call' or 'put'!")
+
+ + +

Returns the option's Greek Rho.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

x: strike price(s).

+ +

r: annualized risk-free interest rate.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

d2: d2 in Black-Scholes formula.

+ +

Returns

+ +

Option's Greek Rho.

+
+ + +
+
+ +
+ + def + get_d1( s0: float | numpy.ndarray, x: float | numpy.ndarray, r: float, vol: float | numpy.ndarray, years_to_maturity: float, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
337def get_d1(
+338    s0: FloatOrNdarray,
+339    x: FloatOrNdarray,
+340    r: float,
+341    vol: FloatOrNdarray,
+342    years_to_maturity: float,
+343    y: float = 0.0,
+344) -> FloatOrNdarray:
+345    """
+346    Returns `d1` used in Black-Scholes formula.
+347
+348    Parameters
+349    ----------
+350    `s0`: spot price(s) of the underlying asset.
+351
+352    `x`: strike price(s).
+353
+354    `r`: annualized risk-free interest rate.
+355
+356    `vol`: annualized volatility(ies).
+357
+358    `years_to_maturity`: time remaining to maturity, in years.
+359
+360    `y`: annualized divident yield.
+361
+362    Returns
+363    -------
+364    `d1` in Black-Scholes formula.
+365    """
+366
+367    return (log(s0 / x) + (r - y + vol * vol / 2.0) * years_to_maturity) / (
+368        vol * sqrt(years_to_maturity)
+369    )
+
+ + +

Returns d1 used in Black-Scholes formula.

+ +

Parameters

+ +

s0: spot price(s) of the underlying asset.

+ +

x: strike price(s).

+ +

r: annualized risk-free interest rate.

+ +

vol: annualized volatility(ies).

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized divident yield.

+ +

Returns

+ +

d1 in Black-Scholes formula.

+
+ + +
+
+ +
+ + def + get_d2( s0: float | numpy.ndarray, x: float | numpy.ndarray, r: float, vol: float | numpy.ndarray, years_to_maturity: float, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
372def get_d2(
+373    s0: FloatOrNdarray,
+374    x: FloatOrNdarray,
+375    r: float,
+376    vol: FloatOrNdarray,
+377    years_to_maturity: float,
+378    y: float = 0.0,
+379) -> FloatOrNdarray:
+380    """
+381    Returns `d2` used in Black-Scholes formula.
+382
+383    Parameters
+384    ----------
+385    `s0`: spot price(s) of the underlying asset.
+386
+387    `x`: strike price(s).
+388
+389    `r`: annualized risk-free interest rate.
+390
+391    `vol`: annualized volatility(ies).
+392
+393    `years_to_maturity`: time remaining to maturity, in years.
+394
+395    `y`: annualized divident yield.
+396
+397    Returns
+398    -------
+399    `d2` in Black-Scholes formula.
+400    """
+401
+402    return (log(s0 / x) + (r - y - vol * vol / 2.0) * years_to_maturity) / (
+403        vol * sqrt(years_to_maturity)
+404    )
+
+ + +

Returns d2 used in Black-Scholes formula.

+ +

Parameters

+ +

s0: spot price(s) of the underlying asset.

+ +

x: strike price(s).

+ +

r: annualized risk-free interest rate.

+ +

vol: annualized volatility(ies).

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized divident yield.

+ +

Returns

+ +

d2 in Black-Scholes formula.

+
+ + +
+
+ +
+ + def + get_implied_vol( option_type: Literal['call', 'put'], oprice: float, s0: float, x: float, r: float, years_to_maturity: float, y: float = 0.0) -> float: + + + +
+ +
407def get_implied_vol(
+408    option_type: OptionType,
+409    oprice: float,
+410    s0: float,
+411    x: float,
+412    r: float,
+413    years_to_maturity: float,
+414    y: float = 0.0,
+415) -> float:
+416    """
+417    Returns the implied volatility of an option.
+418
+419    Parameters
+420    ----------
+421    `option_type`: either *'call'* or *'put'*.
+422
+423    `oprice`: market price of an option.
+424
+425    `s0`: spot price of the underlying asset.
+426
+427    `x`: strike price.
+428
+429    `r`: annualized risk-free interest rate.
+430
+431    `years_to_maturity`: time remaining to maturity, in years.
+432
+433    `y`: annualized dividend yield.
+434
+435    Returns
+436    -------
+437    Option's implied volatility.
+438    """
+439
+440    vol = 0.001 * arange(1, 1001)
+441    d1 = get_d1(s0, x, r, vol, years_to_maturity, y)
+442    d2 = get_d2(s0, x, r, vol, years_to_maturity, y)
+443    dopt = abs(
+444        get_option_price(option_type, s0, x, r, years_to_maturity, d1, d2, y) - oprice
+445    )
+446
+447    return vol[argmin(dopt)]
+
+ + +

Returns the implied volatility of an option.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

oprice: market price of an option.

+ +

s0: spot price of the underlying asset.

+ +

x: strike price.

+ +

r: annualized risk-free interest rate.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Option's implied volatility.

+
+ + +
+
+ +
+ + def + get_itm_probability( option_type: Literal['call', 'put'], d2: float | numpy.ndarray, years_to_maturity: float, y: float = 0.0) -> float | numpy.ndarray: + + + +
+ +
450def get_itm_probability(
+451    option_type: OptionType,
+452    d2: FloatOrNdarray,
+453    years_to_maturity: float,
+454    y: float = 0.0,
+455) -> FloatOrNdarray:
+456    """
+457    Returns the probability(ies) that the option(s) will expire in-the-money (ITM).
+458
+459    Parameters
+460    ----------
+461    `option_type`: either *'call'* or *'put'*.
+462
+463    `d2`: `d2` in Black-Scholes formula.
+464
+465    `years_to_maturity`: time remaining to maturity, in years.
+466
+467    `y`: annualized dividend yield.
+468
+469    Returns
+470    -------
+471    Probability(ies) that the option(s) will expire in-the-money (ITM).
+472    """
+473
+474    yfac = exp(-y * years_to_maturity)
+475
+476    if option_type == "call":
+477        return yfac * stats.norm.cdf(d2)
+478    elif option_type == "put":
+479        return yfac * stats.norm.cdf(-d2)
+480    else:
+481        raise ValueError("Option type must be either 'call' or 'put'!")
+
+ + +

Returns the probability(ies) that the option(s) will expire in-the-money (ITM).

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

d2: d2 in Black-Scholes formula.

+ +

years_to_maturity: time remaining to maturity, in years.

+ +

y: annualized dividend yield.

+ +

Returns

+ +

Probability(ies) that the option(s) will expire in-the-money (ITM).

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab/engine.html b/docs/optionlab/engine.html new file mode 100644 index 0000000..5fae081 --- /dev/null +++ b/docs/optionlab/engine.html @@ -0,0 +1,764 @@ + + + + + + + optionlab.engine API documentation + + + + + + + + + +
+
+

+optionlab.engine

+ +

This module defines the run_strategy function.

+ +

Given input data provided as either an optionlab.models.Inputs object or a dictionary, +run_strategy returns the results of an options strategy calculation (e.g., the +probability of profit on the target date) as an optionlab.models.Outputs object.

+
+ + + + + +
  1"""
+  2This module defines the `run_strategy` function.
+  3
+  4Given input data provided as either an `optionlab.models.Inputs` object or a dictionary,
+  5`run_strategy` returns the results of an options strategy calculation (e.g., the
+  6probability of profit on the target date) as an `optionlab.models.Outputs` object.
+  7"""
+  8
+  9from __future__ import division
+ 10from __future__ import print_function
+ 11
+ 12import datetime as dt
+ 13
+ 14from numpy import zeros, array
+ 15
+ 16
+ 17from optionlab.black_scholes import get_bs_info, get_implied_vol
+ 18from optionlab.models import (
+ 19    Inputs,
+ 20    Action,
+ 21    Option,
+ 22    Stock,
+ 23    ClosedPosition,
+ 24    Outputs,
+ 25    BlackScholesModelInputs,
+ 26    ArrayInputs,
+ 27    OptionType,
+ 28    EngineData,
+ 29    PoPOutputs,
+ 30)
+ 31from optionlab.support import (
+ 32    get_pl_profile,
+ 33    get_pl_profile_stock,
+ 34    get_pl_profile_bs,
+ 35    create_price_seq,
+ 36    get_pop,
+ 37)
+ 38from optionlab.utils import get_nonbusiness_days
+ 39
+ 40
+ 41def run_strategy(inputs_data: Inputs | dict) -> Outputs:
+ 42    """
+ 43    Runs the calculation for a strategy.
+ 44
+ 45    Parameters
+ 46    ----------
+ 47    `inputs_data`: input data used in the strategy calculation.
+ 48
+ 49    Returns
+ 50    -------
+ 51    Output data from the strategy calculation.
+ 52    """
+ 53
+ 54    inputs = (
+ 55        inputs_data
+ 56        if isinstance(inputs_data, Inputs)
+ 57        else Inputs.model_validate(inputs_data)
+ 58    )
+ 59
+ 60    data = _init_inputs(inputs)
+ 61
+ 62    data = _run(data)
+ 63
+ 64    return _generate_outputs(data)
+ 65
+ 66
+ 67def _init_inputs(inputs: Inputs) -> EngineData:
+ 68    data = EngineData(
+ 69        stock_price_array=create_price_seq(inputs.min_stock, inputs.max_stock),
+ 70        terminal_stock_prices=inputs.array if inputs.model == "array" else array([]),
+ 71        inputs=inputs,
+ 72    )
+ 73
+ 74    data.days_in_year = (
+ 75        inputs.business_days_in_year if inputs.discard_nonbusiness_days else 365
+ 76    )
+ 77
+ 78    if inputs.start_date and inputs.target_date:
+ 79        if inputs.discard_nonbusiness_days:
+ 80            n_discarded_days = get_nonbusiness_days(
+ 81                inputs.start_date, inputs.target_date, inputs.country
+ 82            )
+ 83        else:
+ 84            n_discarded_days = 0
+ 85
+ 86        data.days_to_target = (
+ 87            (inputs.target_date - inputs.start_date).days + 1 - n_discarded_days
+ 88        )
+ 89    else:
+ 90        data.days_to_target = inputs.days_to_target_date
+ 91
+ 92    for i, strategy in enumerate(inputs.strategy):
+ 93        data.type.append(strategy.type)
+ 94
+ 95        if isinstance(strategy, Option):
+ 96            data.strike.append(strategy.strike)
+ 97            data.premium.append(strategy.premium)
+ 98            data.n.append(strategy.n)
+ 99            data.action.append(strategy.action)
+100            data.previous_position.append(strategy.prev_pos or 0.0)
+101
+102            if not strategy.expiration:
+103                data.days_to_maturity.append(data.days_to_target)
+104                data.use_bs.append(False)
+105            elif isinstance(strategy.expiration, dt.date) and inputs.start_date:
+106                if inputs.discard_nonbusiness_days:
+107                    n_discarded_days = get_nonbusiness_days(
+108                        inputs.start_date, strategy.expiration, inputs.country
+109                    )
+110                else:
+111                    n_discarded_days = 0
+112
+113                data.days_to_maturity.append(
+114                    (strategy.expiration - inputs.start_date).days
+115                    + 1
+116                    - n_discarded_days
+117                )
+118
+119                data.use_bs.append(strategy.expiration != inputs.target_date)
+120            elif isinstance(strategy.expiration, int):
+121                if strategy.expiration >= data.days_to_target:
+122                    data.days_to_maturity.append(strategy.expiration)
+123
+124                    data.use_bs.append(strategy.expiration != data.days_to_target)
+125                else:
+126                    raise ValueError(
+127                        "Days remaining to maturity must be greater than or equal to the number of days remaining to the target date!"
+128                    )
+129            else:
+130                raise ValueError("Expiration must be a date, an int or None.")
+131
+132        elif isinstance(strategy, Stock):
+133            data.n.append(strategy.n)
+134            data.action.append(strategy.action)
+135            data.previous_position.append(strategy.prev_pos or 0.0)
+136            data.strike.append(0.0)
+137            data.premium.append(0.0)
+138            data.use_bs.append(False)
+139            data.days_to_maturity.append(-1)
+140
+141        elif isinstance(strategy, ClosedPosition):
+142            data.previous_position.append(strategy.prev_pos)
+143            data.strike.append(0.0)
+144            data.n.append(0)
+145            data.premium.append(0.0)
+146            data.action.append("n/a")
+147            data.use_bs.append(False)
+148            data.days_to_maturity.append(-1)
+149        else:
+150            raise ValueError("Type must be 'call', 'put', 'stock' or 'closed'!")
+151
+152    return data
+153
+154
+155def _run(data: EngineData) -> EngineData:
+156    inputs = data.inputs
+157
+158    time_to_target = data.days_to_target / data.days_in_year
+159    data.cost = [0.0] * len(data.type)
+160
+161    data.profit = zeros((len(data.type), data.stock_price_array.shape[0]))
+162    data.strategy_profit = zeros(data.stock_price_array.shape[0])
+163
+164    if inputs.model == "array":
+165        data.profit_mc = zeros((len(data.type), data.terminal_stock_prices.shape[0]))
+166        data.strategy_profit_mc = zeros(data.terminal_stock_prices.shape[0])
+167
+168    pop_inputs: BlackScholesModelInputs | ArrayInputs
+169    pop_out: PoPOutputs
+170
+171    for i, type in enumerate(data.type):
+172        if type in ("call", "put"):
+173            _run_option_calcs(data, i)
+174        elif type == "stock":
+175            _run_stock_calcs(data, i)
+176        elif type == "closed":
+177            _run_closed_position_calcs(data, i)
+178
+179        data.strategy_profit += data.profit[i]
+180
+181        if inputs.model == "array":
+182            data.strategy_profit_mc += data.profit_mc[i]
+183
+184    if inputs.model == "black-scholes":
+185        pop_inputs = BlackScholesModelInputs(
+186            stock_price=inputs.stock_price,
+187            volatility=inputs.volatility,
+188            years_to_target_date=time_to_target,
+189            interest_rate=inputs.interest_rate,
+190            dividend_yield=inputs.dividend_yield,
+191        )
+192    elif inputs.model == "array":
+193        pop_inputs = ArrayInputs(array=data.strategy_profit_mc)
+194    else:
+195        raise ValueError("Model is not valid!")
+196
+197    pop_out = get_pop(data.stock_price_array, data.strategy_profit, pop_inputs)
+198
+199    data.profit_probability = pop_out.probability_of_reaching_target
+200    data.expected_profit = pop_out.expected_return_above_target
+201    data.expected_loss = pop_out.expected_return_below_target
+202    data.profit_ranges = pop_out.reaching_target_range
+203
+204    if inputs.profit_target is not None and inputs.profit_target > 0.01:
+205        pop_out_prof_targ = get_pop(
+206            data.stock_price_array,
+207            data.strategy_profit,
+208            pop_inputs,
+209            inputs.profit_target,
+210        )
+211        data.profit_target_probability = (
+212            pop_out_prof_targ.probability_of_reaching_target
+213        )
+214        data.profit_target_ranges = pop_out_prof_targ.reaching_target_range
+215
+216    if inputs.loss_limit is not None and inputs.loss_limit < 0.0:
+217        pop_out_loss_lim = get_pop(
+218            data.stock_price_array,
+219            data.strategy_profit,
+220            pop_inputs,
+221            inputs.loss_limit + 0.01,
+222        )
+223        data.loss_limit_probability = pop_out_loss_lim.probability_of_missing_target
+224        data.loss_limit_ranges = pop_out_loss_lim.missing_target_range
+225
+226    return data
+227
+228
+229def _run_option_calcs(data: EngineData, i: int) -> EngineData:
+230    inputs = data.inputs
+231    action: Action = data.action[i]  # type: ignore
+232    type: OptionType = data.type[i]  # type: ignore
+233
+234    if data.previous_position[i] < 0.0:
+235        # Previous position is closed
+236        data.implied_volatility.append(0.0)
+237        data.itm_probability.append(0.0)
+238        data.delta.append(0.0)
+239        data.gamma.append(0.0)
+240        data.vega.append(0.0)
+241        data.theta.append(0.0)
+242        data.rho.append(0.0)
+243
+244        cost = (data.premium[i] + data.previous_position[i]) * data.n[i]
+245
+246        if data.action[i] == "buy":
+247            cost *= -1.0
+248
+249        data.cost[i] = cost
+250        data.profit[i] += cost
+251
+252        if inputs.model == "array":
+253            data.profit_mc[i] += cost
+254
+255        return data
+256
+257    time_to_maturity = data.days_to_maturity[i] / data.days_in_year
+258    bs = get_bs_info(
+259        inputs.stock_price,
+260        data.strike[i],
+261        inputs.interest_rate,
+262        inputs.volatility,
+263        time_to_maturity,
+264        inputs.dividend_yield,
+265    )
+266
+267    data.gamma.append(
+268        float(bs.gamma)
+269    )  # TODO: This is required because of mypy. Check later for workarounds, maybe using zero-dimensional numpy arrays
+270    data.vega.append(float(bs.vega))
+271
+272    data.implied_volatility.append(
+273        float(
+274            get_implied_vol(
+275                type,
+276                data.premium[i],
+277                inputs.stock_price,
+278                data.strike[i],
+279                inputs.interest_rate,
+280                time_to_maturity,
+281                inputs.dividend_yield,
+282            )
+283        )
+284    )
+285
+286    negative_multiplier = 1 if data.action[i] == "buy" else -1
+287
+288    if type == "call":
+289        data.itm_probability.append(float(bs.call_itm_prob))
+290        data.delta.append(float(bs.call_delta * negative_multiplier))
+291        data.theta.append(
+292            float(bs.call_theta / data.days_in_year * negative_multiplier)
+293        )
+294        data.rho.append(float(bs.call_rho * negative_multiplier))
+295    else:
+296        data.itm_probability.append(float(bs.put_itm_prob))
+297        data.delta.append(float(bs.put_delta * negative_multiplier))
+298        data.theta.append(float(bs.put_theta / data.days_in_year * negative_multiplier))
+299        data.rho.append(float(bs.put_rho * negative_multiplier))
+300
+301    if data.previous_position[i] > 0.0:  # Premium of the open position
+302        opt_value = data.previous_position[i]
+303    else:  # Current premium
+304        opt_value = data.premium[i]
+305
+306    if data.use_bs[i]:
+307        target_to_maturity = (
+308            data.days_to_maturity[i] - data.days_to_target
+309        ) / data.days_in_year  # To consider the expiration date as a trading day
+310
+311        data.profit[i], data.cost[i] = get_pl_profile_bs(
+312            type,
+313            action,
+314            data.strike[i],
+315            opt_value,
+316            inputs.interest_rate,
+317            target_to_maturity,
+318            inputs.volatility,
+319            data.n[i],
+320            data.stock_price_array,
+321            inputs.dividend_yield,
+322            inputs.opt_commission,
+323        )
+324
+325        if inputs.model == "array":
+326            data.profit_mc[i] = get_pl_profile_bs(
+327                type,
+328                action,
+329                data.strike[i],
+330                opt_value,
+331                inputs.interest_rate,
+332                target_to_maturity,
+333                inputs.interest_rate,
+334                data.n[i],
+335                data.terminal_stock_prices,
+336                inputs.dividend_yield,
+337                inputs.opt_commission,
+338            )[0]
+339    else:
+340        data.profit[i], data.cost[i] = get_pl_profile(
+341            type,
+342            action,
+343            data.strike[i],
+344            opt_value,
+345            data.n[i],
+346            data.stock_price_array,
+347            inputs.opt_commission,
+348        )
+349
+350        if inputs.model == "array":
+351            data.profit_mc[i] = get_pl_profile(
+352                type,
+353                action,
+354                data.strike[i],
+355                opt_value,
+356                data.n[i],
+357                data.terminal_stock_prices,
+358                inputs.opt_commission,
+359            )[0]
+360
+361    return data
+362
+363
+364def _run_stock_calcs(data: EngineData, i: int) -> EngineData:
+365    inputs = data.inputs
+366    action: Action = data.action[i]  # type: ignore
+367
+368    if action == "buy":
+369        data.delta.append(1.0)
+370    else:
+371        data.delta.append(-1.0)
+372
+373    data.itm_probability.append(1.0)
+374    data.implied_volatility.append(0.0)
+375    data.gamma.append(0.0)
+376    data.vega.append(0.0)
+377    data.rho.append(0.0)
+378    data.theta.append(0.0)
+379
+380    if data.previous_position[i] < 0.0:  # Previous position is closed
+381        costtmp = (inputs.stock_price + data.previous_position[i]) * data.n[i]
+382
+383        if data.action[i] == "buy":
+384            costtmp *= -1.0
+385
+386        data.cost[i] = costtmp
+387        data.profit[i] += costtmp
+388
+389        if inputs.model == "array":
+390            data.profit_mc[i] += costtmp
+391
+392        return data
+393
+394    if data.previous_position[i] > 0.0:  # Stock price at previous position
+395        stockpos = data.previous_position[i]
+396    else:  # Spot price of the stock at start date
+397        stockpos = inputs.stock_price
+398
+399    data.profit[i], data.cost[i] = get_pl_profile_stock(
+400        stockpos,
+401        action,
+402        data.n[i],
+403        data.stock_price_array,
+404        inputs.stock_commission,
+405    )
+406
+407    if inputs.model == "array":
+408        data.profit_mc[i] = get_pl_profile_stock(
+409            stockpos,
+410            action,
+411            data.n[i],
+412            data.terminal_stock_prices,
+413            inputs.stock_commission,
+414        )[0]
+415
+416    return data
+417
+418
+419def _run_closed_position_calcs(data: EngineData, i: int) -> EngineData:
+420    inputs = data.inputs
+421
+422    data.implied_volatility.append(0.0)
+423    data.itm_probability.append(0.0)
+424    data.delta.append(0.0)
+425    data.gamma.append(0.0)
+426    data.vega.append(0.0)
+427    data.rho.append(0.0)
+428    data.theta.append(0.0)
+429
+430    data.cost[i] = data.previous_position[i]
+431    data.profit[i] += data.previous_position[i]
+432
+433    if inputs.model == "array":
+434        data.profit_mc[i] += data.previous_position[i]
+435
+436    return data
+437
+438
+439def _generate_outputs(data: EngineData) -> Outputs:
+440    return Outputs(
+441        inputs=data.inputs,
+442        data=data,
+443        probability_of_profit=data.profit_probability,
+444        expected_profit=data.expected_profit,
+445        expected_loss=data.expected_loss,
+446        strategy_cost=sum(data.cost),
+447        per_leg_cost=data.cost,
+448        profit_ranges=data.profit_ranges,
+449        minimum_return_in_the_domain=data.strategy_profit.min(),
+450        maximum_return_in_the_domain=data.strategy_profit.max(),
+451        implied_volatility=data.implied_volatility,
+452        in_the_money_probability=data.itm_probability,
+453        delta=data.delta,
+454        gamma=data.gamma,
+455        theta=data.theta,
+456        vega=data.vega,
+457        rho=data.rho,
+458        probability_of_profit_target=data.profit_target_probability,
+459        probability_of_loss_limit=data.loss_limit_probability,
+460        profit_target_ranges=data.profit_target_ranges,
+461        loss_limit_ranges=data.loss_limit_ranges,
+462    )
+
+ + +
+
+ +
+ + def + run_strategy(inputs_data: optionlab.models.Inputs | dict) -> optionlab.models.Outputs: + + + +
+ +
42def run_strategy(inputs_data: Inputs | dict) -> Outputs:
+43    """
+44    Runs the calculation for a strategy.
+45
+46    Parameters
+47    ----------
+48    `inputs_data`: input data used in the strategy calculation.
+49
+50    Returns
+51    -------
+52    Output data from the strategy calculation.
+53    """
+54
+55    inputs = (
+56        inputs_data
+57        if isinstance(inputs_data, Inputs)
+58        else Inputs.model_validate(inputs_data)
+59    )
+60
+61    data = _init_inputs(inputs)
+62
+63    data = _run(data)
+64
+65    return _generate_outputs(data)
+
+ + +

Runs the calculation for a strategy.

+ +

Parameters

+ +

inputs_data: input data used in the strategy calculation.

+ +

Returns

+ +

Output data from the strategy calculation.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab/models.html b/docs/optionlab/models.html new file mode 100644 index 0000000..772faec --- /dev/null +++ b/docs/optionlab/models.html @@ -0,0 +1,3837 @@ + + + + + + + optionlab.models API documentation + + + + + + + + + +
+
+

+optionlab.models

+ +

This module primarily implements Pydantic models that represent inputs and outputs +of strategy calculations. It also implements constants and custom types.

+ +

From the user's point of view, the two most important classes that they will use +to provide input and subsequently process calculation results are Inputs and +Outputs, respectively.

+
+ + + + + +
  1"""
+  2This module primarily implements Pydantic models that represent inputs and outputs 
+  3of strategy calculations. It also implements constants and custom types.
+  4
+  5From the user's point of view, the two most important classes that they will use 
+  6to provide input and subsequently process calculation results are `Inputs` and 
+  7`Outputs`, respectively.
+  8"""
+  9
+ 10import datetime as dt
+ 11from typing import Literal, Optional
+ 12
+ 13import numpy as np
+ 14from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict
+ 15
+ 16OptionType = Literal["call", "put"]
+ 17"""Option type in a strategy leg."""
+ 18
+ 19Action = Literal["buy", "sell"]
+ 20"""Action taken in in a strategy leg."""
+ 21
+ 22StrategyLegType = Literal["stock"] | OptionType | Literal["closed"]
+ 23"""Type of strategy leg."""
+ 24
+ 25TheoreticalModel = Literal["black-scholes", "array"]
+ 26"""
+ 27Theoretical model used in probability of profit (PoP) calculations.
+ 28"""
+ 29
+ 30Range = tuple[float, float]
+ 31"""Range boundaries."""
+ 32
+ 33FloatOrNdarray = float | np.ndarray
+ 34"""Float or numpy array custom type."""
+ 35
+ 36
+ 37def init_empty_array() -> np.ndarray:
+ 38    """@private"""
+ 39
+ 40    return np.array([])
+ 41
+ 42
+ 43class Stock(BaseModel):
+ 44    """Defines the attributes of a stock leg in a strategy."""
+ 45
+ 46    type: Literal["stock"] = "stock"
+ 47    """It must be *'stock'*."""
+ 48
+ 49    n: int = Field(gt=0)
+ 50    """Number of shares."""
+ 51
+ 52    action: Action
+ 53    """Either *'buy'* or *'sell'*."""
+ 54
+ 55    prev_pos: Optional[float] = None
+ 56    """
+ 57    Stock price effectively paid or received in a previously opened position.
+ 58    
+ 59    - If positive, the position remains open and the payoff calculation considers
+ 60    this price instead of the current stock price. 
+ 61    
+ 62    - If negative, the position is closed and the difference between this price 
+ 63    and the current price is included in the payoff calculation. 
+ 64    
+ 65    The default is `None`, which means this stock position is not a previously 
+ 66    opened position.
+ 67    """
+ 68
+ 69
+ 70class Option(BaseModel):
+ 71    """Defines the attributes of an option leg in a strategy."""
+ 72
+ 73    type: OptionType
+ 74    """Either *'call'* or *'put'*."""
+ 75
+ 76    strike: float = Field(gt=0)
+ 77    """Strike price."""
+ 78
+ 79    premium: float = Field(gt=0)
+ 80    """Option premium."""
+ 81
+ 82    action: Action
+ 83    """Either *'buy'* or *'sell'*."""
+ 84
+ 85    n: int = Field(gt=0)
+ 86    """Number of options."""
+ 87
+ 88    prev_pos: Optional[float] = None
+ 89    """
+ 90    Premium effectively paid or received in a previously opened position. 
+ 91    
+ 92    - If positive, the position remains open and the payoff calculation considers
+ 93    this price instead of the current price of the option. 
+ 94    
+ 95    - If negative, the position is closed and the difference between this price 
+ 96    and the current price is included in the payoff calculation. 
+ 97    
+ 98    The default is `None`, which means this option position is not a previously 
+ 99    opened position.
+100    """
+101
+102    expiration: dt.date | int | None = None
+103    """
+104    Expiration date or number of days remaining to expiration. 
+105    
+106    The default is `None`, which means the expiration is the same as `Inputs.target_date` 
+107    or `Inputs.days_to_target_date`.
+108    """
+109
+110    @field_validator("expiration")
+111    def validate_expiration(cls, v: dt.date | int | None) -> dt.date | int | None:
+112        """@private"""
+113
+114        if isinstance(v, int) and v <= 0:
+115            raise ValueError("If expiration is an integer, it must be greater than 0.")
+116        return v
+117
+118
+119class ClosedPosition(BaseModel):
+120    """Defines the attributes of a previously closed position in a strategy."""
+121
+122    type: Literal["closed"] = "closed"
+123    """It must be *'closed'*."""
+124
+125    prev_pos: float
+126    """
+127    The total amount of the closed position. 
+128    
+129    - If positive, it resulted in a profit.
+130    
+131    - If negative, it incurred a loss.
+132    
+133    This amount will be added to the payoff and taken into account in the strategy 
+134    calculations.
+135    """
+136
+137
+138StrategyLeg = Stock | Option | ClosedPosition
+139"""Leg in a strategy."""
+140
+141
+142class TheoreticalModelInputs(BaseModel):
+143    """Inputs for calculations, such as the probability of profit (PoP)."""
+144
+145    stock_price: float = Field(gt=0.0)
+146    """Stock price."""
+147
+148    volatility: float = Field(gt=0.0)
+149    """Annualized volatility of the underlying asset."""
+150
+151    years_to_target_date: float = Field(ge=0.0)
+152    """Time remaining until target date, in years."""
+153
+154
+155class BlackScholesModelInputs(TheoreticalModelInputs):
+156    """Defines the input data for the calculations using the Black-Scholes model."""
+157
+158    model: Literal["black-scholes"] = "black-scholes"
+159    """It must be *'black-scholes'*."""
+160
+161    interest_rate: float = Field(0.0, ge=0.0)
+162    """
+163    Annualized risk-free interest rate. 
+164    
+165    The default is 0.0.
+166    """
+167
+168    dividend_yield: float = Field(0.0, ge=0.0, le=1.0)
+169    """
+170    Annualized dividend yield. 
+171    
+172    The default is 0.0.
+173    """
+174
+175    __hash__ = object.__hash__
+176
+177
+178class LaplaceInputs(TheoreticalModelInputs):
+179    """
+180    Defines the input data for the calculations using a log-Laplace distribution of
+181    stock prices.
+182    """
+183
+184    model: Literal["laplace"] = "laplace"
+185    """It must be '*laplace*'."""
+186
+187    mu: float
+188    """Annualized return of the underlying asset."""
+189
+190    __hash__ = object.__hash__
+191
+192
+193class ArrayInputs(BaseModel):
+194    """
+195    Defines the input data for the calculations when using an array of strategy
+196    returns.
+197    """
+198
+199    model: Literal["array"] = "array"
+200    """It must be *'array*'."""
+201
+202    array: np.ndarray
+203    """Array of strategy returns."""
+204
+205    model_config = ConfigDict(arbitrary_types_allowed=True)
+206
+207    @field_validator("array", mode="before")
+208    @classmethod
+209    def validate_arrays(cls, v: np.ndarray | list[float]) -> np.ndarray:
+210        """@private"""
+211
+212        arr = np.asarray(v)
+213        if arr.shape[0] == 0:
+214            raise ValueError("The array is empty!")
+215        return arr
+216
+217
+218class Inputs(BaseModel):
+219    """Defines the input data for a strategy calculation."""
+220
+221    stock_price: float = Field(gt=0.0)
+222    """Spot price of the underlying."""
+223
+224    volatility: float = Field(ge=0.0)
+225    """Annualized volatility."""
+226
+227    interest_rate: float = Field(ge=0.0)
+228    """Annualized risk-free interest rate."""
+229
+230    min_stock: float = Field(ge=0.0)
+231    """Minimum value of the stock in the stock price domain."""
+232
+233    max_stock: float = Field(ge=0.0)
+234    """Maximum value of the stock in the stock price domain."""
+235
+236    strategy: list[StrategyLeg] = Field(..., min_length=1)
+237    """A list of strategy legs."""
+238
+239    dividend_yield: float = Field(0.0, ge=0.0)
+240    """
+241    Annualized dividend yield. 
+242    
+243    The default is 0.0.
+244    """
+245
+246    profit_target: Optional[float] = None
+247    """
+248    Target profit level. 
+249    
+250    The default is `None`, which means it is not calculated.
+251    """
+252
+253    loss_limit: Optional[float] = None
+254    """
+255    Limit loss level. 
+256    
+257    The default is `None`, which means it is not calculated.
+258    """
+259
+260    opt_commission: float = 0.0
+261    """
+262    Brokerage commission for options transactions. 
+263    
+264    The default is 0.0.
+265    """
+266
+267    stock_commission: float = 0.0
+268    """
+269    Brokerage commission for stocks transactions. 
+270    
+271    The default is 0.0.
+272    """
+273
+274    discard_nonbusiness_days: bool = True
+275    """
+276    Discards weekends and holidays when counting the number of days between
+277    two dates. 
+278    
+279    The default is `True`.
+280    """
+281
+282    business_days_in_year: int = 252
+283    """
+284    Number of business days in a year. 
+285    
+286    The default is 252.
+287    """
+288
+289    country: str = "US"
+290    """
+291    Country whose holidays will be counted if `discard_nonbusinessdays` is
+292    set to `True`. 
+293    
+294    The default is '*US*'.
+295    """
+296
+297    start_date: dt.date | None = None
+298    """
+299    Start date in the calculations. 
+300    
+301    If not provided, `days_to_target_date` must be provided.
+302    """
+303
+304    target_date: dt.date | None = None
+305    """
+306    Target date in the calculations. 
+307    
+308    If not provided, `days_to_target_date` must be provided.
+309    """
+310
+311    days_to_target_date: int = Field(0, ge=0)
+312    """
+313    Days remaining to the target date. 
+314    
+315    If not provided, `start_date` and `target_date` must be provided.
+316    """
+317
+318    model: TheoreticalModel = "black-scholes"
+319    """
+320    Theoretical model used in the calculations of probability of profit. 
+321    
+322    It can be *'black-scholes'* or *'array*'. 
+323    """
+324
+325    array: np.ndarray = Field(default_factory=init_empty_array)
+326    """
+327    Array of terminal stock prices. 
+328    
+329    The default is an empty array.
+330    """
+331
+332    model_config = ConfigDict(arbitrary_types_allowed=True)
+333
+334    @field_validator("strategy")
+335    @classmethod
+336    def validate_strategy(cls, v: list[StrategyLeg]) -> list[StrategyLeg]:
+337        """@private"""
+338
+339        types = [strategy.type for strategy in v]
+340        if types.count("closed") > 1:
+341            raise ValueError("Only one position of type 'closed' is allowed!")
+342        return v
+343
+344    @model_validator(mode="after")
+345    def validate_dates(self) -> "Inputs":
+346        """@private"""
+347
+348        expiration_dates = [
+349            strategy.expiration
+350            for strategy in self.strategy
+351            if isinstance(strategy, Option) and isinstance(strategy.expiration, dt.date)
+352        ]
+353        if self.start_date and self.target_date:
+354            if any(
+355                expiration_date < self.target_date
+356                for expiration_date in expiration_dates
+357            ):
+358                raise ValueError("Expiration dates must be after or on target date!")
+359            if self.start_date >= self.target_date:
+360                raise ValueError("Start date must be before target date!")
+361            return self
+362        if self.days_to_target_date:
+363            if len(expiration_dates) > 0:
+364                raise ValueError(
+365                    "You can't mix a strategy expiration with a days_to_target_date."
+366                )
+367            return self
+368        raise ValueError(
+369            "Either start_date and target_date or days_to_maturity must be provided"
+370        )
+371
+372    @model_validator(mode="after")
+373    def validate_model_array(self) -> "Inputs":
+374        """@private"""
+375
+376        if self.model != "array":
+377            return self
+378        elif self.array is None:
+379            raise ValueError(
+380                "Array of terminal stock prices must be provided if model is 'array'."
+381            )
+382        elif self.array.shape[0] == 0:
+383            raise ValueError(
+384                "Array of terminal stock prices must be provided if model is 'array'."
+385            )
+386        return self
+387
+388
+389class BlackScholesInfo(BaseModel):
+390    """Defines the data returned by a calculation using the Black-Scholes model."""
+391
+392    call_price: FloatOrNdarray
+393    """Price of a call option."""
+394
+395    put_price: FloatOrNdarray
+396    """Price of a put option."""
+397
+398    call_delta: FloatOrNdarray
+399    """Delta of a call option."""
+400
+401    put_delta: FloatOrNdarray
+402    """Delta of a put option."""
+403
+404    call_theta: FloatOrNdarray
+405    """Theta of a call option."""
+406
+407    put_theta: FloatOrNdarray
+408    """Theta of a put option."""
+409
+410    gamma: FloatOrNdarray
+411    """Gamma of an option."""
+412
+413    vega: FloatOrNdarray
+414    """Vega of an option."""
+415
+416    call_rho: FloatOrNdarray
+417    """Rho of a call option."""
+418
+419    put_rho: FloatOrNdarray
+420    """Rho of a put option."""
+421
+422    call_itm_prob: FloatOrNdarray
+423    """Probability of expiring in-the-money probability of a call option."""
+424
+425    put_itm_prob: FloatOrNdarray
+426    """Probability of expiring in-the-money of a put option."""
+427
+428    model_config = ConfigDict(arbitrary_types_allowed=True)
+429
+430
+431class EngineDataResults(BaseModel):
+432    """@private"""
+433
+434    stock_price_array: np.ndarray
+435    terminal_stock_prices: np.ndarray = Field(default_factory=init_empty_array)
+436    profit: np.ndarray = Field(default_factory=init_empty_array)
+437    profit_mc: np.ndarray = Field(default_factory=init_empty_array)
+438    strategy_profit: np.ndarray = Field(default_factory=init_empty_array)
+439    strategy_profit_mc: np.ndarray = Field(default_factory=init_empty_array)
+440    strike: list[float] = []
+441    premium: list[float] = []
+442    n: list[int] = []
+443    action: list[Action | Literal["n/a"]] = []
+444    type: list[StrategyLegType] = []
+445
+446    model_config = ConfigDict(arbitrary_types_allowed=True)
+447
+448
+449class EngineData(EngineDataResults):
+450    """@private"""
+451
+452    inputs: Inputs
+453    previous_position: list[float] = []
+454    use_bs: list[bool] = []
+455    profit_ranges: list[Range] = []
+456    profit_target_ranges: list[Range] = []
+457    loss_limit_ranges: list[Range] = []
+458    days_to_maturity: list[int] = []
+459    days_in_year: int = 365
+460    days_to_target: int = 30
+461    implied_volatility: list[float] = []
+462    itm_probability: list[float] = []
+463    delta: list[float] = []
+464    gamma: list[float] = []
+465    vega: list[float] = []
+466    rho: list[float] = []
+467    theta: list[float] = []
+468    cost: list[float] = []
+469    profit_probability: float = 0.0
+470    profit_target_probability: float = 0.0
+471    loss_limit_probability: float = 0.0
+472    expected_profit: Optional[float] = None
+473    expected_loss: Optional[float] = None
+474
+475
+476class Outputs(BaseModel):
+477    """
+478    Defines the output data from a strategy calculation.
+479    """
+480
+481    probability_of_profit: float
+482    """
+483    Probability of the strategy yielding at least $0.01.
+484    """
+485
+486    profit_ranges: list[Range]
+487    """
+488    A list of minimum and maximum stock prices defining ranges in which the
+489    strategy makes at least $0.01.
+490    """
+491
+492    expected_profit: Optional[float] = None
+493    """
+494    Expected profit when the strategy is profitable. 
+495    
+496    The default is `None`.
+497    """
+498
+499    expected_loss: Optional[float] = None
+500    """
+501    Expected loss when the strategy is not profitable. 
+502    
+503    The default is `None`.
+504    """
+505
+506    per_leg_cost: list[float]
+507    """
+508    List of leg costs.
+509    """
+510
+511    strategy_cost: float
+512    """
+513    Total strategy cost.
+514    """
+515
+516    minimum_return_in_the_domain: float
+517    """
+518    Minimum return of the strategy within the stock price domain.
+519    """
+520
+521    maximum_return_in_the_domain: float
+522    """
+523    Maximum return of the strategy within the stock price domain.
+524    """
+525
+526    implied_volatility: list[float]
+527    """
+528    List of implied volatilities, one per strategy leg.
+529    """
+530
+531    in_the_money_probability: list[float]
+532    """
+533    List of probabilities of legs expiring in-the-money (ITM).
+534    """
+535
+536    delta: list[float]
+537    """
+538    List of Delta values, one per strategy leg.
+539    """
+540
+541    gamma: list[float]
+542    """
+543    List of Gamma values, one per strategy leg.
+544    """
+545
+546    theta: list[float]
+547    """
+548    List of Theta values, one per strategy leg.
+549    """
+550
+551    vega: list[float]
+552    """
+553    List of Vega values, one per strategy leg.
+554    """
+555
+556    rho: list[float]
+557    """
+558    List of Rho values, one per strategy leg.
+559    """
+560
+561    probability_of_profit_target: float = 0.0
+562    """
+563    Probability of the strategy yielding at least the profit target. 
+564    
+565    The default is 0.0.
+566    """
+567
+568    profit_target_ranges: list[Range] = []
+569    """
+570    List of minimum and maximum stock prices defining ranges in which the
+571    strategy makes at least the profit target. 
+572    
+573    The default is [].
+574    """
+575
+576    probability_of_loss_limit: float = 0.0
+577    """
+578    Probability of the strategy losing at least the loss limit. 
+579    
+580    The default is 0.0.
+581    """
+582
+583    loss_limit_ranges: list[Range] = []
+584    """
+585    List of minimum and maximum stock prices defining ranges where the
+586    strategy loses at least the loss limit. 
+587    
+588    The default is [].
+589    """
+590
+591    inputs: Inputs
+592    """@private"""
+593
+594    data: EngineDataResults
+595    """@private"""
+596
+597    def __str__(self):
+598        s = ""
+599
+600        for key, value in self.model_dump(
+601            exclude={"data", "inputs"},
+602            exclude_none=True,
+603            exclude_defaults=True,
+604        ).items():
+605            s += f"{key.capitalize().replace('_',' ')}: {value}\n"
+606
+607        return s
+608
+609
+610class PoPOutputs(BaseModel):
+611    """
+612    Defines the output data from a probability of profit (PoP) calculation.
+613    """
+614
+615    probability_of_reaching_target: float = 0.0
+616    """
+617    Probability that the strategy return will be equal or greater than the
+618    target. 
+619    
+620    The default is 0.0.
+621    """
+622
+623    probability_of_missing_target: float = 0.0
+624    """
+625    Probability that the strategy return will be less than the target. 
+626    
+627    The default is 0.0.
+628    """
+629
+630    reaching_target_range: list[Range] = []
+631    """
+632    Range of stock prices where the strategy return is equal or greater than
+633    the target. 
+634    
+635    The default is [].
+636    """
+637
+638    missing_target_range: list[Range] = []
+639    """
+640    Range of stock prices where the strategy return is less than the target.
+641    
+642    The default is [].
+643    """
+644
+645    expected_return_above_target: Optional[float] = None
+646    """
+647    Expected value of the strategy return when the return is equal or greater
+648    than the target. 
+649    
+650    The default is `None`.
+651    """
+652
+653    expected_return_below_target: Optional[float] = None
+654    """
+655    Expected value of the strategy return when the return is less than the
+656    target. 
+657    
+658    The default is `None`.
+659    """
+
+ + +
+
+
+ OptionType = +typing.Literal['call', 'put'] + + +
+ + +

Option type in a strategy leg.

+
+ + +
+
+
+ Action = +typing.Literal['buy', 'sell'] + + +
+ + +

Action taken in in a strategy leg.

+
+ + +
+
+
+ StrategyLegType = +typing.Union[typing.Literal['stock'], typing.Literal['call', 'put'], typing.Literal['closed']] + + +
+ + +

Type of strategy leg.

+
+ + +
+
+
+ TheoreticalModel = +typing.Literal['black-scholes', 'array'] + + +
+ + +

Theoretical model used in probability of profit (PoP) calculations.

+
+ + +
+
+
+ Range = +tuple[float, float] + + +
+ + +

Range boundaries.

+
+ + +
+
+
+ FloatOrNdarray = +float | numpy.ndarray + + +
+ + +

Float or numpy array custom type.

+
+ + +
+
+ +
+ + class + Stock(pydantic.main.BaseModel): + + + +
+ +
44class Stock(BaseModel):
+45    """Defines the attributes of a stock leg in a strategy."""
+46
+47    type: Literal["stock"] = "stock"
+48    """It must be *'stock'*."""
+49
+50    n: int = Field(gt=0)
+51    """Number of shares."""
+52
+53    action: Action
+54    """Either *'buy'* or *'sell'*."""
+55
+56    prev_pos: Optional[float] = None
+57    """
+58    Stock price effectively paid or received in a previously opened position.
+59    
+60    - If positive, the position remains open and the payoff calculation considers
+61    this price instead of the current stock price. 
+62    
+63    - If negative, the position is closed and the difference between this price 
+64    and the current price is included in the payoff calculation. 
+65    
+66    The default is `None`, which means this stock position is not a previously 
+67    opened position.
+68    """
+
+ + +

Defines the attributes of a stock leg in a strategy.

+
+ + +
+
+ type: Literal['stock'] + + +
+ + +

It must be 'stock'.

+
+ + +
+
+
+ n: int + + +
+ + +

Number of shares.

+
+ + +
+
+
+ action: Literal['buy', 'sell'] + + +
+ + +

Either 'buy' or 'sell'.

+
+ + +
+
+
+ prev_pos: Optional[float] + + +
+ + +

Stock price effectively paid or received in a previously opened position.

+ +
    +
  • If positive, the position remains open and the payoff calculation considers +this price instead of the current stock price.

  • +
  • If negative, the position is closed and the difference between this price +and the current price is included in the payoff calculation.

  • +
+ +

The default is None, which means this stock position is not a previously +opened position.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'type': FieldInfo(annotation=Literal['stock'], required=False, default='stock'), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + Option(pydantic.main.BaseModel): + + + +
+ +
 71class Option(BaseModel):
+ 72    """Defines the attributes of an option leg in a strategy."""
+ 73
+ 74    type: OptionType
+ 75    """Either *'call'* or *'put'*."""
+ 76
+ 77    strike: float = Field(gt=0)
+ 78    """Strike price."""
+ 79
+ 80    premium: float = Field(gt=0)
+ 81    """Option premium."""
+ 82
+ 83    action: Action
+ 84    """Either *'buy'* or *'sell'*."""
+ 85
+ 86    n: int = Field(gt=0)
+ 87    """Number of options."""
+ 88
+ 89    prev_pos: Optional[float] = None
+ 90    """
+ 91    Premium effectively paid or received in a previously opened position. 
+ 92    
+ 93    - If positive, the position remains open and the payoff calculation considers
+ 94    this price instead of the current price of the option. 
+ 95    
+ 96    - If negative, the position is closed and the difference between this price 
+ 97    and the current price is included in the payoff calculation. 
+ 98    
+ 99    The default is `None`, which means this option position is not a previously 
+100    opened position.
+101    """
+102
+103    expiration: dt.date | int | None = None
+104    """
+105    Expiration date or number of days remaining to expiration. 
+106    
+107    The default is `None`, which means the expiration is the same as `Inputs.target_date` 
+108    or `Inputs.days_to_target_date`.
+109    """
+110
+111    @field_validator("expiration")
+112    def validate_expiration(cls, v: dt.date | int | None) -> dt.date | int | None:
+113        """@private"""
+114
+115        if isinstance(v, int) and v <= 0:
+116            raise ValueError("If expiration is an integer, it must be greater than 0.")
+117        return v
+
+ + +

Defines the attributes of an option leg in a strategy.

+
+ + +
+
+ type: Literal['call', 'put'] + + +
+ + +

Either 'call' or 'put'.

+
+ + +
+
+
+ strike: float + + +
+ + +

Strike price.

+
+ + +
+
+
+ premium: float + + +
+ + +

Option premium.

+
+ + +
+
+
+ action: Literal['buy', 'sell'] + + +
+ + +

Either 'buy' or 'sell'.

+
+ + +
+
+
+ n: int + + +
+ + +

Number of options.

+
+ + +
+
+
+ prev_pos: Optional[float] + + +
+ + +

Premium effectively paid or received in a previously opened position.

+ +
    +
  • If positive, the position remains open and the payoff calculation considers +this price instead of the current price of the option.

  • +
  • If negative, the position is closed and the difference between this price +and the current price is included in the payoff calculation.

  • +
+ +

The default is None, which means this option position is not a previously +opened position.

+
+ + +
+
+
+ expiration: datetime.date | int | None + + +
+ + +

Expiration date or number of days remaining to expiration.

+ +

The default is None, which means the expiration is the same as Inputs.target_date +or Inputs.days_to_target_date.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'type': FieldInfo(annotation=Literal['call', 'put'], required=True), 'strike': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'premium': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expiration': FieldInfo(annotation=Union[date, int, NoneType], required=False, default=None)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + ClosedPosition(pydantic.main.BaseModel): + + + +
+ +
120class ClosedPosition(BaseModel):
+121    """Defines the attributes of a previously closed position in a strategy."""
+122
+123    type: Literal["closed"] = "closed"
+124    """It must be *'closed'*."""
+125
+126    prev_pos: float
+127    """
+128    The total amount of the closed position. 
+129    
+130    - If positive, it resulted in a profit.
+131    
+132    - If negative, it incurred a loss.
+133    
+134    This amount will be added to the payoff and taken into account in the strategy 
+135    calculations.
+136    """
+
+ + +

Defines the attributes of a previously closed position in a strategy.

+
+ + +
+
+ type: Literal['closed'] + + +
+ + +

It must be 'closed'.

+
+ + +
+
+
+ prev_pos: float + + +
+ + +

The total amount of the closed position.

+ +
    +
  • If positive, it resulted in a profit.

  • +
  • If negative, it incurred a loss.

  • +
+ +

This amount will be added to the payoff and taken into account in the strategy +calculations.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'type': FieldInfo(annotation=Literal['closed'], required=False, default='closed'), 'prev_pos': FieldInfo(annotation=float, required=True)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+
+ StrategyLeg = +Stock | Option | ClosedPosition + + +
+ + +

Leg in a strategy.

+
+ + +
+
+ +
+ + class + TheoreticalModelInputs(pydantic.main.BaseModel): + + + +
+ +
143class TheoreticalModelInputs(BaseModel):
+144    """Inputs for calculations, such as the probability of profit (PoP)."""
+145
+146    stock_price: float = Field(gt=0.0)
+147    """Stock price."""
+148
+149    volatility: float = Field(gt=0.0)
+150    """Annualized volatility of the underlying asset."""
+151
+152    years_to_target_date: float = Field(ge=0.0)
+153    """Time remaining until target date, in years."""
+
+ + +

Inputs for calculations, such as the probability of profit (PoP).

+
+ + +
+
+ stock_price: float + + +
+ + +

Stock price.

+
+ + +
+
+
+ volatility: float + + +
+ + +

Annualized volatility of the underlying asset.

+
+ + +
+
+
+ years_to_target_date: float + + +
+ + +

Time remaining until target date, in years.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)])} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + BlackScholesModelInputs(TheoreticalModelInputs): + + + +
+ +
156class BlackScholesModelInputs(TheoreticalModelInputs):
+157    """Defines the input data for the calculations using the Black-Scholes model."""
+158
+159    model: Literal["black-scholes"] = "black-scholes"
+160    """It must be *'black-scholes'*."""
+161
+162    interest_rate: float = Field(0.0, ge=0.0)
+163    """
+164    Annualized risk-free interest rate. 
+165    
+166    The default is 0.0.
+167    """
+168
+169    dividend_yield: float = Field(0.0, ge=0.0, le=1.0)
+170    """
+171    Annualized dividend yield. 
+172    
+173    The default is 0.0.
+174    """
+175
+176    __hash__ = object.__hash__
+
+ + +

Defines the input data for the calculations using the Black-Scholes model.

+
+ + +
+
+ model: Literal['black-scholes'] + + +
+ + +

It must be 'black-scholes'.

+
+ + +
+
+
+ interest_rate: float + + +
+ + +

Annualized risk-free interest rate.

+ +

The default is 0.0.

+
+ + +
+
+
+ dividend_yield: float + + +
+ + +

Annualized dividend yield.

+ +

The default is 0.0.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['black-scholes'], required=False, default='black-scholes'), 'interest_rate': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0), Le(le=1.0)])} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + LaplaceInputs(TheoreticalModelInputs): + + + +
+ +
179class LaplaceInputs(TheoreticalModelInputs):
+180    """
+181    Defines the input data for the calculations using a log-Laplace distribution of
+182    stock prices.
+183    """
+184
+185    model: Literal["laplace"] = "laplace"
+186    """It must be '*laplace*'."""
+187
+188    mu: float
+189    """Annualized return of the underlying asset."""
+190
+191    __hash__ = object.__hash__
+
+ + +

Defines the input data for the calculations using a log-Laplace distribution of +stock prices.

+
+ + +
+
+ model: Literal['laplace'] + + +
+ + +

It must be 'laplace'.

+
+ + +
+
+
+ mu: float + + +
+ + +

Annualized return of the underlying asset.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['laplace'], required=False, default='laplace'), 'mu': FieldInfo(annotation=float, required=True)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + ArrayInputs(pydantic.main.BaseModel): + + + +
+ +
194class ArrayInputs(BaseModel):
+195    """
+196    Defines the input data for the calculations when using an array of strategy
+197    returns.
+198    """
+199
+200    model: Literal["array"] = "array"
+201    """It must be *'array*'."""
+202
+203    array: np.ndarray
+204    """Array of strategy returns."""
+205
+206    model_config = ConfigDict(arbitrary_types_allowed=True)
+207
+208    @field_validator("array", mode="before")
+209    @classmethod
+210    def validate_arrays(cls, v: np.ndarray | list[float]) -> np.ndarray:
+211        """@private"""
+212
+213        arr = np.asarray(v)
+214        if arr.shape[0] == 0:
+215            raise ValueError("The array is empty!")
+216        return arr
+
+ + +

Defines the input data for the calculations when using an array of strategy +returns.

+
+ + +
+
+ model: Literal['array'] + + +
+ + +

It must be 'array'.

+
+ + +
+
+
+ array: numpy.ndarray + + +
+ + +

Array of strategy returns.

+
+ + +
+
+
+ model_config = +{'arbitrary_types_allowed': True} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'model': FieldInfo(annotation=Literal['array'], required=False, default='array'), 'array': FieldInfo(annotation=ndarray, required=True)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + Inputs(pydantic.main.BaseModel): + + + +
+ +
219class Inputs(BaseModel):
+220    """Defines the input data for a strategy calculation."""
+221
+222    stock_price: float = Field(gt=0.0)
+223    """Spot price of the underlying."""
+224
+225    volatility: float = Field(ge=0.0)
+226    """Annualized volatility."""
+227
+228    interest_rate: float = Field(ge=0.0)
+229    """Annualized risk-free interest rate."""
+230
+231    min_stock: float = Field(ge=0.0)
+232    """Minimum value of the stock in the stock price domain."""
+233
+234    max_stock: float = Field(ge=0.0)
+235    """Maximum value of the stock in the stock price domain."""
+236
+237    strategy: list[StrategyLeg] = Field(..., min_length=1)
+238    """A list of strategy legs."""
+239
+240    dividend_yield: float = Field(0.0, ge=0.0)
+241    """
+242    Annualized dividend yield. 
+243    
+244    The default is 0.0.
+245    """
+246
+247    profit_target: Optional[float] = None
+248    """
+249    Target profit level. 
+250    
+251    The default is `None`, which means it is not calculated.
+252    """
+253
+254    loss_limit: Optional[float] = None
+255    """
+256    Limit loss level. 
+257    
+258    The default is `None`, which means it is not calculated.
+259    """
+260
+261    opt_commission: float = 0.0
+262    """
+263    Brokerage commission for options transactions. 
+264    
+265    The default is 0.0.
+266    """
+267
+268    stock_commission: float = 0.0
+269    """
+270    Brokerage commission for stocks transactions. 
+271    
+272    The default is 0.0.
+273    """
+274
+275    discard_nonbusiness_days: bool = True
+276    """
+277    Discards weekends and holidays when counting the number of days between
+278    two dates. 
+279    
+280    The default is `True`.
+281    """
+282
+283    business_days_in_year: int = 252
+284    """
+285    Number of business days in a year. 
+286    
+287    The default is 252.
+288    """
+289
+290    country: str = "US"
+291    """
+292    Country whose holidays will be counted if `discard_nonbusinessdays` is
+293    set to `True`. 
+294    
+295    The default is '*US*'.
+296    """
+297
+298    start_date: dt.date | None = None
+299    """
+300    Start date in the calculations. 
+301    
+302    If not provided, `days_to_target_date` must be provided.
+303    """
+304
+305    target_date: dt.date | None = None
+306    """
+307    Target date in the calculations. 
+308    
+309    If not provided, `days_to_target_date` must be provided.
+310    """
+311
+312    days_to_target_date: int = Field(0, ge=0)
+313    """
+314    Days remaining to the target date. 
+315    
+316    If not provided, `start_date` and `target_date` must be provided.
+317    """
+318
+319    model: TheoreticalModel = "black-scholes"
+320    """
+321    Theoretical model used in the calculations of probability of profit. 
+322    
+323    It can be *'black-scholes'* or *'array*'. 
+324    """
+325
+326    array: np.ndarray = Field(default_factory=init_empty_array)
+327    """
+328    Array of terminal stock prices. 
+329    
+330    The default is an empty array.
+331    """
+332
+333    model_config = ConfigDict(arbitrary_types_allowed=True)
+334
+335    @field_validator("strategy")
+336    @classmethod
+337    def validate_strategy(cls, v: list[StrategyLeg]) -> list[StrategyLeg]:
+338        """@private"""
+339
+340        types = [strategy.type for strategy in v]
+341        if types.count("closed") > 1:
+342            raise ValueError("Only one position of type 'closed' is allowed!")
+343        return v
+344
+345    @model_validator(mode="after")
+346    def validate_dates(self) -> "Inputs":
+347        """@private"""
+348
+349        expiration_dates = [
+350            strategy.expiration
+351            for strategy in self.strategy
+352            if isinstance(strategy, Option) and isinstance(strategy.expiration, dt.date)
+353        ]
+354        if self.start_date and self.target_date:
+355            if any(
+356                expiration_date < self.target_date
+357                for expiration_date in expiration_dates
+358            ):
+359                raise ValueError("Expiration dates must be after or on target date!")
+360            if self.start_date >= self.target_date:
+361                raise ValueError("Start date must be before target date!")
+362            return self
+363        if self.days_to_target_date:
+364            if len(expiration_dates) > 0:
+365                raise ValueError(
+366                    "You can't mix a strategy expiration with a days_to_target_date."
+367                )
+368            return self
+369        raise ValueError(
+370            "Either start_date and target_date or days_to_maturity must be provided"
+371        )
+372
+373    @model_validator(mode="after")
+374    def validate_model_array(self) -> "Inputs":
+375        """@private"""
+376
+377        if self.model != "array":
+378            return self
+379        elif self.array is None:
+380            raise ValueError(
+381                "Array of terminal stock prices must be provided if model is 'array'."
+382            )
+383        elif self.array.shape[0] == 0:
+384            raise ValueError(
+385                "Array of terminal stock prices must be provided if model is 'array'."
+386            )
+387        return self
+
+ + +

Defines the input data for a strategy calculation.

+
+ + +
+
+ stock_price: float + + +
+ + +

Spot price of the underlying.

+
+ + +
+
+
+ volatility: float + + +
+ + +

Annualized volatility.

+
+ + +
+
+
+ interest_rate: float + + +
+ + +

Annualized risk-free interest rate.

+
+ + +
+
+
+ min_stock: float + + +
+ + +

Minimum value of the stock in the stock price domain.

+
+ + +
+
+
+ max_stock: float + + +
+ + +

Maximum value of the stock in the stock price domain.

+
+ + +
+
+
+ strategy: list[Stock | Option | ClosedPosition] + + +
+ + +

A list of strategy legs.

+
+ + +
+
+
+ dividend_yield: float + + +
+ + +

Annualized dividend yield.

+ +

The default is 0.0.

+
+ + +
+
+
+ profit_target: Optional[float] + + +
+ + +

Target profit level.

+ +

The default is None, which means it is not calculated.

+
+ + +
+
+
+ loss_limit: Optional[float] + + +
+ + +

Limit loss level.

+ +

The default is None, which means it is not calculated.

+
+ + +
+
+
+ opt_commission: float + + +
+ + +

Brokerage commission for options transactions.

+ +

The default is 0.0.

+
+ + +
+
+
+ stock_commission: float + + +
+ + +

Brokerage commission for stocks transactions.

+ +

The default is 0.0.

+
+ + +
+
+
+ discard_nonbusiness_days: bool + + +
+ + +

Discards weekends and holidays when counting the number of days between +two dates.

+ +

The default is True.

+
+ + +
+
+
+ business_days_in_year: int + + +
+ + +

Number of business days in a year.

+ +

The default is 252.

+
+ + +
+
+
+ country: str + + +
+ + +

Country whose holidays will be counted if discard_nonbusinessdays is +set to True.

+ +

The default is 'US'.

+
+ + +
+
+
+ start_date: datetime.date | None + + +
+ + +

Start date in the calculations.

+ +

If not provided, days_to_target_date must be provided.

+
+ + +
+
+
+ target_date: datetime.date | None + + +
+ + +

Target date in the calculations.

+ +

If not provided, days_to_target_date must be provided.

+
+ + +
+
+
+ days_to_target_date: int + + +
+ + +

Days remaining to the target date.

+ +

If not provided, start_date and target_date must be provided.

+
+ + +
+
+
+ model: Literal['black-scholes', 'array'] + + +
+ + +

Theoretical model used in the calculations of probability of profit.

+ +

It can be 'black-scholes' or 'array'.

+
+ + +
+
+
+ array: numpy.ndarray + + +
+ + +

Array of terminal stock prices.

+ +

The default is an empty array.

+
+ + +
+
+
+ model_config = +{'arbitrary_types_allowed': True} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'interest_rate': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'min_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'max_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'strategy': FieldInfo(annotation=list[Union[Stock, Option, ClosedPosition]], required=True, metadata=[MinLen(min_length=1)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'profit_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'loss_limit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opt_commission': FieldInfo(annotation=float, required=False, default=0.0), 'stock_commission': FieldInfo(annotation=float, required=False, default=0.0), 'discard_nonbusiness_days': FieldInfo(annotation=bool, required=False, default=True), 'business_days_in_year': FieldInfo(annotation=int, required=False, default=252), 'country': FieldInfo(annotation=str, required=False, default='US'), 'start_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'target_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'days_to_target_date': FieldInfo(annotation=int, required=False, default=0, metadata=[Ge(ge=0)]), 'model': FieldInfo(annotation=Literal['black-scholes', 'array'], required=False, default='black-scholes'), 'array': FieldInfo(annotation=ndarray, required=False, default_factory=init_empty_array)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + BlackScholesInfo(pydantic.main.BaseModel): + + + +
+ +
390class BlackScholesInfo(BaseModel):
+391    """Defines the data returned by a calculation using the Black-Scholes model."""
+392
+393    call_price: FloatOrNdarray
+394    """Price of a call option."""
+395
+396    put_price: FloatOrNdarray
+397    """Price of a put option."""
+398
+399    call_delta: FloatOrNdarray
+400    """Delta of a call option."""
+401
+402    put_delta: FloatOrNdarray
+403    """Delta of a put option."""
+404
+405    call_theta: FloatOrNdarray
+406    """Theta of a call option."""
+407
+408    put_theta: FloatOrNdarray
+409    """Theta of a put option."""
+410
+411    gamma: FloatOrNdarray
+412    """Gamma of an option."""
+413
+414    vega: FloatOrNdarray
+415    """Vega of an option."""
+416
+417    call_rho: FloatOrNdarray
+418    """Rho of a call option."""
+419
+420    put_rho: FloatOrNdarray
+421    """Rho of a put option."""
+422
+423    call_itm_prob: FloatOrNdarray
+424    """Probability of expiring in-the-money probability of a call option."""
+425
+426    put_itm_prob: FloatOrNdarray
+427    """Probability of expiring in-the-money of a put option."""
+428
+429    model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ + +

Defines the data returned by a calculation using the Black-Scholes model.

+
+ + +
+
+ call_price: float | numpy.ndarray + + +
+ + +

Price of a call option.

+
+ + +
+
+
+ put_price: float | numpy.ndarray + + +
+ + +

Price of a put option.

+
+ + +
+
+
+ call_delta: float | numpy.ndarray + + +
+ + +

Delta of a call option.

+
+ + +
+
+
+ put_delta: float | numpy.ndarray + + +
+ + +

Delta of a put option.

+
+ + +
+
+
+ call_theta: float | numpy.ndarray + + +
+ + +

Theta of a call option.

+
+ + +
+
+
+ put_theta: float | numpy.ndarray + + +
+ + +

Theta of a put option.

+
+ + +
+
+
+ gamma: float | numpy.ndarray + + +
+ + +

Gamma of an option.

+
+ + +
+
+
+ vega: float | numpy.ndarray + + +
+ + +

Vega of an option.

+
+ + +
+
+
+ call_rho: float | numpy.ndarray + + +
+ + +

Rho of a call option.

+
+ + +
+
+
+ put_rho: float | numpy.ndarray + + +
+ + +

Rho of a put option.

+
+ + +
+
+
+ call_itm_prob: float | numpy.ndarray + + +
+ + +

Probability of expiring in-the-money probability of a call option.

+
+ + +
+
+
+ put_itm_prob: float | numpy.ndarray + + +
+ + +

Probability of expiring in-the-money of a put option.

+
+ + +
+
+
+ model_config = +{'arbitrary_types_allowed': True} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'call_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'gamma': FieldInfo(annotation=Union[float, ndarray], required=True), 'vega': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + Outputs(pydantic.main.BaseModel): + + + +
+ +
477class Outputs(BaseModel):
+478    """
+479    Defines the output data from a strategy calculation.
+480    """
+481
+482    probability_of_profit: float
+483    """
+484    Probability of the strategy yielding at least $0.01.
+485    """
+486
+487    profit_ranges: list[Range]
+488    """
+489    A list of minimum and maximum stock prices defining ranges in which the
+490    strategy makes at least $0.01.
+491    """
+492
+493    expected_profit: Optional[float] = None
+494    """
+495    Expected profit when the strategy is profitable. 
+496    
+497    The default is `None`.
+498    """
+499
+500    expected_loss: Optional[float] = None
+501    """
+502    Expected loss when the strategy is not profitable. 
+503    
+504    The default is `None`.
+505    """
+506
+507    per_leg_cost: list[float]
+508    """
+509    List of leg costs.
+510    """
+511
+512    strategy_cost: float
+513    """
+514    Total strategy cost.
+515    """
+516
+517    minimum_return_in_the_domain: float
+518    """
+519    Minimum return of the strategy within the stock price domain.
+520    """
+521
+522    maximum_return_in_the_domain: float
+523    """
+524    Maximum return of the strategy within the stock price domain.
+525    """
+526
+527    implied_volatility: list[float]
+528    """
+529    List of implied volatilities, one per strategy leg.
+530    """
+531
+532    in_the_money_probability: list[float]
+533    """
+534    List of probabilities of legs expiring in-the-money (ITM).
+535    """
+536
+537    delta: list[float]
+538    """
+539    List of Delta values, one per strategy leg.
+540    """
+541
+542    gamma: list[float]
+543    """
+544    List of Gamma values, one per strategy leg.
+545    """
+546
+547    theta: list[float]
+548    """
+549    List of Theta values, one per strategy leg.
+550    """
+551
+552    vega: list[float]
+553    """
+554    List of Vega values, one per strategy leg.
+555    """
+556
+557    rho: list[float]
+558    """
+559    List of Rho values, one per strategy leg.
+560    """
+561
+562    probability_of_profit_target: float = 0.0
+563    """
+564    Probability of the strategy yielding at least the profit target. 
+565    
+566    The default is 0.0.
+567    """
+568
+569    profit_target_ranges: list[Range] = []
+570    """
+571    List of minimum and maximum stock prices defining ranges in which the
+572    strategy makes at least the profit target. 
+573    
+574    The default is [].
+575    """
+576
+577    probability_of_loss_limit: float = 0.0
+578    """
+579    Probability of the strategy losing at least the loss limit. 
+580    
+581    The default is 0.0.
+582    """
+583
+584    loss_limit_ranges: list[Range] = []
+585    """
+586    List of minimum and maximum stock prices defining ranges where the
+587    strategy loses at least the loss limit. 
+588    
+589    The default is [].
+590    """
+591
+592    inputs: Inputs
+593    """@private"""
+594
+595    data: EngineDataResults
+596    """@private"""
+597
+598    def __str__(self):
+599        s = ""
+600
+601        for key, value in self.model_dump(
+602            exclude={"data", "inputs"},
+603            exclude_none=True,
+604            exclude_defaults=True,
+605        ).items():
+606            s += f"{key.capitalize().replace('_',' ')}: {value}\n"
+607
+608        return s
+
+ + +

Defines the output data from a strategy calculation.

+
+ + +
+
+ probability_of_profit: float + + +
+ + +

Probability of the strategy yielding at least $0.01.

+
+ + +
+
+
+ profit_ranges: list[tuple[float, float]] + + +
+ + +

A list of minimum and maximum stock prices defining ranges in which the +strategy makes at least $0.01.

+
+ + +
+
+
+ expected_profit: Optional[float] + + +
+ + +

Expected profit when the strategy is profitable.

+ +

The default is None.

+
+ + +
+
+
+ expected_loss: Optional[float] + + +
+ + +

Expected loss when the strategy is not profitable.

+ +

The default is None.

+
+ + +
+
+
+ per_leg_cost: list[float] + + +
+ + +

List of leg costs.

+
+ + +
+
+
+ strategy_cost: float + + +
+ + +

Total strategy cost.

+
+ + +
+
+
+ minimum_return_in_the_domain: float + + +
+ + +

Minimum return of the strategy within the stock price domain.

+
+ + +
+
+
+ maximum_return_in_the_domain: float + + +
+ + +

Maximum return of the strategy within the stock price domain.

+
+ + +
+
+
+ implied_volatility: list[float] + + +
+ + +

List of implied volatilities, one per strategy leg.

+
+ + +
+
+
+ in_the_money_probability: list[float] + + +
+ + +

List of probabilities of legs expiring in-the-money (ITM).

+
+ + +
+
+
+ delta: list[float] + + +
+ + +

List of Delta values, one per strategy leg.

+
+ + +
+
+
+ gamma: list[float] + + +
+ + +

List of Gamma values, one per strategy leg.

+
+ + +
+
+
+ theta: list[float] + + +
+ + +

List of Theta values, one per strategy leg.

+
+ + +
+
+
+ vega: list[float] + + +
+ + +

List of Vega values, one per strategy leg.

+
+ + +
+
+
+ rho: list[float] + + +
+ + +

List of Rho values, one per strategy leg.

+
+ + +
+
+
+ probability_of_profit_target: float + + +
+ + +

Probability of the strategy yielding at least the profit target.

+ +

The default is 0.0.

+
+ + +
+
+
+ profit_target_ranges: list[tuple[float, float]] + + +
+ + +

List of minimum and maximum stock prices defining ranges in which the +strategy makes at least the profit target.

+ +

The default is [].

+
+ + +
+
+
+ probability_of_loss_limit: float + + +
+ + +

Probability of the strategy losing at least the loss limit.

+ +

The default is 0.0.

+
+ + +
+
+
+ loss_limit_ranges: list[tuple[float, float]] + + +
+ + +

List of minimum and maximum stock prices defining ranges where the +strategy loses at least the loss limit.

+ +

The default is [].

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'probability_of_profit': FieldInfo(annotation=float, required=True), 'profit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=True), 'expected_profit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_loss': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'per_leg_cost': FieldInfo(annotation=list[float], required=True), 'strategy_cost': FieldInfo(annotation=float, required=True), 'minimum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'maximum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'implied_volatility': FieldInfo(annotation=list[float], required=True), 'in_the_money_probability': FieldInfo(annotation=list[float], required=True), 'delta': FieldInfo(annotation=list[float], required=True), 'gamma': FieldInfo(annotation=list[float], required=True), 'theta': FieldInfo(annotation=list[float], required=True), 'vega': FieldInfo(annotation=list[float], required=True), 'rho': FieldInfo(annotation=list[float], required=True), 'probability_of_profit_target': FieldInfo(annotation=float, required=False, default=0.0), 'profit_target_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'probability_of_loss_limit': FieldInfo(annotation=float, required=False, default=0.0), 'loss_limit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'inputs': FieldInfo(annotation=Inputs, required=True), 'data': FieldInfo(annotation=EngineDataResults, required=True)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ +
+ + class + PoPOutputs(pydantic.main.BaseModel): + + + +
+ +
611class PoPOutputs(BaseModel):
+612    """
+613    Defines the output data from a probability of profit (PoP) calculation.
+614    """
+615
+616    probability_of_reaching_target: float = 0.0
+617    """
+618    Probability that the strategy return will be equal or greater than the
+619    target. 
+620    
+621    The default is 0.0.
+622    """
+623
+624    probability_of_missing_target: float = 0.0
+625    """
+626    Probability that the strategy return will be less than the target. 
+627    
+628    The default is 0.0.
+629    """
+630
+631    reaching_target_range: list[Range] = []
+632    """
+633    Range of stock prices where the strategy return is equal or greater than
+634    the target. 
+635    
+636    The default is [].
+637    """
+638
+639    missing_target_range: list[Range] = []
+640    """
+641    Range of stock prices where the strategy return is less than the target.
+642    
+643    The default is [].
+644    """
+645
+646    expected_return_above_target: Optional[float] = None
+647    """
+648    Expected value of the strategy return when the return is equal or greater
+649    than the target. 
+650    
+651    The default is `None`.
+652    """
+653
+654    expected_return_below_target: Optional[float] = None
+655    """
+656    Expected value of the strategy return when the return is less than the
+657    target. 
+658    
+659    The default is `None`.
+660    """
+
+ + +

Defines the output data from a probability of profit (PoP) calculation.

+
+ + +
+
+ probability_of_reaching_target: float + + +
+ + +

Probability that the strategy return will be equal or greater than the +target.

+ +

The default is 0.0.

+
+ + +
+
+
+ probability_of_missing_target: float + + +
+ + +

Probability that the strategy return will be less than the target.

+ +

The default is 0.0.

+
+ + +
+
+
+ reaching_target_range: list[tuple[float, float]] + + +
+ + +

Range of stock prices where the strategy return is equal or greater than +the target.

+ +

The default is [].

+
+ + +
+
+
+ missing_target_range: list[tuple[float, float]] + + +
+ + +

Range of stock prices where the strategy return is less than the target.

+ +

The default is [].

+
+ + +
+
+
+ expected_return_above_target: Optional[float] + + +
+ + +

Expected value of the strategy return when the return is equal or greater +than the target.

+ +

The default is None.

+
+ + +
+
+
+ expected_return_below_target: Optional[float] + + +
+ + +

Expected value of the strategy return when the return is less than the +target.

+ +

The default is None.

+
+ + +
+
+
+ model_config: ClassVar[pydantic.config.ConfigDict] = +{} + + +
+ + +

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ + +
+
+
+ model_fields: ClassVar[Dict[str, pydantic.fields.FieldInfo]] = + + {'probability_of_reaching_target': FieldInfo(annotation=float, required=False, default=0.0), 'probability_of_missing_target': FieldInfo(annotation=float, required=False, default=0.0), 'reaching_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'missing_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'expected_return_above_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_return_below_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)} + + +
+ + +

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

+ +

This replaces Model.__fields__ from Pydantic V1.

+
+ + +
+
+
+ model_computed_fields: ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]] = +{} + + +
+ + +

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ + +
+
+
+ + \ No newline at end of file diff --git a/docs/optionlab/optionlab.png b/docs/optionlab/optionlab.png new file mode 100644 index 0000000000000000000000000000000000000000..bea8efff70a53066869aeda1932f4ef469205c2a GIT binary patch literal 29413 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 W+A22a^3jwnoE%*3_m1`X?tcI|d(8y^ literal 0 HcmV?d00001 diff --git a/docs/optionlab/plot.html b/docs/optionlab/plot.html new file mode 100644 index 0000000..0b9e44b --- /dev/null +++ b/docs/optionlab/plot.html @@ -0,0 +1,640 @@ + + + + + + + optionlab.plot API documentation + + + + + + + + + +
+
+

+optionlab.plot

+ +

This module implements the plot_pl function, which displays the profit/loss diagram +of an options trading strategy.

+
+ + + + + +
  1"""
+  2This module implements the `plot_pl` function, which displays the profit/loss diagram 
+  3of an options trading strategy.
+  4"""
+  5
+  6from __future__ import division
+  7from __future__ import print_function
+  8
+  9import matplotlib.pyplot as plt
+ 10from matplotlib import rcParams
+ 11from numpy import zeros, full
+ 12
+ 13from optionlab.models import Outputs
+ 14
+ 15
+ 16def plot_pl(outputs: Outputs) -> None:
+ 17    """
+ 18    Displays the strategy's profit/loss diagram.
+ 19
+ 20    Parameters
+ 21    ----------
+ 22    `outputs`: output data from a strategy calculation with `optionlab.engine.run_strategy`.
+ 23
+ 24    Returns
+ 25    -------
+ 26    `None`.
+ 27    """
+ 28
+ 29    st = outputs.data
+ 30    inputs = outputs.inputs
+ 31
+ 32    if len(st.strategy_profit) == 0:
+ 33        raise RuntimeError(
+ 34            "Before plotting the profit/loss profile diagram, you must run a calculation!"
+ 35        )
+ 36
+ 37    rcParams.update({"figure.autolayout": True})
+ 38
+ 39    zero_line = zeros(st.stock_price_array.shape[0])
+ 40    strike_call_buy = []
+ 41    strike_put_buy = []
+ 42    zero_call_buy = []
+ 43    zero_put_buy = []
+ 44    strike_call_sell = []
+ 45    strike_put_sell = []
+ 46    zero_call_sell = []
+ 47    zero_put_sell = []
+ 48    comment = "Profit/Loss diagram:\n--------------------\n"
+ 49    comment += "The vertical green dashed line corresponds to the position "
+ 50    comment += "of the stock's spot price. The right and left arrow "
+ 51    comment += "markers indicate the strike prices of calls and puts, "
+ 52    comment += "respectively, with blue representing long and red representing "
+ 53    comment += "short positions."
+ 54
+ 55    plt.axvline(inputs.stock_price, ls="--", color="green")
+ 56    plt.xlabel("Stock price")
+ 57    plt.ylabel("Profit/Loss")
+ 58    plt.xlim(st.stock_price_array.min(), st.stock_price_array.max())
+ 59
+ 60    for i, strike in enumerate(st.strike):
+ 61        if strike == 0.0:
+ 62            continue
+ 63
+ 64        if st.type[i] == "call":
+ 65            if st.action[i] == "buy":
+ 66                strike_call_buy.append(strike)
+ 67                zero_call_buy.append(0.0)
+ 68            elif st.action[i] == "sell":
+ 69                strike_call_sell.append(strike)
+ 70                zero_call_sell.append(0.0)
+ 71        elif st.type[i] == "put":
+ 72            if st.action[i] == "buy":
+ 73                strike_put_buy.append(strike)
+ 74                zero_put_buy.append(0.0)
+ 75            elif st.action[i] == "sell":
+ 76                strike_put_sell.append(strike)
+ 77                zero_put_sell.append(0.0)
+ 78
+ 79    target_line = None
+ 80    if inputs.profit_target is not None:
+ 81        comment += " The blue dashed line represents the profit target level."
+ 82        target_line = full(st.stock_price_array.shape[0], inputs.profit_target)
+ 83
+ 84    loss_line = None
+ 85    if inputs.loss_limit is not None:
+ 86        comment += " The red dashed line represents the loss limit level."
+ 87        loss_line = full(st.stock_price_array.shape[0], inputs.loss_limit)
+ 88
+ 89    print(comment)
+ 90
+ 91    if loss_line is not None and target_line is not None:
+ 92        plt.plot(
+ 93            st.stock_price_array,
+ 94            zero_line,
+ 95            "m--",
+ 96            st.stock_price_array,
+ 97            loss_line,
+ 98            "r--",
+ 99            st.stock_price_array,
+100            target_line,
+101            "b--",
+102            st.stock_price_array,
+103            st.strategy_profit,
+104            "k-",
+105            strike_call_buy,
+106            zero_call_buy,
+107            "b>",
+108            strike_put_buy,
+109            zero_put_buy,
+110            "b<",
+111            strike_call_sell,
+112            zero_call_sell,
+113            "r>",
+114            strike_put_sell,
+115            zero_put_sell,
+116            "r<",
+117            markersize=10,
+118        )
+119    elif loss_line is not None:
+120        plt.plot(
+121            st.stock_price_array,
+122            zero_line,
+123            "m--",
+124            st.stock_price_array,
+125            loss_line,
+126            "r--",
+127            st.stock_price_array,
+128            st.strategy_profit,
+129            "k-",
+130            strike_call_buy,
+131            zero_call_buy,
+132            "b>",
+133            strike_put_buy,
+134            zero_put_buy,
+135            "b<",
+136            strike_call_sell,
+137            zero_call_sell,
+138            "r>",
+139            strike_put_sell,
+140            zero_put_sell,
+141            "r<",
+142            markersize=10,
+143        )
+144    elif target_line is not None:
+145        plt.plot(
+146            st.stock_price_array,
+147            zero_line,
+148            "m--",
+149            st.stock_price_array,
+150            target_line,
+151            "b--",
+152            st.stock_price_array,
+153            st.strategy_profit,
+154            "k-",
+155            strike_call_buy,
+156            zero_call_buy,
+157            "b>",
+158            strike_put_buy,
+159            zero_put_buy,
+160            "b<",
+161            strike_call_sell,
+162            zero_call_sell,
+163            "r>",
+164            strike_put_sell,
+165            zero_put_sell,
+166            "r<",
+167            markersize=10,
+168        )
+169    else:
+170        plt.plot(
+171            st.stock_price_array,
+172            zero_line,
+173            "m--",
+174            st.stock_price_array,
+175            st.strategy_profit,
+176            "k-",
+177            strike_call_buy,
+178            zero_call_buy,
+179            "b>",
+180            strike_put_buy,
+181            zero_put_buy,
+182            "b<",
+183            strike_call_sell,
+184            zero_call_sell,
+185            "r>",
+186            strike_put_sell,
+187            zero_put_sell,
+188            "r<",
+189            markersize=10,
+190        )
+
+ + +
+
+ +
+ + def + plot_pl(outputs: optionlab.models.Outputs) -> None: + + + +
+ +
 17def plot_pl(outputs: Outputs) -> None:
+ 18    """
+ 19    Displays the strategy's profit/loss diagram.
+ 20
+ 21    Parameters
+ 22    ----------
+ 23    `outputs`: output data from a strategy calculation with `optionlab.engine.run_strategy`.
+ 24
+ 25    Returns
+ 26    -------
+ 27    `None`.
+ 28    """
+ 29
+ 30    st = outputs.data
+ 31    inputs = outputs.inputs
+ 32
+ 33    if len(st.strategy_profit) == 0:
+ 34        raise RuntimeError(
+ 35            "Before plotting the profit/loss profile diagram, you must run a calculation!"
+ 36        )
+ 37
+ 38    rcParams.update({"figure.autolayout": True})
+ 39
+ 40    zero_line = zeros(st.stock_price_array.shape[0])
+ 41    strike_call_buy = []
+ 42    strike_put_buy = []
+ 43    zero_call_buy = []
+ 44    zero_put_buy = []
+ 45    strike_call_sell = []
+ 46    strike_put_sell = []
+ 47    zero_call_sell = []
+ 48    zero_put_sell = []
+ 49    comment = "Profit/Loss diagram:\n--------------------\n"
+ 50    comment += "The vertical green dashed line corresponds to the position "
+ 51    comment += "of the stock's spot price. The right and left arrow "
+ 52    comment += "markers indicate the strike prices of calls and puts, "
+ 53    comment += "respectively, with blue representing long and red representing "
+ 54    comment += "short positions."
+ 55
+ 56    plt.axvline(inputs.stock_price, ls="--", color="green")
+ 57    plt.xlabel("Stock price")
+ 58    plt.ylabel("Profit/Loss")
+ 59    plt.xlim(st.stock_price_array.min(), st.stock_price_array.max())
+ 60
+ 61    for i, strike in enumerate(st.strike):
+ 62        if strike == 0.0:
+ 63            continue
+ 64
+ 65        if st.type[i] == "call":
+ 66            if st.action[i] == "buy":
+ 67                strike_call_buy.append(strike)
+ 68                zero_call_buy.append(0.0)
+ 69            elif st.action[i] == "sell":
+ 70                strike_call_sell.append(strike)
+ 71                zero_call_sell.append(0.0)
+ 72        elif st.type[i] == "put":
+ 73            if st.action[i] == "buy":
+ 74                strike_put_buy.append(strike)
+ 75                zero_put_buy.append(0.0)
+ 76            elif st.action[i] == "sell":
+ 77                strike_put_sell.append(strike)
+ 78                zero_put_sell.append(0.0)
+ 79
+ 80    target_line = None
+ 81    if inputs.profit_target is not None:
+ 82        comment += " The blue dashed line represents the profit target level."
+ 83        target_line = full(st.stock_price_array.shape[0], inputs.profit_target)
+ 84
+ 85    loss_line = None
+ 86    if inputs.loss_limit is not None:
+ 87        comment += " The red dashed line represents the loss limit level."
+ 88        loss_line = full(st.stock_price_array.shape[0], inputs.loss_limit)
+ 89
+ 90    print(comment)
+ 91
+ 92    if loss_line is not None and target_line is not None:
+ 93        plt.plot(
+ 94            st.stock_price_array,
+ 95            zero_line,
+ 96            "m--",
+ 97            st.stock_price_array,
+ 98            loss_line,
+ 99            "r--",
+100            st.stock_price_array,
+101            target_line,
+102            "b--",
+103            st.stock_price_array,
+104            st.strategy_profit,
+105            "k-",
+106            strike_call_buy,
+107            zero_call_buy,
+108            "b>",
+109            strike_put_buy,
+110            zero_put_buy,
+111            "b<",
+112            strike_call_sell,
+113            zero_call_sell,
+114            "r>",
+115            strike_put_sell,
+116            zero_put_sell,
+117            "r<",
+118            markersize=10,
+119        )
+120    elif loss_line is not None:
+121        plt.plot(
+122            st.stock_price_array,
+123            zero_line,
+124            "m--",
+125            st.stock_price_array,
+126            loss_line,
+127            "r--",
+128            st.stock_price_array,
+129            st.strategy_profit,
+130            "k-",
+131            strike_call_buy,
+132            zero_call_buy,
+133            "b>",
+134            strike_put_buy,
+135            zero_put_buy,
+136            "b<",
+137            strike_call_sell,
+138            zero_call_sell,
+139            "r>",
+140            strike_put_sell,
+141            zero_put_sell,
+142            "r<",
+143            markersize=10,
+144        )
+145    elif target_line is not None:
+146        plt.plot(
+147            st.stock_price_array,
+148            zero_line,
+149            "m--",
+150            st.stock_price_array,
+151            target_line,
+152            "b--",
+153            st.stock_price_array,
+154            st.strategy_profit,
+155            "k-",
+156            strike_call_buy,
+157            zero_call_buy,
+158            "b>",
+159            strike_put_buy,
+160            zero_put_buy,
+161            "b<",
+162            strike_call_sell,
+163            zero_call_sell,
+164            "r>",
+165            strike_put_sell,
+166            zero_put_sell,
+167            "r<",
+168            markersize=10,
+169        )
+170    else:
+171        plt.plot(
+172            st.stock_price_array,
+173            zero_line,
+174            "m--",
+175            st.stock_price_array,
+176            st.strategy_profit,
+177            "k-",
+178            strike_call_buy,
+179            zero_call_buy,
+180            "b>",
+181            strike_put_buy,
+182            zero_put_buy,
+183            "b<",
+184            strike_call_sell,
+185            zero_call_sell,
+186            "r>",
+187            strike_put_sell,
+188            zero_put_sell,
+189            "r<",
+190            markersize=10,
+191        )
+
+ + +

Displays the strategy's profit/loss diagram.

+ +

Parameters

+ +

outputs: output data from a strategy calculation with optionlab.engine.run_strategy.

+ +

Returns

+ +

None.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab/price_array.html b/docs/optionlab/price_array.html new file mode 100644 index 0000000..514cfd8 --- /dev/null +++ b/docs/optionlab/price_array.html @@ -0,0 +1,434 @@ + + + + + + + optionlab.price_array API documentation + + + + + + + + + +
+
+

+optionlab.price_array

+ +

This module defines the create_price_array function, which calculates terminal +prices from numerical simulations of multiple stock paths.

+ +

The terminal price array can later be used to calculate the probability of profit +(PoP) of a strategy using the optionlab.engine.run_strategy function.

+
+ + + + + +
  1"""
+  2This module defines the `create_price_array` function, which calculates terminal 
+  3prices from numerical simulations of multiple stock paths.
+  4
+  5The terminal price array can later be used to calculate the probability of profit 
+  6(PoP) of a strategy using the `optionlab.engine.run_strategy` function.
+  7"""
+  8
+  9from functools import lru_cache
+ 10
+ 11import numpy as np
+ 12from numpy import exp
+ 13from numpy.random import seed as np_seed_number, normal, laplace
+ 14from numpy.lib.scimath import log, sqrt
+ 15
+ 16from optionlab.models import BlackScholesModelInputs, LaplaceInputs
+ 17
+ 18
+ 19def create_price_array(
+ 20    inputs_data: BlackScholesModelInputs | LaplaceInputs | dict,
+ 21    n: int = 100_000,
+ 22    seed: int | None = None,
+ 23) -> np.ndarray:
+ 24    """
+ 25    Generates terminal stock prices.
+ 26
+ 27    Parameters
+ 28    ----------
+ 29    `inputs_data`: input data used to generate the terminal stock prices.
+ 30
+ 31    `n`: number of terminal stock prices.
+ 32
+ 33    `seed`: seed for random number generation.
+ 34
+ 35    Returns
+ 36    -------
+ 37    Array of terminal prices.
+ 38    """
+ 39
+ 40    inputs: BlackScholesModelInputs | LaplaceInputs
+ 41
+ 42    if isinstance(inputs_data, dict):
+ 43        input_type = inputs_data["model"]
+ 44
+ 45        if input_type == "black-scholes":
+ 46            inputs = BlackScholesModelInputs.model_validate(inputs_data)
+ 47        elif input_type == "laplace":
+ 48            inputs = LaplaceInputs.model_validate(inputs_data)
+ 49        else:
+ 50            raise ValueError("Inputs are not valid!")
+ 51    else:
+ 52        inputs = inputs_data
+ 53
+ 54        if isinstance(inputs, BlackScholesModelInputs):
+ 55            input_type = "black-scholes"
+ 56        elif isinstance(inputs, LaplaceInputs):
+ 57            input_type = "laplace"
+ 58        else:
+ 59            raise ValueError("Inputs are not valid!")
+ 60
+ 61    np_seed_number(seed)
+ 62
+ 63    if input_type == "black-scholes":
+ 64        arr = _get_array_price_from_BS(inputs, n)
+ 65    elif input_type == "laplace":
+ 66        arr = _get_array_price_from_laplace(inputs, n)
+ 67
+ 68    np_seed_number(None)
+ 69
+ 70    return arr
+ 71
+ 72
+ 73@lru_cache
+ 74def _get_array_price_from_BS(inputs: BlackScholesModelInputs, n: int) -> np.ndarray:
+ 75    return exp(
+ 76        normal(
+ 77            (
+ 78                log(inputs.stock_price)
+ 79                + (
+ 80                    inputs.interest_rate
+ 81                    - inputs.dividend_yield
+ 82                    - 0.5 * inputs.volatility * inputs.volatility
+ 83                )
+ 84                * inputs.years_to_target_date
+ 85            ),
+ 86            inputs.volatility * sqrt(inputs.years_to_target_date),
+ 87            n,
+ 88        )
+ 89    )
+ 90
+ 91
+ 92@lru_cache
+ 93def _get_array_price_from_laplace(inputs: LaplaceInputs, n: int) -> np.ndarray:
+ 94    return exp(
+ 95        laplace(
+ 96            (log(inputs.stock_price) + inputs.mu * inputs.years_to_target_date),
+ 97            (inputs.volatility * sqrt(inputs.years_to_target_date)) / sqrt(2.0),
+ 98            n,
+ 99        )
+100    )
+
+ + +
+
+ +
+ + def + create_price_array( inputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.LaplaceInputs | dict, n: int = 100000, seed: int | None = None) -> numpy.ndarray: + + + +
+ +
20def create_price_array(
+21    inputs_data: BlackScholesModelInputs | LaplaceInputs | dict,
+22    n: int = 100_000,
+23    seed: int | None = None,
+24) -> np.ndarray:
+25    """
+26    Generates terminal stock prices.
+27
+28    Parameters
+29    ----------
+30    `inputs_data`: input data used to generate the terminal stock prices.
+31
+32    `n`: number of terminal stock prices.
+33
+34    `seed`: seed for random number generation.
+35
+36    Returns
+37    -------
+38    Array of terminal prices.
+39    """
+40
+41    inputs: BlackScholesModelInputs | LaplaceInputs
+42
+43    if isinstance(inputs_data, dict):
+44        input_type = inputs_data["model"]
+45
+46        if input_type == "black-scholes":
+47            inputs = BlackScholesModelInputs.model_validate(inputs_data)
+48        elif input_type == "laplace":
+49            inputs = LaplaceInputs.model_validate(inputs_data)
+50        else:
+51            raise ValueError("Inputs are not valid!")
+52    else:
+53        inputs = inputs_data
+54
+55        if isinstance(inputs, BlackScholesModelInputs):
+56            input_type = "black-scholes"
+57        elif isinstance(inputs, LaplaceInputs):
+58            input_type = "laplace"
+59        else:
+60            raise ValueError("Inputs are not valid!")
+61
+62    np_seed_number(seed)
+63
+64    if input_type == "black-scholes":
+65        arr = _get_array_price_from_BS(inputs, n)
+66    elif input_type == "laplace":
+67        arr = _get_array_price_from_laplace(inputs, n)
+68
+69    np_seed_number(None)
+70
+71    return arr
+
+ + +

Generates terminal stock prices.

+ +

Parameters

+ +

inputs_data: input data used to generate the terminal stock prices.

+ +

n: number of terminal stock prices.

+ +

seed: seed for random number generation.

+ +

Returns

+ +

Array of terminal prices.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab/support.html b/docs/optionlab/support.html new file mode 100644 index 0000000..43b9340 --- /dev/null +++ b/docs/optionlab/support.html @@ -0,0 +1,1219 @@ + + + + + + + optionlab.support API documentation + + + + + + + + + +
+
+

+optionlab.support

+ +

This module implements a number of helper functions that are not intended to be +called directly by users, but rather support functionalities within the +optionlab.engine.run_strategy function.

+
+ + + + + +
  1"""
+  2This module implements a number of helper functions that are not intended to be 
+  3called directly by users, but rather support functionalities within the 
+  4`optionlab.engine.run_strategy` function.
+  5"""
+  6
+  7from __future__ import division
+  8
+  9from functools import lru_cache
+ 10
+ 11from typing import Optional
+ 12
+ 13import numpy as np
+ 14from numpy import abs, round, arange
+ 15from numpy.lib.scimath import log, sqrt
+ 16from scipy import stats
+ 17
+ 18from optionlab.black_scholes import get_d1, get_d2, get_option_price
+ 19from optionlab.models import (
+ 20    OptionType,
+ 21    Action,
+ 22    BlackScholesModelInputs,
+ 23    ArrayInputs,
+ 24    Range,
+ 25    PoPOutputs,
+ 26    FloatOrNdarray,
+ 27)
+ 28
+ 29
+ 30def get_pl_profile(
+ 31    option_type: OptionType,
+ 32    action: Action,
+ 33    x: float,
+ 34    val: float,
+ 35    n: int,
+ 36    s: np.ndarray,
+ 37    commission: float = 0.0,
+ 38) -> tuple[np.ndarray, float]:
+ 39    """
+ 40    Returns the profit/loss profile and cost of an options trade at expiration.
+ 41
+ 42    Parameters
+ 43    ----------
+ 44    `option_type`: either *'call'* or *'put'*.
+ 45
+ 46    `action`: either *'buy'* or *'sell'*.
+ 47
+ 48    `x`: strike price.
+ 49
+ 50    `val`: option price.
+ 51
+ 52    `n`: number of options.
+ 53
+ 54    `s`: array of stock prices.
+ 55
+ 56    `commission`: brokerage commission.
+ 57
+ 58    Returns
+ 59    -------
+ 60    Profit/loss profile and cost of an option trade at expiration.
+ 61    """
+ 62
+ 63    if action == "buy":
+ 64        cost = -val
+ 65    elif action == "sell":
+ 66        cost = val
+ 67    else:
+ 68        raise ValueError("Action must be either 'buy' or 'sell'!")
+ 69
+ 70    if option_type in ("call", "put"):
+ 71        return (
+ 72            n * _get_pl_option(option_type, val, action, s, x) - commission,
+ 73            n * cost - commission,
+ 74        )
+ 75    else:
+ 76        raise ValueError("Option type must be either 'call' or 'put'!")
+ 77
+ 78
+ 79def get_pl_profile_stock(
+ 80    s0: float, action: Action, n: int, s: np.ndarray, commission: float = 0.0
+ 81) -> tuple[np.ndarray, float]:
+ 82    """
+ 83    Returns the profit/loss profile and cost of a stock position.
+ 84
+ 85    Parameters
+ 86    ----------
+ 87    `s0`: initial stock price.
+ 88
+ 89    `action`: either *'buy'* or *'sell'*.
+ 90
+ 91    `n`: number of shares.
+ 92
+ 93    `s`: array of stock prices.
+ 94
+ 95    `commission`: brokerage commission.
+ 96
+ 97    Returns
+ 98    -------
+ 99    Profit/loss profile and cost of a stock position.
+100    """
+101
+102    if action == "buy":
+103        cost = -s0
+104    elif action == "sell":
+105        cost = s0
+106    else:
+107        raise ValueError("Action must be either 'buy' or 'sell'!")
+108
+109    return n * _get_pl_stock(s0, action, s) - commission, n * cost - commission
+110
+111
+112def get_pl_profile_bs(
+113    option_type: OptionType,
+114    action: Action,
+115    x: float,
+116    val: float,
+117    r: float,
+118    target_to_maturity_years: float,
+119    volatility: float,
+120    n: int,
+121    s: np.ndarray,
+122    y: float = 0.0,
+123    commission: float = 0.0,
+124) -> tuple[FloatOrNdarray, float]:
+125    """
+126    Returns the profit/loss profile and cost of an options trade on a target date
+127    before expiration using the Black-Scholes model for option pricing.
+128
+129    Parameters
+130    ----------
+131    `option_type`: either *'call'* or *'put'*.
+132
+133    `action`: either *'buy'* or *'sell'*.
+134
+135    `x`: strike price.
+136
+137    `val`: initial option price.
+138
+139    `r`: annualized risk-free interest rate.
+140
+141    `target_to_maturity_years`: time remaining to maturity from the target date,
+142    in years.
+143
+144    `volatility`: annualized volatility of the underlying asset.
+145
+146    `n`: number of options.
+147
+148    `s`: array of stock prices.
+149
+150    `y`: annualized dividend yield.
+151
+152    `commission`: brokerage commission.
+153
+154    Returns
+155    -------
+156    Profit/loss profile and cost of an option trade before expiration.
+157    """
+158
+159    if action == "buy":
+160        cost = -val
+161        fac = 1
+162    elif action == "sell":
+163        cost = val
+164        fac = -1
+165    else:
+166        raise ValueError("Action must be either 'buy' or 'sell'!")
+167
+168    d1: FloatOrNdarray = get_d1(s, x, r, volatility, target_to_maturity_years, y)
+169    d2: FloatOrNdarray = get_d2(s, x, r, volatility, target_to_maturity_years, y)
+170    calcprice: FloatOrNdarray = get_option_price(
+171        option_type, s, x, r, target_to_maturity_years, d1, d2, y
+172    )
+173    profile: FloatOrNdarray = fac * n * (calcprice - val) - commission
+174
+175    return profile, n * cost - commission
+176
+177
+178@lru_cache
+179def create_price_seq(min_price: float, max_price: float) -> np.ndarray:
+180    """
+181    Generates a sequence of stock prices from a minimum to a maximum price with
+182    increment $0.01.
+183
+184    Parameters
+185    ----------
+186    `min_price`: minimum stock price in the range.
+187
+188    `max_price`: maximum stock price in the range.
+189
+190    Returns
+191    -------
+192    Array of sequential stock prices.
+193    """
+194
+195    if max_price > min_price:
+196        return round((arange((max_price - min_price) * 100 + 1) * 0.01 + min_price), 2)
+197    else:
+198        raise ValueError("Maximum price cannot be less than minimum price!")
+199
+200
+201def get_pop(
+202    s: np.ndarray,
+203    profit: np.ndarray,
+204    inputs_data: BlackScholesModelInputs | ArrayInputs,
+205    target: float = 0.01,
+206) -> PoPOutputs:
+207    """
+208    Estimates the probability of profit (PoP) of an options trading strategy.
+209
+210    Parameters
+211    ----------
+212    `s`: array of stock prices.
+213
+214    `profit`: array of profits and losses.
+215
+216    `inputs_data`: input data used to estimate the probability of profit.
+217
+218    `target`: target return.
+219
+220    Returns
+221    -------
+222    Outputs of a probability of profit (PoP) calculation.
+223    """
+224
+225    probability_of_reaching_target: float
+226    probability_of_missing_target: float
+227
+228    expected_return_above_target: Optional[float] = None
+229    expected_return_below_target: Optional[float] = None
+230
+231    t_ranges = _get_profit_range(s, profit, target)
+232
+233    reaching_target_range = t_ranges[0] if t_ranges[0] != [(0.0, 0.0)] else []
+234    missing_target_range = t_ranges[1] if t_ranges[1] != [(0.0, 0.0)] else []
+235
+236    if isinstance(inputs_data, BlackScholesModelInputs):
+237        (
+238            probability_of_reaching_target,
+239            expected_return_above_target,
+240            probability_of_missing_target,
+241            expected_return_below_target,
+242        ) = _get_pop_bs(s, profit, inputs_data, t_ranges)
+243    elif isinstance(inputs_data, ArrayInputs):
+244        (
+245            probability_of_reaching_target,
+246            expected_return_above_target,
+247            probability_of_missing_target,
+248            expected_return_below_target,
+249        ) = _get_pop_array(inputs_data, target)
+250
+251    return PoPOutputs(
+252        probability_of_reaching_target=probability_of_reaching_target,
+253        probability_of_missing_target=probability_of_missing_target,
+254        reaching_target_range=reaching_target_range,
+255        missing_target_range=missing_target_range,
+256        expected_return_above_target=expected_return_above_target,
+257        expected_return_below_target=expected_return_below_target,
+258    )
+259
+260
+261def _get_pl_option(
+262    option_type: OptionType, opvalue: float, action: Action, s: np.ndarray, x: float
+263) -> np.ndarray:
+264    """
+265    Returns the profit or loss profile of an option leg at expiration.
+266
+267    Parameters
+268    ----------
+269    `option_type`: either *'call'* or *'put'*.
+270
+271    `opvalue`: option price.
+272
+273    `action`: either *'buy'* or *'sell'*.
+274
+275    `s`: array of stock prices.
+276
+277    `x`: strike price.
+278
+279    Returns
+280    -------
+281    Profit or loss profile of an option leg at expiration.
+282    """
+283
+284    if action == "sell":
+285        return opvalue - _get_payoff(option_type, s, x)
+286    elif action == "buy":
+287        return _get_payoff(option_type, s, x) - opvalue
+288    else:
+289        raise ValueError("Action must be either 'sell' or 'buy'!")
+290
+291
+292def _get_payoff(option_type: OptionType, s: np.ndarray, x: float) -> np.ndarray:
+293    """
+294    Returns the payoff of an option leg at expiration.
+295
+296    Parameters
+297    ----------
+298    `option_type`: either *'call'* or *'put'*.
+299
+300    `s`: array of stock prices.
+301
+302    `x`: strike price.
+303
+304    Returns
+305    -------
+306    Payoff of an option leg at expiration.
+307    """
+308
+309    if option_type == "call":
+310        return (s - x + abs(s - x)) / 2.0
+311    elif option_type == "put":
+312        return (x - s + abs(x - s)) / 2.0
+313    else:
+314        raise ValueError("Option type must be either 'call' or 'put'!")
+315
+316
+317def _get_pl_stock(s0: float, action: Action, s: np.ndarray) -> np.ndarray:
+318    """
+319    Returns the profit or loss profile of a stock position.
+320
+321    Parameters
+322    ----------
+323    `s0`: spot price of the underlying asset.
+324
+325    `action`: either *'buy'* or *'sell'*.
+326
+327    `s`: array of stock prices.
+328
+329    Returns
+330    -------
+331    Profit or loss profile of a stock position.
+332    """
+333
+334    if action == "sell":
+335        return s0 - s
+336    elif action == "buy":
+337        return s - s0
+338    else:
+339        raise ValueError("Action must be either 'sell' or 'buy'!")
+340
+341
+342def _get_pop_bs(
+343    s: np.ndarray,
+344    profit: np.ndarray,
+345    inputs: BlackScholesModelInputs,
+346    profit_range: tuple[list[Range], list[Range]],
+347) -> tuple[float, Optional[float], float, Optional[float]]:
+348    """
+349    Estimates the probability of profit (PoP) of an options trading strategy using
+350    the Black-Scholes model.
+351
+352    Parameters
+353    ----------
+354    `s`: array of stock prices.
+355
+356    `profit`: array of profits and losses.
+357
+358    `inputs`: input data used to estimate the probability of profit.
+359
+360    `profit_range`: lists of stock price pairs defining the profit and loss
+361    ranges.
+362
+363    Returns
+364    -------
+365    Probability of reaching the return target, expected value above the target,
+366    probability of missing the return target, and expected value below the
+367    target.
+368    """
+369
+370    expected_return_above_target = None
+371    expected_return_below_target = None
+372
+373    sigma = (
+374        inputs.volatility * sqrt(inputs.years_to_target_date)
+375        if inputs.volatility > 0.0
+376        else 1e-10
+377    )
+378
+379    for i, t in enumerate(profit_range):
+380        prob = 0.0
+381
+382        if t != [(0.0, 0.0)]:
+383            for p_range in t:
+384                lval = log(p_range[0]) if p_range[0] > 0.0 else -float("inf")
+385                hval = log(p_range[1])
+386                drift = (
+387                    inputs.interest_rate
+388                    - inputs.dividend_yield
+389                    - 0.5 * inputs.volatility * inputs.volatility
+390                ) * inputs.years_to_target_date
+391                m = log(inputs.stock_price) + drift
+392                prob += stats.norm.cdf((hval - m) / sigma) - stats.norm.cdf(
+393                    (lval - m) / sigma
+394                )
+395
+396        if i == 0:
+397            probability_of_reaching_target = prob
+398        else:
+399            probability_of_missing_target = prob
+400
+401    return (
+402        probability_of_reaching_target,
+403        expected_return_above_target,
+404        probability_of_missing_target,
+405        expected_return_below_target,
+406    )
+407
+408
+409def _get_pop_array(
+410    inputs: ArrayInputs, target: float
+411) -> tuple[float, Optional[float], float, Optional[float]]:
+412    """
+413    Estimates the probability of profit (PoP) of an options trading strategy using
+414    an array of terminal stock prices.
+415
+416    Parameters
+417    ----------
+418    `inputs`: input data used to estimate the probability of profit.
+419
+420    `target`: target return.
+421
+422    Returns
+423    -------
+424    Probability of reaching the target return, expected value above the target,
+425    probability of missing the target return, and expected value below the
+426    target.
+427    """
+428
+429    if inputs.array.shape[0] == 0:
+430        raise ValueError("The array is empty!")
+431
+432    tmp1 = inputs.array[inputs.array >= target]
+433    tmp2 = inputs.array[inputs.array < target]
+434
+435    probability_of_reaching_target = tmp1.shape[0] / inputs.array.shape[0]
+436    probability_of_missing_target = 1.0 - probability_of_reaching_target
+437
+438    expected_return_above_target = round(tmp1.mean(), 2) if tmp1.shape[0] > 0 else None
+439    expected_return_below_target = round(tmp2.mean(), 2) if tmp2.shape[0] > 0 else None
+440
+441    return (
+442        probability_of_reaching_target,
+443        expected_return_above_target,
+444        probability_of_missing_target,
+445        expected_return_below_target,
+446    )
+447
+448
+449def _get_profit_range(
+450    s: np.ndarray, profit: np.ndarray, target: float = 0.01
+451) -> tuple[list[Range], list[Range]]:
+452    """
+453    Returns lists of stock price ranges: one representing the ranges where the
+454    options trade returns are equal to or greater than the target, and the other
+455    representing the ranges where they fall short.
+456
+457    Parameters
+458    ----------
+459    `s`: array of stock prices.
+460
+461    `profit`: array of profits and losses.
+462
+463    `target`: target profit.
+464
+465    Returns
+466    -------
+467    Lists of stock price pairs.
+468    """
+469
+470    profit_range = []
+471    loss_range = []
+472
+473    crossings = _get_sign_changes(profit, target)
+474    n_crossings = len(crossings)
+475
+476    if n_crossings == 0:
+477        if profit[0] >= target:
+478            return [(0.0, float("inf"))], [(0.0, 0.0)]
+479        else:
+480            return [(0.0, 0.0)], [(0.0, float("inf"))]
+481
+482    lb_profit = hb_profit = None
+483    lb_loss = hb_loss = None
+484
+485    for i, index in enumerate(crossings):
+486        if i == 0:
+487            if profit[index] < profit[index - 1]:
+488                lb_profit = 0.0
+489                hb_profit = s[index - 1]
+490                lb_loss = s[index]
+491
+492                if n_crossings == 1:
+493                    hb_loss = float("inf")
+494            else:
+495                lb_profit = s[index]
+496                lb_loss = 0.0
+497                hb_loss = s[index - 1]
+498
+499                if n_crossings == 1:
+500                    hb_profit = float("inf")
+501        elif i == n_crossings - 1:
+502            if profit[index] > profit[index - 1]:
+503                lb_profit = s[index]
+504                hb_profit = float("inf")
+505                hb_loss = s[index - 1]
+506            else:
+507                hb_profit = s[index - 1]
+508                lb_loss = s[index]
+509                hb_loss = float("inf")
+510        else:
+511            if profit[index] > profit[index - 1]:
+512                lb_profit = s[index]
+513                hb_loss = s[index - 1]
+514            else:
+515                hb_profit = s[index - 1]
+516                lb_loss = s[index]
+517
+518        if lb_profit is not None and hb_profit is not None:
+519            profit_range.append((lb_profit, hb_profit))
+520
+521            lb_profit = hb_profit = None
+522
+523        if lb_loss is not None and hb_loss is not None:
+524            loss_range.append((lb_loss, hb_loss))
+525
+526            lb_loss = hb_loss = None
+527
+528    return profit_range, loss_range
+529
+530
+531def _get_sign_changes(profit: np.ndarray, target: float) -> list[int]:
+532    """
+533    Returns a list of the indices in the array of profits where the sign changes.
+534
+535    Parameters
+536    ----------
+537    `profit`: array of profits and losses.
+538
+539    `target`: target profit.
+540
+541    Returns
+542    -------
+543    List of indices.
+544    """
+545
+546    p_temp = profit - target + 1e-10
+547
+548    sign_changes = (np.sign(p_temp[:-1]) * np.sign(p_temp[1:])) < 0
+549
+550    return list(np.where(sign_changes)[0] + 1)
+
+ + +
+
+ +
+ + def + get_pl_profile( option_type: Literal['call', 'put'], action: Literal['buy', 'sell'], x: float, val: float, n: int, s: numpy.ndarray, commission: float = 0.0) -> tuple[numpy.ndarray, float]: + + + +
+ +
31def get_pl_profile(
+32    option_type: OptionType,
+33    action: Action,
+34    x: float,
+35    val: float,
+36    n: int,
+37    s: np.ndarray,
+38    commission: float = 0.0,
+39) -> tuple[np.ndarray, float]:
+40    """
+41    Returns the profit/loss profile and cost of an options trade at expiration.
+42
+43    Parameters
+44    ----------
+45    `option_type`: either *'call'* or *'put'*.
+46
+47    `action`: either *'buy'* or *'sell'*.
+48
+49    `x`: strike price.
+50
+51    `val`: option price.
+52
+53    `n`: number of options.
+54
+55    `s`: array of stock prices.
+56
+57    `commission`: brokerage commission.
+58
+59    Returns
+60    -------
+61    Profit/loss profile and cost of an option trade at expiration.
+62    """
+63
+64    if action == "buy":
+65        cost = -val
+66    elif action == "sell":
+67        cost = val
+68    else:
+69        raise ValueError("Action must be either 'buy' or 'sell'!")
+70
+71    if option_type in ("call", "put"):
+72        return (
+73            n * _get_pl_option(option_type, val, action, s, x) - commission,
+74            n * cost - commission,
+75        )
+76    else:
+77        raise ValueError("Option type must be either 'call' or 'put'!")
+
+ + +

Returns the profit/loss profile and cost of an options trade at expiration.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

action: either 'buy' or 'sell'.

+ +

x: strike price.

+ +

val: option price.

+ +

n: number of options.

+ +

s: array of stock prices.

+ +

commission: brokerage commission.

+ +

Returns

+ +

Profit/loss profile and cost of an option trade at expiration.

+
+ + +
+
+ +
+ + def + get_pl_profile_stock( s0: float, action: Literal['buy', 'sell'], n: int, s: numpy.ndarray, commission: float = 0.0) -> tuple[numpy.ndarray, float]: + + + +
+ +
 80def get_pl_profile_stock(
+ 81    s0: float, action: Action, n: int, s: np.ndarray, commission: float = 0.0
+ 82) -> tuple[np.ndarray, float]:
+ 83    """
+ 84    Returns the profit/loss profile and cost of a stock position.
+ 85
+ 86    Parameters
+ 87    ----------
+ 88    `s0`: initial stock price.
+ 89
+ 90    `action`: either *'buy'* or *'sell'*.
+ 91
+ 92    `n`: number of shares.
+ 93
+ 94    `s`: array of stock prices.
+ 95
+ 96    `commission`: brokerage commission.
+ 97
+ 98    Returns
+ 99    -------
+100    Profit/loss profile and cost of a stock position.
+101    """
+102
+103    if action == "buy":
+104        cost = -s0
+105    elif action == "sell":
+106        cost = s0
+107    else:
+108        raise ValueError("Action must be either 'buy' or 'sell'!")
+109
+110    return n * _get_pl_stock(s0, action, s) - commission, n * cost - commission
+
+ + +

Returns the profit/loss profile and cost of a stock position.

+ +

Parameters

+ +

s0: initial stock price.

+ +

action: either 'buy' or 'sell'.

+ +

n: number of shares.

+ +

s: array of stock prices.

+ +

commission: brokerage commission.

+ +

Returns

+ +

Profit/loss profile and cost of a stock position.

+
+ + +
+
+ +
+ + def + get_pl_profile_bs( option_type: Literal['call', 'put'], action: Literal['buy', 'sell'], x: float, val: float, r: float, target_to_maturity_years: float, volatility: float, n: int, s: numpy.ndarray, y: float = 0.0, commission: float = 0.0) -> tuple[float | numpy.ndarray, float]: + + + +
+ +
113def get_pl_profile_bs(
+114    option_type: OptionType,
+115    action: Action,
+116    x: float,
+117    val: float,
+118    r: float,
+119    target_to_maturity_years: float,
+120    volatility: float,
+121    n: int,
+122    s: np.ndarray,
+123    y: float = 0.0,
+124    commission: float = 0.0,
+125) -> tuple[FloatOrNdarray, float]:
+126    """
+127    Returns the profit/loss profile and cost of an options trade on a target date
+128    before expiration using the Black-Scholes model for option pricing.
+129
+130    Parameters
+131    ----------
+132    `option_type`: either *'call'* or *'put'*.
+133
+134    `action`: either *'buy'* or *'sell'*.
+135
+136    `x`: strike price.
+137
+138    `val`: initial option price.
+139
+140    `r`: annualized risk-free interest rate.
+141
+142    `target_to_maturity_years`: time remaining to maturity from the target date,
+143    in years.
+144
+145    `volatility`: annualized volatility of the underlying asset.
+146
+147    `n`: number of options.
+148
+149    `s`: array of stock prices.
+150
+151    `y`: annualized dividend yield.
+152
+153    `commission`: brokerage commission.
+154
+155    Returns
+156    -------
+157    Profit/loss profile and cost of an option trade before expiration.
+158    """
+159
+160    if action == "buy":
+161        cost = -val
+162        fac = 1
+163    elif action == "sell":
+164        cost = val
+165        fac = -1
+166    else:
+167        raise ValueError("Action must be either 'buy' or 'sell'!")
+168
+169    d1: FloatOrNdarray = get_d1(s, x, r, volatility, target_to_maturity_years, y)
+170    d2: FloatOrNdarray = get_d2(s, x, r, volatility, target_to_maturity_years, y)
+171    calcprice: FloatOrNdarray = get_option_price(
+172        option_type, s, x, r, target_to_maturity_years, d1, d2, y
+173    )
+174    profile: FloatOrNdarray = fac * n * (calcprice - val) - commission
+175
+176    return profile, n * cost - commission
+
+ + +

Returns the profit/loss profile and cost of an options trade on a target date +before expiration using the Black-Scholes model for option pricing.

+ +

Parameters

+ +

option_type: either 'call' or 'put'.

+ +

action: either 'buy' or 'sell'.

+ +

x: strike price.

+ +

val: initial option price.

+ +

r: annualized risk-free interest rate.

+ +

target_to_maturity_years: time remaining to maturity from the target date, +in years.

+ +

volatility: annualized volatility of the underlying asset.

+ +

n: number of options.

+ +

s: array of stock prices.

+ +

y: annualized dividend yield.

+ +

commission: brokerage commission.

+ +

Returns

+ +

Profit/loss profile and cost of an option trade before expiration.

+
+ + +
+
+ +
+
@lru_cache
+ + def + create_price_seq(min_price: float, max_price: float) -> numpy.ndarray: + + + +
+ +
179@lru_cache
+180def create_price_seq(min_price: float, max_price: float) -> np.ndarray:
+181    """
+182    Generates a sequence of stock prices from a minimum to a maximum price with
+183    increment $0.01.
+184
+185    Parameters
+186    ----------
+187    `min_price`: minimum stock price in the range.
+188
+189    `max_price`: maximum stock price in the range.
+190
+191    Returns
+192    -------
+193    Array of sequential stock prices.
+194    """
+195
+196    if max_price > min_price:
+197        return round((arange((max_price - min_price) * 100 + 1) * 0.01 + min_price), 2)
+198    else:
+199        raise ValueError("Maximum price cannot be less than minimum price!")
+
+ + +

Generates a sequence of stock prices from a minimum to a maximum price with +increment $0.01.

+ +

Parameters

+ +

min_price: minimum stock price in the range.

+ +

max_price: maximum stock price in the range.

+ +

Returns

+ +

Array of sequential stock prices.

+
+ + +
+
+ +
+ + def + get_pop( s: numpy.ndarray, profit: numpy.ndarray, inputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.ArrayInputs, target: float = 0.01) -> optionlab.models.PoPOutputs: + + + +
+ +
202def get_pop(
+203    s: np.ndarray,
+204    profit: np.ndarray,
+205    inputs_data: BlackScholesModelInputs | ArrayInputs,
+206    target: float = 0.01,
+207) -> PoPOutputs:
+208    """
+209    Estimates the probability of profit (PoP) of an options trading strategy.
+210
+211    Parameters
+212    ----------
+213    `s`: array of stock prices.
+214
+215    `profit`: array of profits and losses.
+216
+217    `inputs_data`: input data used to estimate the probability of profit.
+218
+219    `target`: target return.
+220
+221    Returns
+222    -------
+223    Outputs of a probability of profit (PoP) calculation.
+224    """
+225
+226    probability_of_reaching_target: float
+227    probability_of_missing_target: float
+228
+229    expected_return_above_target: Optional[float] = None
+230    expected_return_below_target: Optional[float] = None
+231
+232    t_ranges = _get_profit_range(s, profit, target)
+233
+234    reaching_target_range = t_ranges[0] if t_ranges[0] != [(0.0, 0.0)] else []
+235    missing_target_range = t_ranges[1] if t_ranges[1] != [(0.0, 0.0)] else []
+236
+237    if isinstance(inputs_data, BlackScholesModelInputs):
+238        (
+239            probability_of_reaching_target,
+240            expected_return_above_target,
+241            probability_of_missing_target,
+242            expected_return_below_target,
+243        ) = _get_pop_bs(s, profit, inputs_data, t_ranges)
+244    elif isinstance(inputs_data, ArrayInputs):
+245        (
+246            probability_of_reaching_target,
+247            expected_return_above_target,
+248            probability_of_missing_target,
+249            expected_return_below_target,
+250        ) = _get_pop_array(inputs_data, target)
+251
+252    return PoPOutputs(
+253        probability_of_reaching_target=probability_of_reaching_target,
+254        probability_of_missing_target=probability_of_missing_target,
+255        reaching_target_range=reaching_target_range,
+256        missing_target_range=missing_target_range,
+257        expected_return_above_target=expected_return_above_target,
+258        expected_return_below_target=expected_return_below_target,
+259    )
+
+ + +

Estimates the probability of profit (PoP) of an options trading strategy.

+ +

Parameters

+ +

s: array of stock prices.

+ +

profit: array of profits and losses.

+ +

inputs_data: input data used to estimate the probability of profit.

+ +

target: target return.

+ +

Returns

+ +

Outputs of a probability of profit (PoP) calculation.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/optionlab/utils.html b/docs/optionlab/utils.html new file mode 100644 index 0000000..c400405 --- /dev/null +++ b/docs/optionlab/utils.html @@ -0,0 +1,542 @@ + + + + + + + optionlab.utils API documentation + + + + + + + + + +
+
+

+optionlab.utils

+ +

This module defines utility functions.

+
+ + + + + +
  1"""
+  2This module defines utility functions.
+  3"""
+  4
+  5from __future__ import division
+  6
+  7import datetime as dt
+  8from datetime import timedelta
+  9from functools import lru_cache
+ 10
+ 11import numpy as np
+ 12from holidays import country_holidays
+ 13
+ 14from optionlab.models import Outputs
+ 15
+ 16
+ 17@lru_cache
+ 18def get_nonbusiness_days(
+ 19    start_date: dt.date, end_date: dt.date, country: str = "US"
+ 20) -> int:
+ 21    """
+ 22    Returns the number of non-business days (i.e., weekends and holidays) between
+ 23    the start and end date.
+ 24
+ 25    Parameters
+ 26    ----------
+ 27    `start_date`: start date.
+ 28
+ 29    `end_date`: end date.
+ 30
+ 31    `country`: country of the stock exchange.
+ 32
+ 33    Returns
+ 34    -------
+ 35    Number of weekends and holidays between the start and end date.
+ 36    """
+ 37
+ 38    if end_date > start_date:
+ 39        n_days = (end_date - start_date).days
+ 40    else:
+ 41        raise ValueError("End date must be after start date!")
+ 42
+ 43    nonbusiness_days: int = 0
+ 44    holidays = country_holidays(country)
+ 45
+ 46    for i in range(n_days):
+ 47        current_date = start_date + timedelta(days=i)
+ 48
+ 49        if current_date.weekday() >= 5 or current_date.strftime("%Y-%m-%d") in holidays:
+ 50            nonbusiness_days += 1
+ 51
+ 52    return nonbusiness_days
+ 53
+ 54
+ 55def get_pl(outputs: Outputs, leg: int | None = None) -> tuple[np.ndarray, np.ndarray]:
+ 56    """
+ 57    Returns the stock prices and the corresponding profit/loss profile of either
+ 58    a leg or the whole strategy.
+ 59
+ 60    Parameters
+ 61    ----------
+ 62    `outputs`: output data from a strategy calculation.
+ 63
+ 64    `leg`: index of a strategy leg. The default is `None`, which means the whole
+ 65    strategy.
+ 66
+ 67    Returns
+ 68    -------
+ 69    Array of stock prices and array or profits/losses.
+ 70    """
+ 71
+ 72    if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]:
+ 73        return outputs.data.stock_price_array, outputs.data.profit[leg]
+ 74
+ 75    return outputs.data.stock_price_array, outputs.data.strategy_profit
+ 76
+ 77
+ 78def pl_to_csv(
+ 79    outputs: Outputs, filename: str = "pl.csv", leg: int | None = None
+ 80) -> None:
+ 81    """
+ 82    Saves the stock prices and corresponding profit/loss profile of either a leg
+ 83    or the whole strategy to a CSV file.
+ 84
+ 85    Parameters
+ 86    ----------
+ 87    `outputs`: output data from a strategy calculation.
+ 88
+ 89    `filename`: name of the CSV file.
+ 90
+ 91    `leg`: index of a strategy leg. The default is `None`, which means the whole
+ 92    strategy.
+ 93
+ 94    Returns
+ 95    -------
+ 96    `None`.
+ 97    """
+ 98
+ 99    if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]:
+100        arr = np.stack((outputs.data.stock_price_array, outputs.data.profit[leg]))
+101    else:
+102        arr = np.stack((outputs.data.stock_price_array, outputs.data.strategy_profit))
+103
+104    np.savetxt(
+105        filename, arr.transpose(), delimiter=",", header="StockPrice,Profit/Loss"
+106    )
+
+ + +
+
+ +
+
@lru_cache
+ + def + get_nonbusiness_days( start_date: datetime.date, end_date: datetime.date, country: str = 'US') -> int: + + + +
+ +
18@lru_cache
+19def get_nonbusiness_days(
+20    start_date: dt.date, end_date: dt.date, country: str = "US"
+21) -> int:
+22    """
+23    Returns the number of non-business days (i.e., weekends and holidays) between
+24    the start and end date.
+25
+26    Parameters
+27    ----------
+28    `start_date`: start date.
+29
+30    `end_date`: end date.
+31
+32    `country`: country of the stock exchange.
+33
+34    Returns
+35    -------
+36    Number of weekends and holidays between the start and end date.
+37    """
+38
+39    if end_date > start_date:
+40        n_days = (end_date - start_date).days
+41    else:
+42        raise ValueError("End date must be after start date!")
+43
+44    nonbusiness_days: int = 0
+45    holidays = country_holidays(country)
+46
+47    for i in range(n_days):
+48        current_date = start_date + timedelta(days=i)
+49
+50        if current_date.weekday() >= 5 or current_date.strftime("%Y-%m-%d") in holidays:
+51            nonbusiness_days += 1
+52
+53    return nonbusiness_days
+
+ + +

Returns the number of non-business days (i.e., weekends and holidays) between +the start and end date.

+ +

Parameters

+ +

start_date: start date.

+ +

end_date: end date.

+ +

country: country of the stock exchange.

+ +

Returns

+ +

Number of weekends and holidays between the start and end date.

+
+ + +
+
+ +
+ + def + get_pl( outputs: optionlab.models.Outputs, leg: int | None = None) -> tuple[numpy.ndarray, numpy.ndarray]: + + + +
+ +
56def get_pl(outputs: Outputs, leg: int | None = None) -> tuple[np.ndarray, np.ndarray]:
+57    """
+58    Returns the stock prices and the corresponding profit/loss profile of either
+59    a leg or the whole strategy.
+60
+61    Parameters
+62    ----------
+63    `outputs`: output data from a strategy calculation.
+64
+65    `leg`: index of a strategy leg. The default is `None`, which means the whole
+66    strategy.
+67
+68    Returns
+69    -------
+70    Array of stock prices and array or profits/losses.
+71    """
+72
+73    if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]:
+74        return outputs.data.stock_price_array, outputs.data.profit[leg]
+75
+76    return outputs.data.stock_price_array, outputs.data.strategy_profit
+
+ + +

Returns the stock prices and the corresponding profit/loss profile of either +a leg or the whole strategy.

+ +

Parameters

+ +

outputs: output data from a strategy calculation.

+ +

leg: index of a strategy leg. The default is None, which means the whole +strategy.

+ +

Returns

+ +

Array of stock prices and array or profits/losses.

+
+ + +
+
+ +
+ + def + pl_to_csv( outputs: optionlab.models.Outputs, filename: str = 'pl.csv', leg: int | None = None) -> None: + + + +
+ +
 79def pl_to_csv(
+ 80    outputs: Outputs, filename: str = "pl.csv", leg: int | None = None
+ 81) -> None:
+ 82    """
+ 83    Saves the stock prices and corresponding profit/loss profile of either a leg
+ 84    or the whole strategy to a CSV file.
+ 85
+ 86    Parameters
+ 87    ----------
+ 88    `outputs`: output data from a strategy calculation.
+ 89
+ 90    `filename`: name of the CSV file.
+ 91
+ 92    `leg`: index of a strategy leg. The default is `None`, which means the whole
+ 93    strategy.
+ 94
+ 95    Returns
+ 96    -------
+ 97    `None`.
+ 98    """
+ 99
+100    if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]:
+101        arr = np.stack((outputs.data.stock_price_array, outputs.data.profit[leg]))
+102    else:
+103        arr = np.stack((outputs.data.stock_price_array, outputs.data.strategy_profit))
+104
+105    np.savetxt(
+106        filename, arr.transpose(), delimiter=",", header="StockPrice,Profit/Loss"
+107    )
+
+ + +

Saves the stock prices and corresponding profit/loss profile of either a leg +or the whole strategy to a CSV file.

+ +

Parameters

+ +

outputs: output data from a strategy calculation.

+ +

filename: name of the CSV file.

+ +

leg: index of a strategy leg. The default is None, which means the whole +strategy.

+ +

Returns

+ +

None.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/search.js b/docs/search.js new file mode 100644 index 0000000..e3ff565 --- /dev/null +++ b/docs/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oOptionLab is...\n\n

... a Python library designed as a research tool for quickly evaluating options \nstrategy ideas. It is intended for a wide range of users, from individuals learning \nabout options trading to developers of quantitative strategies.

\n\n

OptionLab calculations can produce a number of useful outputs:

\n\n
    \n
  • the profit/loss profile of the strategy on a user-defined target date,

  • \n
  • the range of stock prices for which the strategy is profitable,

  • \n
  • the Greeks associated with each leg of the strategy,

  • \n
  • the resulting debit or credit on the trading account,

  • \n
  • the maximum and minimum returns within a specified lower and higher price range \nof the underlying asset,

  • \n
  • the expected profit and expected loss, and

  • \n
  • an estimate of the strategy's probability of profit.

  • \n
\n\n

The probability of profit (PoP) of the strategy on the user-defined target date
\nis calculated by default using the Black-Scholes model. The user can alternatively \nprovide an array of underlying asset prices obtained elsewhere (e.g. from the \nHeston model, a Laplace distribution or a Machine Learning/Deep Learning model) \nto be used in the calculations instead of the Black-Scholes model. This allows \nOptionLab to function as a calculator using custom models.

\n\n

An advanced feature of OptionLab that provides great flexibility in building \ncomplex dynamic strategies is the ability to include previously created positions \nas legs in a new strategy. Popular strategies that can benefit from this feature \ninclude the Wheel and Covered Call strategies.

\n\n

OptionLab is not...

\n\n

... a platform for direct order execution. This capability has not been and \nprobably will not be implemented.

\n\n

Backtesting and trade simulation using Monte Carlo have also not (yet) been \nimplemented in the API.

\n\n

That being said, nothing prevents OptionLab from being integrated into an \noptions quant trader's workflow alongside other tools.

\n\n

Installation

\n\n

The easiest way to install OptionLab is using pip:

\n\n
pip install optionlab\n
\n\n

Quickstart

\n\n

OptionLab is designed with ease of use in mind. An options strategy can be \ndefined and evaluated with just a few lines of Python code. The API is streamlined, \nand the learning curve is minimal.

\n\n

The evaluation of a strategy is done by calling the optionlab.engine.run_strategy \nfunction provided by the library. This function receives the input data either \nas a dictionary or an optionlab.models.Inputs object.

\n\n

For example, let's say we wanted to calculate the probability of profit for naked \ncalls on Apple stocks expiring on December 17, 2021. The strategy setup consisted \nof selling 100 175.00 strike calls for 1.15 each on November 22, 2021.

\n\n

The input data for this strategy can be provided in a dictionary as follows:

\n\n
\n
input_data = {\n    "stock_price": 164.04,\n    "start_date": "2021-11-22",\n    "target_date": "2021-12-17",\n    "volatility": 0.272,\n    "interest_rate": 0.0002,\n    "min_stock": 120,\n    "max_stock": 200,\n    "strategy": [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n}\n
\n
\n\n

Alternatively, the input data could be defined as the optionlab.models.Inputs \nobject below:

\n\n
\n
from optionlab import Inputs\n\ninput_data = Inputs(\n    stock_price = 164.04,\n    start_date = "2021-11-22",\n    target_date = "2021-12-17",\n    volatility = 0.272,\n    interest_rate = 0.0002,\n    min_stock = 120,\n    max_stock = 200,\n    strategy = [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n)\n
\n
\n\n

In both cases, the strategy itself is a list of dictionaries, where each dictionary\ndefines a leg in the strategy. The fields in a leg, depending on the type of the\nleg, are described in optionlab.models.Stock, optionlab.models.Option, and\noptionlab.models.ClosedPosition.

\n\n

After defining the input data, we pass it to the run_strategy function as shown \nbelow:

\n\n
\n
from optionlab import run_strategy, plot_pl\n\nout = run_strategy(input_data)\n\nprint(out)\n\nplot_pl(out)\n
\n
\n\n

The variable out is an optionlab.models.Outputs object that contains the \nresults from the calculations. By calling print with out as an argument, \nthese results are displayed on screen.

\n\n

The optionlab.plot.plot_pl function, in turn, takes an optionlab.models.Outputs \nobject as its argument and plots the profit/loss diagram for the strategy.

\n\n

Examples

\n\n

Examples for a number of popular options trading strategies can be found as \nJupyter notebooks in the examples \ndirectory.

\n"}, {"fullname": "optionlab.VERSION", "modulename": "optionlab", "qualname": "VERSION", "kind": "variable", "doc": "

\n", "default_value": "'1.4.3'"}, {"fullname": "optionlab.black_scholes", "modulename": "optionlab.black_scholes", "kind": "module", "doc": "

This module defines functions that calculate quantities, such as option prices\nand the Greeks, related to the Black-Scholes model.

\n"}, {"fullname": "optionlab.black_scholes.get_bs_info", "modulename": "optionlab.black_scholes", "qualname": "get_bs_info", "kind": "function", "doc": "

Provides information about call and put options calculated using the Black-Scholes\nformula.

\n\n

Parameters

\n\n

s: stock price.

\n\n

x: strike price(s).

\n\n

r: annualized risk-free interest rate.

\n\n

vol: annualized volatility.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Information calculated using the Black-Scholes formula.

\n", "signature": "(\ts: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\ty: float = 0.0) -> optionlab.models.BlackScholesInfo:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_option_price", "modulename": "optionlab.black_scholes", "qualname": "get_option_price", "kind": "function", "doc": "

Returns the price of an option.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

s0: spot price(s) of the underlying asset.

\n\n

x: strike price(s).

\n\n

r: annualize risk-free interest rate.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

d1: d1 in Black-Scholes formula.

\n\n

d2: d2 in Black-Scholes formula.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Option price(s).

\n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_delta", "modulename": "optionlab.black_scholes", "qualname": "get_delta", "kind": "function", "doc": "

Returns the option's Greek Delta.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

d1: d1 in Black-Scholes formula.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Option's Greek Delta.

\n", "signature": "(\toption_type: Literal['call', 'put'],\td1: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_gamma", "modulename": "optionlab.black_scholes", "qualname": "get_gamma", "kind": "function", "doc": "

Returns the option's Greek Gamma.

\n\n

Parameters

\n\n

s0: spot price of the underlying asset.

\n\n

vol: annualized volatitily.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

d1: d1 in Black-Scholes formula.

\n\n

y: annualized divident yield.

\n\n

Returns

\n\n

Option's Greek Gamma.

\n", "signature": "(\ts0: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_theta", "modulename": "optionlab.black_scholes", "qualname": "get_theta", "kind": "function", "doc": "

Returns the option's Greek Theta.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

s0: spot price of the underlying asset.

\n\n

x: strike price(s).

\n\n

r: annualized risk-free interest rate.

\n\n

vol: annualized volatility.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

d1: d1 in Black-Scholes formula.

\n\n

d2: d2 in Black-Scholes formula.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Option's Greek Theta.

\n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_vega", "modulename": "optionlab.black_scholes", "qualname": "get_vega", "kind": "function", "doc": "

Returns the option's Greek Vega.

\n\n

Parameters

\n\n

s0: spot price of the underlying asset.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

d1: d1 in Black-Scholes formula.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Option's Greek Vega.

\n", "signature": "(\ts0: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_rho", "modulename": "optionlab.black_scholes", "qualname": "get_rho", "kind": "function", "doc": "

Returns the option's Greek Rho.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

x: strike price(s).

\n\n

r: annualized risk-free interest rate.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

d2: d2 in Black-Scholes formula.

\n\n

Returns

\n\n

Option's Greek Rho.

\n", "signature": "(\toption_type: Literal['call', 'put'],\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td2: float | numpy.ndarray) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d1", "modulename": "optionlab.black_scholes", "qualname": "get_d1", "kind": "function", "doc": "

Returns d1 used in Black-Scholes formula.

\n\n

Parameters

\n\n

s0: spot price(s) of the underlying asset.

\n\n

x: strike price(s).

\n\n

r: annualized risk-free interest rate.

\n\n

vol: annualized volatility(ies).

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized divident yield.

\n\n

Returns

\n\n

d1 in Black-Scholes formula.

\n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d2", "modulename": "optionlab.black_scholes", "qualname": "get_d2", "kind": "function", "doc": "

Returns d2 used in Black-Scholes formula.

\n\n

Parameters

\n\n

s0: spot price(s) of the underlying asset.

\n\n

x: strike price(s).

\n\n

r: annualized risk-free interest rate.

\n\n

vol: annualized volatility(ies).

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized divident yield.

\n\n

Returns

\n\n

d2 in Black-Scholes formula.

\n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_implied_vol", "modulename": "optionlab.black_scholes", "qualname": "get_implied_vol", "kind": "function", "doc": "

Returns the implied volatility of an option.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

oprice: market price of an option.

\n\n

s0: spot price of the underlying asset.

\n\n

x: strike price.

\n\n

r: annualized risk-free interest rate.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Option's implied volatility.

\n", "signature": "(\toption_type: Literal['call', 'put'],\toprice: float,\ts0: float,\tx: float,\tr: float,\tyears_to_maturity: float,\ty: float = 0.0) -> float:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_itm_probability", "modulename": "optionlab.black_scholes", "qualname": "get_itm_probability", "kind": "function", "doc": "

Returns the probability(ies) that the option(s) will expire in-the-money (ITM).

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

d2: d2 in Black-Scholes formula.

\n\n

years_to_maturity: time remaining to maturity, in years.

\n\n

y: annualized dividend yield.

\n\n

Returns

\n\n

Probability(ies) that the option(s) will expire in-the-money (ITM).

\n", "signature": "(\toption_type: Literal['call', 'put'],\td2: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.engine", "modulename": "optionlab.engine", "kind": "module", "doc": "

This module defines the run_strategy function.

\n\n

Given input data provided as either an optionlab.models.Inputs object or a dictionary,\nrun_strategy returns the results of an options strategy calculation (e.g., the\nprobability of profit on the target date) as an optionlab.models.Outputs object.

\n"}, {"fullname": "optionlab.engine.run_strategy", "modulename": "optionlab.engine", "qualname": "run_strategy", "kind": "function", "doc": "

Runs the calculation for a strategy.

\n\n

Parameters

\n\n

inputs_data: input data used in the strategy calculation.

\n\n

Returns

\n\n

Output data from the strategy calculation.

\n", "signature": "(inputs_data: optionlab.models.Inputs | dict) -> optionlab.models.Outputs:", "funcdef": "def"}, {"fullname": "optionlab.models", "modulename": "optionlab.models", "kind": "module", "doc": "

This module primarily implements Pydantic models that represent inputs and outputs \nof strategy calculations. It also implements constants and custom types.

\n\n

From the user's point of view, the two most important classes that they will use \nto provide input and subsequently process calculation results are Inputs and \nOutputs, respectively.

\n"}, {"fullname": "optionlab.models.OptionType", "modulename": "optionlab.models", "qualname": "OptionType", "kind": "variable", "doc": "

Option type in a strategy leg.

\n", "default_value": "typing.Literal['call', 'put']"}, {"fullname": "optionlab.models.Action", "modulename": "optionlab.models", "qualname": "Action", "kind": "variable", "doc": "

Action taken in in a strategy leg.

\n", "default_value": "typing.Literal['buy', 'sell']"}, {"fullname": "optionlab.models.StrategyLegType", "modulename": "optionlab.models", "qualname": "StrategyLegType", "kind": "variable", "doc": "

Type of strategy leg.

\n", "default_value": "typing.Union[typing.Literal['stock'], typing.Literal['call', 'put'], typing.Literal['closed']]"}, {"fullname": "optionlab.models.TheoreticalModel", "modulename": "optionlab.models", "qualname": "TheoreticalModel", "kind": "variable", "doc": "

Theoretical model used in probability of profit (PoP) calculations.

\n", "default_value": "typing.Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Range", "modulename": "optionlab.models", "qualname": "Range", "kind": "variable", "doc": "

Range boundaries.

\n", "default_value": "tuple[float, float]"}, {"fullname": "optionlab.models.FloatOrNdarray", "modulename": "optionlab.models", "qualname": "FloatOrNdarray", "kind": "variable", "doc": "

Float or numpy array custom type.

\n", "default_value": "float | numpy.ndarray"}, {"fullname": "optionlab.models.Stock", "modulename": "optionlab.models", "qualname": "Stock", "kind": "class", "doc": "

Defines the attributes of a stock leg in a strategy.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Stock.type", "modulename": "optionlab.models", "qualname": "Stock.type", "kind": "variable", "doc": "

It must be 'stock'.

\n", "annotation": ": Literal['stock']"}, {"fullname": "optionlab.models.Stock.n", "modulename": "optionlab.models", "qualname": "Stock.n", "kind": "variable", "doc": "

Number of shares.

\n", "annotation": ": int"}, {"fullname": "optionlab.models.Stock.action", "modulename": "optionlab.models", "qualname": "Stock.action", "kind": "variable", "doc": "

Either 'buy' or 'sell'.

\n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Stock.prev_pos", "modulename": "optionlab.models", "qualname": "Stock.prev_pos", "kind": "variable", "doc": "

Stock price effectively paid or received in a previously opened position.

\n\n
    \n
  • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current stock price.

  • \n
  • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

  • \n
\n\n

The default is None, which means this stock position is not a previously \nopened position.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Stock.model_config", "modulename": "optionlab.models", "qualname": "Stock.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Stock.model_fields", "modulename": "optionlab.models", "qualname": "Stock.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['stock'], required=False, default='stock'), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Stock.model_computed_fields", "modulename": "optionlab.models", "qualname": "Stock.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Option", "modulename": "optionlab.models", "qualname": "Option", "kind": "class", "doc": "

Defines the attributes of an option leg in a strategy.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Option.type", "modulename": "optionlab.models", "qualname": "Option.type", "kind": "variable", "doc": "

Either 'call' or 'put'.

\n", "annotation": ": Literal['call', 'put']"}, {"fullname": "optionlab.models.Option.strike", "modulename": "optionlab.models", "qualname": "Option.strike", "kind": "variable", "doc": "

Strike price.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.premium", "modulename": "optionlab.models", "qualname": "Option.premium", "kind": "variable", "doc": "

Option premium.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.action", "modulename": "optionlab.models", "qualname": "Option.action", "kind": "variable", "doc": "

Either 'buy' or 'sell'.

\n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Option.n", "modulename": "optionlab.models", "qualname": "Option.n", "kind": "variable", "doc": "

Number of options.

\n", "annotation": ": int"}, {"fullname": "optionlab.models.Option.prev_pos", "modulename": "optionlab.models", "qualname": "Option.prev_pos", "kind": "variable", "doc": "

Premium effectively paid or received in a previously opened position.

\n\n
    \n
  • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current price of the option.

  • \n
  • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

  • \n
\n\n

The default is None, which means this option position is not a previously \nopened position.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Option.expiration", "modulename": "optionlab.models", "qualname": "Option.expiration", "kind": "variable", "doc": "

Expiration date or number of days remaining to expiration.

\n\n

The default is None, which means the expiration is the same as Inputs.target_date \nor Inputs.days_to_target_date.

\n", "annotation": ": datetime.date | int | None"}, {"fullname": "optionlab.models.Option.model_config", "modulename": "optionlab.models", "qualname": "Option.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Option.model_fields", "modulename": "optionlab.models", "qualname": "Option.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['call', 'put'], required=True), 'strike': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'premium': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expiration': FieldInfo(annotation=Union[date, int, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Option.model_computed_fields", "modulename": "optionlab.models", "qualname": "Option.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition", "modulename": "optionlab.models", "qualname": "ClosedPosition", "kind": "class", "doc": "

Defines the attributes of a previously closed position in a strategy.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ClosedPosition.type", "modulename": "optionlab.models", "qualname": "ClosedPosition.type", "kind": "variable", "doc": "

It must be 'closed'.

\n", "annotation": ": Literal['closed']"}, {"fullname": "optionlab.models.ClosedPosition.prev_pos", "modulename": "optionlab.models", "qualname": "ClosedPosition.prev_pos", "kind": "variable", "doc": "

The total amount of the closed position.

\n\n
    \n
  • If positive, it resulted in a profit.

  • \n
  • If negative, it incurred a loss.

  • \n
\n\n

This amount will be added to the payoff and taken into account in the strategy \ncalculations.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.ClosedPosition.model_config", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition.model_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['closed'], required=False, default='closed'), 'prev_pos': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.ClosedPosition.model_computed_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.StrategyLeg", "modulename": "optionlab.models", "qualname": "StrategyLeg", "kind": "variable", "doc": "

Leg in a strategy.

\n", "default_value": "optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition"}, {"fullname": "optionlab.models.TheoreticalModelInputs", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs", "kind": "class", "doc": "

Inputs for calculations, such as the probability of profit (PoP).

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.TheoreticalModelInputs.stock_price", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.stock_price", "kind": "variable", "doc": "

Stock price.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.volatility", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.volatility", "kind": "variable", "doc": "

Annualized volatility of the underlying asset.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.years_to_target_date", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.years_to_target_date", "kind": "variable", "doc": "

Time remaining until target date, in years.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_config", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)])}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs", "kind": "class", "doc": "

Defines the input data for the calculations using the Black-Scholes model.

\n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model", "kind": "variable", "doc": "

It must be 'black-scholes'.

\n", "annotation": ": Literal['black-scholes']"}, {"fullname": "optionlab.models.BlackScholesModelInputs.interest_rate", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.interest_rate", "kind": "variable", "doc": "

Annualized risk-free interest rate.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.dividend_yield", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.dividend_yield", "kind": "variable", "doc": "

Annualized dividend yield.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['black-scholes'], required=False, default='black-scholes'), 'interest_rate': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0), Le(le=1.0)])}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs", "modulename": "optionlab.models", "qualname": "LaplaceInputs", "kind": "class", "doc": "

Defines the input data for the calculations using a log-Laplace distribution of\nstock prices.

\n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.LaplaceInputs.model", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model", "kind": "variable", "doc": "

It must be 'laplace'.

\n", "annotation": ": Literal['laplace']"}, {"fullname": "optionlab.models.LaplaceInputs.mu", "modulename": "optionlab.models", "qualname": "LaplaceInputs.mu", "kind": "variable", "doc": "

Annualized return of the underlying asset.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.LaplaceInputs.model_config", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs.model_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['laplace'], required=False, default='laplace'), 'mu': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.LaplaceInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ArrayInputs", "modulename": "optionlab.models", "qualname": "ArrayInputs", "kind": "class", "doc": "

Defines the input data for the calculations when using an array of strategy\nreturns.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ArrayInputs.model", "modulename": "optionlab.models", "qualname": "ArrayInputs.model", "kind": "variable", "doc": "

It must be 'array'.

\n", "annotation": ": Literal['array']"}, {"fullname": "optionlab.models.ArrayInputs.array", "modulename": "optionlab.models", "qualname": "ArrayInputs.array", "kind": "variable", "doc": "

Array of strategy returns.

\n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.ArrayInputs.model_config", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.ArrayInputs.model_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'model': FieldInfo(annotation=Literal['array'], required=False, default='array'), 'array': FieldInfo(annotation=ndarray, required=True)}"}, {"fullname": "optionlab.models.ArrayInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Inputs", "modulename": "optionlab.models", "qualname": "Inputs", "kind": "class", "doc": "

Defines the input data for a strategy calculation.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Inputs.stock_price", "modulename": "optionlab.models", "qualname": "Inputs.stock_price", "kind": "variable", "doc": "

Spot price of the underlying.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.volatility", "modulename": "optionlab.models", "qualname": "Inputs.volatility", "kind": "variable", "doc": "

Annualized volatility.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.interest_rate", "modulename": "optionlab.models", "qualname": "Inputs.interest_rate", "kind": "variable", "doc": "

Annualized risk-free interest rate.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.min_stock", "modulename": "optionlab.models", "qualname": "Inputs.min_stock", "kind": "variable", "doc": "

Minimum value of the stock in the stock price domain.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.max_stock", "modulename": "optionlab.models", "qualname": "Inputs.max_stock", "kind": "variable", "doc": "

Maximum value of the stock in the stock price domain.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.strategy", "modulename": "optionlab.models", "qualname": "Inputs.strategy", "kind": "variable", "doc": "

A list of strategy legs.

\n", "annotation": ": list[optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition]"}, {"fullname": "optionlab.models.Inputs.dividend_yield", "modulename": "optionlab.models", "qualname": "Inputs.dividend_yield", "kind": "variable", "doc": "

Annualized dividend yield.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.profit_target", "modulename": "optionlab.models", "qualname": "Inputs.profit_target", "kind": "variable", "doc": "

Target profit level.

\n\n

The default is None, which means it is not calculated.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.loss_limit", "modulename": "optionlab.models", "qualname": "Inputs.loss_limit", "kind": "variable", "doc": "

Limit loss level.

\n\n

The default is None, which means it is not calculated.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.opt_commission", "modulename": "optionlab.models", "qualname": "Inputs.opt_commission", "kind": "variable", "doc": "

Brokerage commission for options transactions.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.stock_commission", "modulename": "optionlab.models", "qualname": "Inputs.stock_commission", "kind": "variable", "doc": "

Brokerage commission for stocks transactions.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.discard_nonbusiness_days", "modulename": "optionlab.models", "qualname": "Inputs.discard_nonbusiness_days", "kind": "variable", "doc": "

Discards weekends and holidays when counting the number of days between\ntwo dates.

\n\n

The default is True.

\n", "annotation": ": bool"}, {"fullname": "optionlab.models.Inputs.business_days_in_year", "modulename": "optionlab.models", "qualname": "Inputs.business_days_in_year", "kind": "variable", "doc": "

Number of business days in a year.

\n\n

The default is 252.

\n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.country", "modulename": "optionlab.models", "qualname": "Inputs.country", "kind": "variable", "doc": "

Country whose holidays will be counted if discard_nonbusinessdays is\nset to True.

\n\n

The default is 'US'.

\n", "annotation": ": str"}, {"fullname": "optionlab.models.Inputs.start_date", "modulename": "optionlab.models", "qualname": "Inputs.start_date", "kind": "variable", "doc": "

Start date in the calculations.

\n\n

If not provided, days_to_target_date must be provided.

\n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.target_date", "modulename": "optionlab.models", "qualname": "Inputs.target_date", "kind": "variable", "doc": "

Target date in the calculations.

\n\n

If not provided, days_to_target_date must be provided.

\n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.days_to_target_date", "modulename": "optionlab.models", "qualname": "Inputs.days_to_target_date", "kind": "variable", "doc": "

Days remaining to the target date.

\n\n

If not provided, start_date and target_date must be provided.

\n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.model", "modulename": "optionlab.models", "qualname": "Inputs.model", "kind": "variable", "doc": "

Theoretical model used in the calculations of probability of profit.

\n\n

It can be 'black-scholes' or 'array'.

\n", "annotation": ": Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Inputs.array", "modulename": "optionlab.models", "qualname": "Inputs.array", "kind": "variable", "doc": "

Array of terminal stock prices.

\n\n

The default is an empty array.

\n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.Inputs.model_config", "modulename": "optionlab.models", "qualname": "Inputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.Inputs.model_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'interest_rate': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'min_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'max_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'strategy': FieldInfo(annotation=list[Union[Stock, Option, ClosedPosition]], required=True, metadata=[MinLen(min_length=1)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'profit_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'loss_limit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opt_commission': FieldInfo(annotation=float, required=False, default=0.0), 'stock_commission': FieldInfo(annotation=float, required=False, default=0.0), 'discard_nonbusiness_days': FieldInfo(annotation=bool, required=False, default=True), 'business_days_in_year': FieldInfo(annotation=int, required=False, default=252), 'country': FieldInfo(annotation=str, required=False, default='US'), 'start_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'target_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'days_to_target_date': FieldInfo(annotation=int, required=False, default=0, metadata=[Ge(ge=0)]), 'model': FieldInfo(annotation=Literal['black-scholes', 'array'], required=False, default='black-scholes'), 'array': FieldInfo(annotation=ndarray, required=False, default_factory=init_empty_array)}"}, {"fullname": "optionlab.models.Inputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesInfo", "modulename": "optionlab.models", "qualname": "BlackScholesInfo", "kind": "class", "doc": "

Defines the data returned by a calculation using the Black-Scholes model.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.BlackScholesInfo.call_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_price", "kind": "variable", "doc": "

Price of a call option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_price", "kind": "variable", "doc": "

Price of a put option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_delta", "kind": "variable", "doc": "

Delta of a call option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_delta", "kind": "variable", "doc": "

Delta of a put option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_theta", "kind": "variable", "doc": "

Theta of a call option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_theta", "kind": "variable", "doc": "

Theta of a put option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.gamma", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.gamma", "kind": "variable", "doc": "

Gamma of an option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.vega", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.vega", "kind": "variable", "doc": "

Vega of an option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_rho", "kind": "variable", "doc": "

Rho of a call option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_rho", "kind": "variable", "doc": "

Rho of a put option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_itm_prob", "kind": "variable", "doc": "

Probability of expiring in-the-money probability of a call option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_itm_prob", "kind": "variable", "doc": "

Probability of expiring in-the-money of a put option.

\n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'call_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'gamma': FieldInfo(annotation=Union[float, ndarray], required=True), 'vega': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True)}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs", "modulename": "optionlab.models", "qualname": "Outputs", "kind": "class", "doc": "

Defines the output data from a strategy calculation.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Outputs.probability_of_profit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit", "kind": "variable", "doc": "

Probability of the strategy yielding at least $0.01.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_ranges", "kind": "variable", "doc": "

A list of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least $0.01.

\n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.expected_profit", "modulename": "optionlab.models", "qualname": "Outputs.expected_profit", "kind": "variable", "doc": "

Expected profit when the strategy is profitable.

\n\n

The default is None.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.expected_loss", "modulename": "optionlab.models", "qualname": "Outputs.expected_loss", "kind": "variable", "doc": "

Expected loss when the strategy is not profitable.

\n\n

The default is None.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.per_leg_cost", "modulename": "optionlab.models", "qualname": "Outputs.per_leg_cost", "kind": "variable", "doc": "

List of leg costs.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.strategy_cost", "modulename": "optionlab.models", "qualname": "Outputs.strategy_cost", "kind": "variable", "doc": "

Total strategy cost.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.minimum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.minimum_return_in_the_domain", "kind": "variable", "doc": "

Minimum return of the strategy within the stock price domain.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.maximum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.maximum_return_in_the_domain", "kind": "variable", "doc": "

Maximum return of the strategy within the stock price domain.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.implied_volatility", "modulename": "optionlab.models", "qualname": "Outputs.implied_volatility", "kind": "variable", "doc": "

List of implied volatilities, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.in_the_money_probability", "modulename": "optionlab.models", "qualname": "Outputs.in_the_money_probability", "kind": "variable", "doc": "

List of probabilities of legs expiring in-the-money (ITM).

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.delta", "modulename": "optionlab.models", "qualname": "Outputs.delta", "kind": "variable", "doc": "

List of Delta values, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.gamma", "modulename": "optionlab.models", "qualname": "Outputs.gamma", "kind": "variable", "doc": "

List of Gamma values, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.theta", "modulename": "optionlab.models", "qualname": "Outputs.theta", "kind": "variable", "doc": "

List of Theta values, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.vega", "modulename": "optionlab.models", "qualname": "Outputs.vega", "kind": "variable", "doc": "

List of Vega values, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.rho", "modulename": "optionlab.models", "qualname": "Outputs.rho", "kind": "variable", "doc": "

List of Rho values, one per strategy leg.

\n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.probability_of_profit_target", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit_target", "kind": "variable", "doc": "

Probability of the strategy yielding at least the profit target.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_target_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_target_ranges", "kind": "variable", "doc": "

List of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least the profit target.

\n\n

The default is [].

\n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.probability_of_loss_limit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_loss_limit", "kind": "variable", "doc": "

Probability of the strategy losing at least the loss limit.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.loss_limit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.loss_limit_ranges", "kind": "variable", "doc": "

List of minimum and maximum stock prices defining ranges where the\nstrategy loses at least the loss limit.

\n\n

The default is [].

\n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.model_config", "modulename": "optionlab.models", "qualname": "Outputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs.model_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_profit': FieldInfo(annotation=float, required=True), 'profit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=True), 'expected_profit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_loss': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'per_leg_cost': FieldInfo(annotation=list[float], required=True), 'strategy_cost': FieldInfo(annotation=float, required=True), 'minimum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'maximum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'implied_volatility': FieldInfo(annotation=list[float], required=True), 'in_the_money_probability': FieldInfo(annotation=list[float], required=True), 'delta': FieldInfo(annotation=list[float], required=True), 'gamma': FieldInfo(annotation=list[float], required=True), 'theta': FieldInfo(annotation=list[float], required=True), 'vega': FieldInfo(annotation=list[float], required=True), 'rho': FieldInfo(annotation=list[float], required=True), 'probability_of_profit_target': FieldInfo(annotation=float, required=False, default=0.0), 'profit_target_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'probability_of_loss_limit': FieldInfo(annotation=float, required=False, default=0.0), 'loss_limit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'inputs': FieldInfo(annotation=Inputs, required=True), 'data': FieldInfo(annotation=EngineDataResults, required=True)}"}, {"fullname": "optionlab.models.Outputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs", "modulename": "optionlab.models", "qualname": "PoPOutputs", "kind": "class", "doc": "

Defines the output data from a probability of profit (PoP) calculation.

\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_reaching_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_reaching_target", "kind": "variable", "doc": "

Probability that the strategy return will be equal or greater than the\ntarget.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_missing_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_missing_target", "kind": "variable", "doc": "

Probability that the strategy return will be less than the target.

\n\n

The default is 0.0.

\n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.reaching_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.reaching_target_range", "kind": "variable", "doc": "

Range of stock prices where the strategy return is equal or greater than\nthe target.

\n\n

The default is [].

\n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.missing_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.missing_target_range", "kind": "variable", "doc": "

Range of stock prices where the strategy return is less than the target.

\n\n

The default is [].

\n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_above_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_above_target", "kind": "variable", "doc": "

Expected value of the strategy return when the return is equal or greater\nthan the target.

\n\n

The default is None.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_below_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_below_target", "kind": "variable", "doc": "

Expected value of the strategy return when the return is less than the\ntarget.

\n\n

The default is None.

\n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.model_config", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_config", "kind": "variable", "doc": "

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs.model_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_fields", "kind": "variable", "doc": "

Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

\n\n

This replaces Model.__fields__ from Pydantic V1.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_reaching_target': FieldInfo(annotation=float, required=False, default=0.0), 'probability_of_missing_target': FieldInfo(annotation=float, required=False, default=0.0), 'reaching_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'missing_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'expected_return_above_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_return_below_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.PoPOutputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_computed_fields", "kind": "variable", "doc": "

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

\n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.plot", "modulename": "optionlab.plot", "kind": "module", "doc": "

This module implements the plot_pl function, which displays the profit/loss diagram \nof an options trading strategy.

\n"}, {"fullname": "optionlab.plot.plot_pl", "modulename": "optionlab.plot", "qualname": "plot_pl", "kind": "function", "doc": "

Displays the strategy's profit/loss diagram.

\n\n

Parameters

\n\n

outputs: output data from a strategy calculation with optionlab.engine.run_strategy.

\n\n

Returns

\n\n

None.

\n", "signature": "(outputs: optionlab.models.Outputs) -> None:", "funcdef": "def"}, {"fullname": "optionlab.price_array", "modulename": "optionlab.price_array", "kind": "module", "doc": "

This module defines the create_price_array function, which calculates terminal \nprices from numerical simulations of multiple stock paths.

\n\n

The terminal price array can later be used to calculate the probability of profit \n(PoP) of a strategy using the optionlab.engine.run_strategy function.

\n"}, {"fullname": "optionlab.price_array.create_price_array", "modulename": "optionlab.price_array", "qualname": "create_price_array", "kind": "function", "doc": "

Generates terminal stock prices.

\n\n

Parameters

\n\n

inputs_data: input data used to generate the terminal stock prices.

\n\n

n: number of terminal stock prices.

\n\n

seed: seed for random number generation.

\n\n

Returns

\n\n

Array of terminal prices.

\n", "signature": "(\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.LaplaceInputs | dict,\tn: int = 100000,\tseed: int | None = None) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support", "modulename": "optionlab.support", "kind": "module", "doc": "

This module implements a number of helper functions that are not intended to be \ncalled directly by users, but rather support functionalities within the \noptionlab.engine.run_strategy function.

\n"}, {"fullname": "optionlab.support.get_pl_profile", "modulename": "optionlab.support", "qualname": "get_pl_profile", "kind": "function", "doc": "

Returns the profit/loss profile and cost of an options trade at expiration.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

action: either 'buy' or 'sell'.

\n\n

x: strike price.

\n\n

val: option price.

\n\n

n: number of options.

\n\n

s: array of stock prices.

\n\n

commission: brokerage commission.

\n\n

Returns

\n\n

Profit/loss profile and cost of an option trade at expiration.

\n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_stock", "modulename": "optionlab.support", "qualname": "get_pl_profile_stock", "kind": "function", "doc": "

Returns the profit/loss profile and cost of a stock position.

\n\n

Parameters

\n\n

s0: initial stock price.

\n\n

action: either 'buy' or 'sell'.

\n\n

n: number of shares.

\n\n

s: array of stock prices.

\n\n

commission: brokerage commission.

\n\n

Returns

\n\n

Profit/loss profile and cost of a stock position.

\n", "signature": "(\ts0: float,\taction: Literal['buy', 'sell'],\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_bs", "modulename": "optionlab.support", "qualname": "get_pl_profile_bs", "kind": "function", "doc": "

Returns the profit/loss profile and cost of an options trade on a target date\nbefore expiration using the Black-Scholes model for option pricing.

\n\n

Parameters

\n\n

option_type: either 'call' or 'put'.

\n\n

action: either 'buy' or 'sell'.

\n\n

x: strike price.

\n\n

val: initial option price.

\n\n

r: annualized risk-free interest rate.

\n\n

target_to_maturity_years: time remaining to maturity from the target date,\nin years.

\n\n

volatility: annualized volatility of the underlying asset.

\n\n

n: number of options.

\n\n

s: array of stock prices.

\n\n

y: annualized dividend yield.

\n\n

commission: brokerage commission.

\n\n

Returns

\n\n

Profit/loss profile and cost of an option trade before expiration.

\n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tr: float,\ttarget_to_maturity_years: float,\tvolatility: float,\tn: int,\ts: numpy.ndarray,\ty: float = 0.0,\tcommission: float = 0.0) -> tuple[float | numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.create_price_seq", "modulename": "optionlab.support", "qualname": "create_price_seq", "kind": "function", "doc": "

Generates a sequence of stock prices from a minimum to a maximum price with\nincrement $0.01.

\n\n

Parameters

\n\n

min_price: minimum stock price in the range.

\n\n

max_price: maximum stock price in the range.

\n\n

Returns

\n\n

Array of sequential stock prices.

\n", "signature": "(min_price: float, max_price: float) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pop", "modulename": "optionlab.support", "qualname": "get_pop", "kind": "function", "doc": "

Estimates the probability of profit (PoP) of an options trading strategy.

\n\n

Parameters

\n\n

s: array of stock prices.

\n\n

profit: array of profits and losses.

\n\n

inputs_data: input data used to estimate the probability of profit.

\n\n

target: target return.

\n\n

Returns

\n\n

Outputs of a probability of profit (PoP) calculation.

\n", "signature": "(\ts: numpy.ndarray,\tprofit: numpy.ndarray,\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.ArrayInputs,\ttarget: float = 0.01) -> optionlab.models.PoPOutputs:", "funcdef": "def"}, {"fullname": "optionlab.utils", "modulename": "optionlab.utils", "kind": "module", "doc": "

This module defines utility functions.

\n"}, {"fullname": "optionlab.utils.get_nonbusiness_days", "modulename": "optionlab.utils", "qualname": "get_nonbusiness_days", "kind": "function", "doc": "

Returns the number of non-business days (i.e., weekends and holidays) between\nthe start and end date.

\n\n

Parameters

\n\n

start_date: start date.

\n\n

end_date: end date.

\n\n

country: country of the stock exchange.

\n\n

Returns

\n\n

Number of weekends and holidays between the start and end date.

\n", "signature": "(\tstart_date: datetime.date,\tend_date: datetime.date,\tcountry: str = 'US') -> int:", "funcdef": "def"}, {"fullname": "optionlab.utils.get_pl", "modulename": "optionlab.utils", "qualname": "get_pl", "kind": "function", "doc": "

Returns the stock prices and the corresponding profit/loss profile of either\na leg or the whole strategy.

\n\n

Parameters

\n\n

outputs: output data from a strategy calculation.

\n\n

leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

\n\n

Returns

\n\n

Array of stock prices and array or profits/losses.

\n", "signature": "(\toutputs: optionlab.models.Outputs,\tleg: int | None = None) -> tuple[numpy.ndarray, numpy.ndarray]:", "funcdef": "def"}, {"fullname": "optionlab.utils.pl_to_csv", "modulename": "optionlab.utils", "qualname": "pl_to_csv", "kind": "function", "doc": "

Saves the stock prices and corresponding profit/loss profile of either a leg\nor the whole strategy to a CSV file.

\n\n

Parameters

\n\n

outputs: output data from a strategy calculation.

\n\n

filename: name of the CSV file.

\n\n

leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

\n\n

Returns

\n\n

None.

\n", "signature": "(\toutputs: optionlab.models.Outputs,\tfilename: str = 'pl.csv',\tleg: int | None = None) -> None:", "funcdef": "def"}]; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/optionlab/__init__.py b/optionlab/__init__.py index d680080..529a671 100644 --- a/optionlab/__init__.py +++ b/optionlab/__init__.py @@ -1,61 +1,208 @@ -import typing - - -VERSION = "1.4.2" - - -if typing.TYPE_CHECKING: - # import of virtually everything is supported via `__getattr__` below, - # but we need them here for type checking and IDE support - from .models import ( - Inputs, - OptionType, - Option, - Outputs, - ClosedPosition, - ArrayInputs, - TheoreticalModelInputs, - BlackScholesModelInputs, - LaplaceInputs, - BlackScholesInfo, - TheoreticalModel, - FloatOrNdarray, - StrategyLeg, - StrategyType, - Stock, - Action, - ) - from .black_scholes import ( - get_itm_probability, - get_implied_vol, - get_option_price, - get_d1, - get_d2, - get_bs_info, - get_vega, - get_delta, - get_gamma, - get_theta, - get_rho, - ) - from .engine import run_strategy - from .plot import plot_pl - from .price_array import create_price_array - from .support import ( - get_pl_profile, - get_pl_profile_stock, - get_pl_profile_bs, - create_price_seq, - get_pop, - ) - from .utils import ( - get_nonbusiness_days, - get_pl, - pl_to_csv, - ) +""" +## OptionLab is... +... a Python library designed as a research tool for quickly evaluating options +strategy ideas. It is intended for a wide range of users, from individuals learning +about options trading to developers of quantitative strategies. + +**OptionLab** calculations can produce a number of useful outputs: + +- the profit/loss profile of the strategy on a user-defined target date, + +- the range of stock prices for which the strategy is profitable, + +- the Greeks associated with each leg of the strategy, + +- the resulting debit or credit on the trading account, + +- the maximum and minimum returns within a specified lower and higher price range +of the underlying asset, + +- the expected profit and expected loss, and + +- an estimate of the strategy's probability of profit. + +The probability of profit (PoP) of the strategy on the user-defined target date +is calculated by default using the Black-Scholes model. The user can alternatively +provide an array of underlying asset prices obtained elsewhere (e.g. from the +Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) +to be used in the calculations instead of the Black-Scholes model. This allows +**OptionLab** to function as a calculator using custom models. + +An advanced feature of **OptionLab** that provides great flexibility in building +complex dynamic strategies is the ability to include previously created positions +as legs in a new strategy. Popular strategies that can benefit from this feature +include the Wheel and Covered Call strategies. + +## OptionLab is not... + +... a platform for direct order execution. This capability has not been and +probably will not be implemented. + +Backtesting and trade simulation using Monte Carlo have also not (yet) been +implemented in the API. + +That being said, nothing prevents **OptionLab** from being integrated into an +options quant trader's workflow alongside other tools. + +## Installation + +The easiest way to install **OptionLab** is using **pip**: + +``` +pip install optionlab +``` + +## Quickstart + +**OptionLab** is designed with ease of use in mind. An options strategy can be +defined and evaluated with just a few lines of Python code. The API is streamlined, +and the learning curve is minimal. + +The evaluation of a strategy is done by calling the `optionlab.engine.run_strategy` +function provided by the library. This function receives the input data either +as a dictionary or an `optionlab.models.Inputs` object. + +For example, let's say we wanted to calculate the probability of profit for naked +calls on Apple stocks expiring on December 17, 2021. The strategy setup consisted +of selling 100 175.00 strike calls for 1.15 each on November 22, 2021. + +The input data for this strategy can be provided in a dictionary as follows: + +```python +input_data = { + "stock_price": 164.04, + "start_date": "2021-11-22", + "target_date": "2021-12-17", + "volatility": 0.272, + "interest_rate": 0.0002, + "min_stock": 120, + "max_stock": 200, + "strategy": [ + { + "type": "call", + "strike": 175.0, + "premium": 1.15, + "n": 100, + "action":"sell" + } + ], +} +``` + +Alternatively, the input data could be defined as the `optionlab.models.Inputs` +object below: + +```python +from optionlab import Inputs + +input_data = Inputs( + stock_price = 164.04, + start_date = "2021-11-22", + target_date = "2021-12-17", + volatility = 0.272, + interest_rate = 0.0002, + min_stock = 120, + max_stock = 200, + strategy = [ + { + "type": "call", + "strike": 175.0, + "premium": 1.15, + "n": 100, + "action":"sell" + } + ], +) +``` + +In both cases, the strategy itself is a list of dictionaries, where each dictionary +defines a leg in the strategy. The fields in a leg, depending on the type of the +leg, are described in `optionlab.models.Stock`, `optionlab.models.Option`, and +`optionlab.models.ClosedPosition`. + +After defining the input data, we pass it to the `run_strategy` function as shown +below: + +```python +from optionlab import run_strategy, plot_pl + +out = run_strategy(input_data) + +print(out) + +plot_pl(out) +``` + +The variable `out` is an `optionlab.models.Outputs` object that contains the +results from the calculations. By calling `print` with `out` as an argument, +these results are displayed on screen. + +The `optionlab.plot.plot_pl` function, in turn, takes an `optionlab.models.Outputs` +object as its argument and plots the profit/loss diagram for the strategy. + +## Examples + +Examples for a number of popular options trading strategies can be found as +Jupyter notebooks in the [examples](https://github.com/rgaveiga/optionlab/tree/main/examples) +directory. +""" + +from .models import ( + Inputs, + OptionType, + Option, + Outputs, + ClosedPosition, + ArrayInputs, + TheoreticalModelInputs, + BlackScholesModelInputs, + LaplaceInputs, + BlackScholesInfo, + TheoreticalModel, + FloatOrNdarray, + StrategyLeg, + StrategyLegType, + Stock, + Action, +) +from .black_scholes import ( + get_itm_probability, + get_implied_vol, + get_option_price, + get_d1, + get_d2, + get_bs_info, + get_vega, + get_delta, + get_gamma, + get_theta, + get_rho, +) +from .engine import run_strategy +from .plot import plot_pl +from .price_array import create_price_array +from .support import ( + get_pl_profile, + get_pl_profile_stock, + get_pl_profile_bs, + create_price_seq, + get_pop, +) +from .utils import ( + get_nonbusiness_days, + get_pl, + pl_to_csv, +) + + +VERSION = "1.4.3" + +__docformat__ = "markdown" __version__ = VERSION -__all__ = ( + + +ALL = ( # models "Inputs", "OptionType", @@ -70,7 +217,7 @@ "TheoreticalModel", "FloatOrNdarray", "StrategyLeg", - "StrategyType", + "StrategyLegType", "Stock", "Action", # engine @@ -102,70 +249,8 @@ "get_pl", "pl_to_csv", ) - -# A mapping of {: (package, )} defining dynamic imports -_dynamic_imports: "dict[str, tuple[str, str]]" = { - # models - "Inputs": (__package__, ".models"), - "Outputs": (__package__, ".models"), - "OptionType": (__package__, ".models"), - "Option": (__package__, ".models"), - "ClosedPosition": (__package__, ".models"), - "ArrayInputs": (__package__, ".models"), - "TheoreticalModelInputs": (__package__, ".models"), - "BlackScholesModelInputs": (__package__, ".models"), - "LaplaceInputs": (__package__, ".models"), - "BlackScholesInfo": (__package__, ".models"), - "TheoreticalModel": (__package__, ".models"), - "FloatOrNdarray": (__package__, ".models"), - "StrategyLeg": (__package__, ".models"), - "StrategyType": (__package__, ".models"), - "Stock": (__package__, ".models"), - "Action": (__package__, ".models"), - # engine - "run_strategy": (__package__, ".engine"), - # support - "get_pl_profile": (__package__, ".support"), - "get_pl_profile_stock": (__package__, ".support"), - "get_pl_profile_bs": (__package__, ".support"), - "create_price_seq": (__package__, ".support"), - "get_pop": (__package__, ".support"), - # black_scholes - "get_d1": (__package__, ".black_scholes"), - "get_d2": (__package__, ".black_scholes"), - "get_option_price": (__package__, ".black_scholes"), - "get_itm_probability": (__package__, ".black_scholes"), - "get_implied_vol": (__package__, ".black_scholes"), - "get_bs_info": (__package__, ".black_scholes"), - "get_vega": (__package__, ".black_scholes"), - "get_delta": (__package__, ".black_scholes"), - "get_gamma": (__package__, ".black_scholes"), - "get_theta": (__package__, ".black_scholes"), - "get_rho": (__package__, ".black_scholes"), - # plot - "plot_pl": (__package__, ".plot"), - # price_array - "create_price_array": (__package__, ".price_array"), - # utils - "get_nonbusiness_days": (__package__, ".utils"), - "get_pl": (__package__, ".utils"), - "pl_to_csv": (__package__, ".utils"), -} - - -def __getattr__(attr_name: str) -> object: - dynamic_attr = _dynamic_imports[attr_name] - - package, module_name = dynamic_attr - - from importlib import import_module - - if module_name == "__module__": - return import_module(f".{attr_name}", package=package) - else: - module = import_module(module_name, package=package) - return getattr(module, attr_name) +"""@private""" def __dir__() -> "list[str]": - return list(__all__) + return list(ALL) diff --git a/optionlab/black_scholes.py b/optionlab/black_scholes.py index 3c9c394..5a12d6d 100644 --- a/optionlab/black_scholes.py +++ b/optionlab/black_scholes.py @@ -1,3 +1,8 @@ +""" +This module defines functions that calculate quantities, such as option prices +and the Greeks, related to the Black-Scholes model. +""" + from __future__ import division from scipy import stats @@ -21,24 +26,21 @@ def get_bs_info( Parameters ---------- - s : float - Stock price. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualized risk-free interest rate. - vol : float - Annualized volatility. - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized dividend yield. The default is 0.0. + `s`: stock price. + + `x`: strike price(s). + + `r`: annualized risk-free interest rate. + + `vol`: annualized volatility. + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized dividend yield. Returns ------- - BlackScholesInfo - Information calculated using the Black-Scholes formula. See the documentation - for `BlackScholesInfo`. + Information calculated using the Black-Scholes formula. """ d1 = get_d1(s, x, r, vol, years_to_maturity, y) @@ -87,27 +89,25 @@ def get_option_price( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - s0 : float | numpy.ndarray - Spot price(s) of the underlying asset. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualize risk-free interest rate. - years_to_maturity : float - Time remaining to maturity, in years. - d1 : float | numpy.ndarray - `d1` in Black-Scholes formula. - d2 : float | numpy.ndarray - `d2` in Black-Scholes formula. - y : float, optional - Annualized dividend yield. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `s0`: spot price(s) of the underlying asset. + + `x`: strike price(s). + + `r`: annualize risk-free interest rate. + + `years_to_maturity`: time remaining to maturity, in years. + + `d1`: `d1` in Black-Scholes formula. + + `d2`: `d2` in Black-Scholes formula. + + `y`: annualized dividend yield. Returns ------- - float | numpy.ndarray - Option price(s). + Option price(s). """ s = s0 * exp(-y * years_to_maturity) @@ -139,19 +139,17 @@ def get_delta( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - d1 : float | numpy.ndarray - `d1` in Black-Scholes formula. - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized dividend yield. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `d1`: `d1` in Black-Scholes formula. + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized dividend yield. Returns ------- - float | numpy.ndarray - Option's Greek Delta. + Option's Greek Delta. """ yfac = exp(-y * years_to_maturity) @@ -176,21 +174,19 @@ def get_gamma( Parameters ---------- - s0 : float - Spot price of the underlying asset. - vol : float - Annualized volatitily. - years_to_maturity : float - Time remaining to maturity, in years. - d1 : float | numpy.ndarray - `d1` in Black-Scholes formula. - y : float, optional - Annualized divident yield. The default is 0.0. + `s0`: spot price of the underlying asset. + + `vol`: annualized volatitily. + + `years_to_maturity`: time remaining to maturity, in years. + + `d1`: `d1` in Black-Scholes formula. + + `y`: annualized divident yield. Returns ------- - float | numpy.ndarray - Option's Greek Gamma. + Option's Greek Gamma. """ yfac = exp(-y * years_to_maturity) @@ -216,29 +212,27 @@ def get_theta( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - s0 : float - Spot price of the underlying asset. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualized risk-free interest rate. - vol : float - Annualized volatility. - years_to_maturity : float - Time remaining to maturity, in years. - d1 : float | numpy.ndarray - `d1` in Black-Scholes formula. - d2 : float | numpy.ndarray - `d2` in Black-Scholes formula. - y : float, optional - Annualized dividend yield. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `s0`: spot price of the underlying asset. + + `x`: strike price(s). + + `r`: annualized risk-free interest rate. + + `vol`: annualized volatility. + + `years_to_maturity`: time remaining to maturity, in years. + + `d1`: `d1` in Black-Scholes formula. + + `d2`: `d2` in Black-Scholes formula. + + `y`: annualized dividend yield. Returns ------- - float | numpy.ndarray - Option's Greek Theta. + Option's Greek Theta. """ s = s0 * exp(-y * years_to_maturity) @@ -272,19 +266,17 @@ def get_vega( Parameters ---------- - s0 : float - Spot price of the underlying asset. - years_to_maturity : float - Time remaining to maturity, in years. - d1 : float | numpy.ndarray - `d1` in Black-Scholes formula. - y : float, optional - Annualized dividend yield. The default is 0.0. + `s0`: spot price of the underlying asset. + + `years_to_maturity`: time remaining to maturity, in years. + + `d1`: `d1` in Black-Scholes formula. + + `y`: annualized dividend yield. Returns ------- - float | numpy.ndarray - Option's Greek Vega. + Option's Greek Vega. """ s = s0 * exp(-y * years_to_maturity) @@ -306,21 +298,19 @@ def get_rho( Parameters ---------- - option_type : OptionType - `OptionType` literal value, which must be either **call** or **put**. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualized risk-free interest rate. - years_to_maturity : float - Time remaining to maturity, in years. - d2 : float | numpy.ndarray - `d2` in Black-Scholes formula. + `option_type`: either *'call'* or *'put'*. + + `x`: strike price(s). + + `r`: annualized risk-free interest rate. + + `years_to_maturity`: time remaining to maturity, in years. + + `d2`: `d2` in Black-Scholes formula. Returns ------- - float | numpy.ndarray - Option's Greek Rho. + Option's Greek Rho. """ if option_type == "call": @@ -356,23 +346,21 @@ def get_d1( Parameters ---------- - s0 : float | numpy.ndarray - Spot price(s) of the underlying asset. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualized risk-free interest rate. - vol : float | numpy.ndarray - Annualized volatility(ies). - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized divident yield. The default is 0.0. + `s0`: spot price(s) of the underlying asset. + + `x`: strike price(s). + + `r`: annualized risk-free interest rate. + + `vol`: annualized volatility(ies). + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized divident yield. Returns ------- - float | numpy.ndarray - `d1` in Black-Scholes formula. + `d1` in Black-Scholes formula. """ return (log(s0 / x) + (r - y + vol * vol / 2.0) * years_to_maturity) / ( @@ -393,23 +381,21 @@ def get_d2( Parameters ---------- - s0 : float | numpy.ndarray - Spot price(s) of the underlying asset. - x : float | numpy.ndarray - Strike price(s). - r : float - Annualized risk-free interest rate. - vol : float | numpy.ndarray - Annualized volatility(ies). - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized divident yield. The default is 0.0. + `s0`: spot price(s) of the underlying asset. + + `x`: strike price(s). + + `r`: annualized risk-free interest rate. + + `vol`: annualized volatility(ies). + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized divident yield. Returns ------- - float | numpy.ndarray - `d2` in Black-Scholes formula. + `d2` in Black-Scholes formula. """ return (log(s0 / x) + (r - y - vol * vol / 2.0) * years_to_maturity) / ( @@ -431,25 +417,23 @@ def get_implied_vol( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - oprice : float - Market price of an option. - s0 : float - Spot price of the underlying asset. - x : float - Strike price. - r : float - Annualized risk-free interest rate. - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized dividend yield. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `oprice`: market price of an option. + + `s0`: spot price of the underlying asset. + + `x`: strike price. + + `r`: annualized risk-free interest rate. + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized dividend yield. Returns ------- - float - Implied volatility of the option. + Option's implied volatility. """ vol = 0.001 * arange(1, 1001) @@ -469,23 +453,21 @@ def get_itm_probability( y: float = 0.0, ) -> FloatOrNdarray: """ - Returns the In-The-Money probability of an option. + Returns the probability(ies) that the option(s) will expire in-the-money (ITM). Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - d2 : float | numpy.ndarray - `d2` in Black-Scholes formula. - years_to_maturity : float - Time remaining to maturity, in years. - y : float, optional - Annualized dividend yield. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `d2`: `d2` in Black-Scholes formula. + + `years_to_maturity`: time remaining to maturity, in years. + + `y`: annualized dividend yield. Returns ------- - float | numpy.ndarray - In-The-Money probability(ies). + Probability(ies) that the option(s) will expire in-the-money (ITM). """ yfac = exp(-y * years_to_maturity) diff --git a/optionlab/engine.py b/optionlab/engine.py index 90d4278..93051a4 100644 --- a/optionlab/engine.py +++ b/optionlab/engine.py @@ -1,3 +1,11 @@ +""" +This module defines the `run_strategy` function. + +Given input data provided as either an `optionlab.models.Inputs` object or a dictionary, +`run_strategy` returns the results of an options strategy calculation (e.g., the +probability of profit on the target date) as an `optionlab.models.Outputs` object. +""" + from __future__ import division from __future__ import print_function @@ -36,15 +44,11 @@ def run_strategy(inputs_data: Inputs | dict) -> Outputs: Parameters ---------- - inputs_data : Inputs | dict - Input data used in the strategy calculation. See the documentation for - `Inputs` for more details. + `inputs_data`: input data used in the strategy calculation. Returns ------- - Outputs - Output data from the strategy calculation. See the documentation for - `Outputs` for more details. + Output data from the strategy calculation. """ inputs = ( @@ -177,7 +181,7 @@ def _run(data: EngineData) -> EngineData: if inputs.model == "array": data.strategy_profit_mc += data.profit_mc[i] - if inputs.model in ("normal", "black-scholes"): + if inputs.model == "black-scholes": pop_inputs = BlackScholesModelInputs( stock_price=inputs.stock_price, volatility=inputs.volatility, diff --git a/optionlab/models.py b/optionlab/models.py index fcc4b9b..20b0e47 100644 --- a/optionlab/models.py +++ b/optionlab/models.py @@ -1,3 +1,12 @@ +""" +This module primarily implements Pydantic models that represent inputs and outputs +of strategy calculations. It also implements constants and custom types. + +From the user's point of view, the two most important classes that they will use +to provide input and subsequently process calculation results are `Inputs` and +`Outputs`, respectively. +""" + import datetime as dt from typing import Literal, Optional @@ -5,136 +14,163 @@ from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict OptionType = Literal["call", "put"] +"""Option type in a strategy leg.""" + Action = Literal["buy", "sell"] -StrategyType = Literal["stock"] | OptionType | Literal["closed"] +"""Action taken in in a strategy leg.""" + +StrategyLegType = Literal["stock"] | OptionType | Literal["closed"] +"""Type of strategy leg.""" + +TheoreticalModel = Literal["black-scholes", "array"] +""" +Theoretical model used in probability of profit (PoP) calculations. +""" + Range = tuple[float, float] -TheoreticalModel = Literal["black-scholes", "normal", "array"] +"""Range boundaries.""" + FloatOrNdarray = float | np.ndarray +"""Float or numpy array custom type.""" def init_empty_array() -> np.ndarray: + """@private""" + return np.array([]) -class BaseLeg(BaseModel): +class Stock(BaseModel): + """Defines the attributes of a stock leg in a strategy.""" + + type: Literal["stock"] = "stock" + """It must be *'stock'*.""" + n: int = Field(gt=0) - action: Action - prev_pos: Optional[float] = None + """Number of shares.""" + action: Action + """Either *'buy'* or *'sell'*.""" -class Stock(BaseLeg): + prev_pos: Optional[float] = None """ - Defines the attributes of a stock leg in a strategy. - - Attributes - ---------- - type : str - It must be **stock**. - n : int - Number of shares. - action : str - `Action` literal value, which must be either **buy** or **sell**. - prev_pos : float, optional - Stock price effectively paid or received in a previously opened position. - If positive, the position remains open and the payoff calculation considers - this price instead of the current stock price. If negative, the position - is closed and the difference between this price and the current price is - included in the payoff calculation. The default is None, which means this - stock position is not a previously opened position. + Stock price effectively paid or received in a previously opened position. + + - If positive, the position remains open and the payoff calculation considers + this price instead of the current stock price. + + - If negative, the position is closed and the difference between this price + and the current price is included in the payoff calculation. + + The default is `None`, which means this stock position is not a previously + opened position. """ - type: Literal["stock"] = "stock" - -class Option(BaseLeg): - """ - Defines the attributes of an option leg in a strategy. - - Attributes - ---------- - type : str - `OptionType` literal value, which must be either **call** or **put**. - strike : float - Strike price. - premium : float - Option premium. - n : int - Number of options. - action : str - `Action` literal value, which must be either **buy** or **sell**. - prev_pos : float | None, optional - Premium effectively paid or received in a previously opened position. If - positive, the position remains open and the payoff calculation considers - this price instead of the current price of the option. If negative, the - position is closed and the difference between this price and the current - price is included in the payoff calculation. The default is None, which - means this option position is not a previously opened position. - expiration : str | int | None, optional - Expiration date or number of days remaining to maturity. The default is - None. - """ +class Option(BaseModel): + """Defines the attributes of an option leg in a strategy.""" type: OptionType + """Either *'call'* or *'put'*.""" + strike: float = Field(gt=0) + """Strike price.""" + premium: float = Field(gt=0) + """Option premium.""" + + action: Action + """Either *'buy'* or *'sell'*.""" + + n: int = Field(gt=0) + """Number of options.""" + + prev_pos: Optional[float] = None + """ + Premium effectively paid or received in a previously opened position. + + - If positive, the position remains open and the payoff calculation considers + this price instead of the current price of the option. + + - If negative, the position is closed and the difference between this price + and the current price is included in the payoff calculation. + + The default is `None`, which means this option position is not a previously + opened position. + """ + expiration: dt.date | int | None = None + """ + Expiration date or number of days remaining to expiration. + + The default is `None`, which means the expiration is the same as `Inputs.target_date` + or `Inputs.days_to_target_date`. + """ @field_validator("expiration") def validate_expiration(cls, v: dt.date | int | None) -> dt.date | int | None: + """@private""" + if isinstance(v, int) and v <= 0: raise ValueError("If expiration is an integer, it must be greater than 0.") return v class ClosedPosition(BaseModel): - """ - Defines the attributes of a previously closed position in a strategy. - - Attributes - ---------- - type : str - It must be **closed**. - prev_pos : float - The total amount of the closed position. If positive, it resulted in a - profit; if negative, it incurred a loss. - """ + """Defines the attributes of a previously closed position in a strategy.""" type: Literal["closed"] = "closed" + """It must be *'closed'*.""" + prev_pos: float + """ + The total amount of the closed position. + + - If positive, it resulted in a profit. + + - If negative, it incurred a loss. + + This amount will be added to the payoff and taken into account in the strategy + calculations. + """ StrategyLeg = Stock | Option | ClosedPosition +"""Leg in a strategy.""" class TheoreticalModelInputs(BaseModel): + """Inputs for calculations, such as the probability of profit (PoP).""" + stock_price: float = Field(gt=0.0) + """Stock price.""" + volatility: float = Field(gt=0.0) + """Annualized volatility of the underlying asset.""" + years_to_target_date: float = Field(ge=0.0) + """Time remaining until target date, in years.""" class BlackScholesModelInputs(TheoreticalModelInputs): - """ - Defines the input data for the calculations using the Black-Scholes model. - - Attributes - ---------- - model : str - It must be either **black-scholes** or **normal**. - stock_price : float - Stock price. - volatility : float - Annualized volatility of the underlying asset. - years_to_target_date : float - Time remaining until target date, in years. - interest_rate : float, optional - Annualized risk-free interest rate. The default is 0.0. - dividend_yield : float, optional - Annualized dividend yield. The default is 0.0. - """ - - model: Literal["black-scholes", "normal"] = "black-scholes" + """Defines the input data for the calculations using the Black-Scholes model.""" + + model: Literal["black-scholes"] = "black-scholes" + """It must be *'black-scholes'*.""" + interest_rate: float = Field(0.0, ge=0.0) + """ + Annualized risk-free interest rate. + + The default is 0.0. + """ + dividend_yield: float = Field(0.0, ge=0.0, le=1.0) + """ + Annualized dividend yield. + + The default is 0.0. + """ __hash__ = object.__hash__ @@ -143,23 +179,13 @@ class LaplaceInputs(TheoreticalModelInputs): """ Defines the input data for the calculations using a log-Laplace distribution of stock prices. - - Attributes - ---------- - model : str - It must be **laplace**. - stock_price : float - Stock price. - mu : float - Annualized return of the underlying asset. - volatility : float - Annualized volatility of the underlying asset. - years_to_target_date : float - Time remaining until target date, in years. """ model: Literal["laplace"] = "laplace" + """It must be '*laplace*'.""" + mu: float + """Annualized return of the underlying asset.""" __hash__ = object.__hash__ @@ -168,23 +194,21 @@ class ArrayInputs(BaseModel): """ Defines the input data for the calculations when using an array of strategy returns. - - Attributes - ---------- - model : str - It must be **array**. - array : numpy.ndarray - Array of strategy returns. """ model: Literal["array"] = "array" + """It must be *'array*'.""" + array: np.ndarray + """Array of strategy returns.""" model_config = ConfigDict(arbitrary_types_allowed=True) @field_validator("array", mode="before") @classmethod def validate_arrays(cls, v: np.ndarray | list[float]) -> np.ndarray: + """@private""" + arr = np.asarray(v) if arr.shape[0] == 0: raise ValueError("The array is empty!") @@ -192,84 +216,126 @@ def validate_arrays(cls, v: np.ndarray | list[float]) -> np.ndarray: class Inputs(BaseModel): - """ - Defines the input data for a strategy calculation. - - Attributes - ---------- - stock_price : float - Spot price of the underlying. - volatility : float - Annualized volatility. - interest_rate : float - Annualized risk-free interest rate. - min_stock : float - Minimum value of the stock in the stock price domain. - max_stock : float - Maximum value of the stock in the stock price domain. - strategy : list[StrategyLeg] - A list of strategy legs. - dividend_yield : float, optional - Annualized dividend yield. The default is 0.0. - profit_target : float, optional - Target profit level. The default is None, which means it is not - calculated. - loss_limit : float, optional - Limit loss level. The default is None, which means it is not calculated. - opt_commission : float - Brokerage commission for options transactions. The default is 0.0. - stock_commission : float - Brokerage commission for stocks transactions. The default is 0.0. - discard_nonbusiness_days : bool, optional - Discards weekends and holidays when counting the number of days between - two dates. The default is True. - business_days_in_year : int, optional - Number of business days in a year. The default is 252. - country : str, optional - Country whose holidays will be counted if `discard_nonbusinessdays` is - set to True. The default is **US**. - start_date : datetime.date, optional - Start date in the calculations. If not provided, `days_to_target_date` - must be provided. - target_date : datetime.date, optional - Target date in the calculations. If not provided, `days_to_target_date` - must be provided. - days_to_target_date : int, optional - Days remaining to the target date. If not provided, `start_date` and - `target_date` must be provided. - model : str, optional - Theoretical model used in the calculations of probability of profit. It - can be **black-scholes** (the same as **normal**) or **array**. The default - is **black-scholes**. - array : numpy.ndarray, optional - Array of terminal stock prices. The default is an empty array. - """ + """Defines the input data for a strategy calculation.""" stock_price: float = Field(gt=0.0) + """Spot price of the underlying.""" + volatility: float = Field(ge=0.0) + """Annualized volatility.""" + interest_rate: float = Field(ge=0.0) + """Annualized risk-free interest rate.""" + min_stock: float = Field(ge=0.0) + """Minimum value of the stock in the stock price domain.""" + max_stock: float = Field(ge=0.0) + """Maximum value of the stock in the stock price domain.""" + strategy: list[StrategyLeg] = Field(..., min_length=1) + """A list of strategy legs.""" + dividend_yield: float = Field(0.0, ge=0.0) + """ + Annualized dividend yield. + + The default is 0.0. + """ + profit_target: Optional[float] = None + """ + Target profit level. + + The default is `None`, which means it is not calculated. + """ + loss_limit: Optional[float] = None + """ + Limit loss level. + + The default is `None`, which means it is not calculated. + """ + opt_commission: float = 0.0 + """ + Brokerage commission for options transactions. + + The default is 0.0. + """ + stock_commission: float = 0.0 + """ + Brokerage commission for stocks transactions. + + The default is 0.0. + """ + discard_nonbusiness_days: bool = True + """ + Discards weekends and holidays when counting the number of days between + two dates. + + The default is `True`. + """ + business_days_in_year: int = 252 + """ + Number of business days in a year. + + The default is 252. + """ + country: str = "US" + """ + Country whose holidays will be counted if `discard_nonbusinessdays` is + set to `True`. + + The default is '*US*'. + """ + start_date: dt.date | None = None + """ + Start date in the calculations. + + If not provided, `days_to_target_date` must be provided. + """ + target_date: dt.date | None = None + """ + Target date in the calculations. + + If not provided, `days_to_target_date` must be provided. + """ + days_to_target_date: int = Field(0, ge=0) + """ + Days remaining to the target date. + + If not provided, `start_date` and `target_date` must be provided. + """ + model: TheoreticalModel = "black-scholes" + """ + Theoretical model used in the calculations of probability of profit. + + It can be *'black-scholes'* or *'array*'. + """ + array: np.ndarray = Field(default_factory=init_empty_array) + """ + Array of terminal stock prices. + + The default is an empty array. + """ model_config = ConfigDict(arbitrary_types_allowed=True) @field_validator("strategy") @classmethod def validate_strategy(cls, v: list[StrategyLeg]) -> list[StrategyLeg]: + """@private""" + types = [strategy.type for strategy in v] if types.count("closed") > 1: raise ValueError("Only one position of type 'closed' is allowed!") @@ -277,6 +343,8 @@ def validate_strategy(cls, v: list[StrategyLeg]) -> list[StrategyLeg]: @model_validator(mode="after") def validate_dates(self) -> "Inputs": + """@private""" + expiration_dates = [ strategy.expiration for strategy in self.strategy @@ -303,6 +371,8 @@ def validate_dates(self) -> "Inputs": @model_validator(mode="after") def validate_model_array(self) -> "Inputs": + """@private""" + if self.model != "array": return self elif self.array is None: @@ -317,50 +387,50 @@ def validate_model_array(self) -> "Inputs": class BlackScholesInfo(BaseModel): - """ - Defines the data returned by a calculation using the Black-Scholes model. - - Attributes - ---------- - call_price : float | numpy.ndarray - Price of a call option. - put_price : float | numpy.ndarray - Price of a put option. - call_delta : float | numpy.ndarray - Delta of a call option. - put_delta : float | numpy.ndarray - Delta of a put option. - gamma : float | numpy.ndarray - Gamma of an option. - vega : float | numpy.ndarray - Vega of an option. - call_rho : float | numpy.ndarray - Rho of a call option. - put_rho : float | numpy.ndarray - Rho of a put option. - call_itm_prob : float | numpy.ndarray - In-the-money probability of a call option. - put_itm_prob : float | numpy.ndarray - In-the-money probability of a put option. - """ + """Defines the data returned by a calculation using the Black-Scholes model.""" call_price: FloatOrNdarray + """Price of a call option.""" + put_price: FloatOrNdarray + """Price of a put option.""" + call_delta: FloatOrNdarray + """Delta of a call option.""" + put_delta: FloatOrNdarray + """Delta of a put option.""" + call_theta: FloatOrNdarray + """Theta of a call option.""" + put_theta: FloatOrNdarray + """Theta of a put option.""" + gamma: FloatOrNdarray + """Gamma of an option.""" + vega: FloatOrNdarray + """Vega of an option.""" + call_rho: FloatOrNdarray + """Rho of a call option.""" + put_rho: FloatOrNdarray + """Rho of a put option.""" + call_itm_prob: FloatOrNdarray + """Probability of expiring in-the-money probability of a call option.""" + put_itm_prob: FloatOrNdarray + """Probability of expiring in-the-money of a put option.""" model_config = ConfigDict(arbitrary_types_allowed=True) class EngineDataResults(BaseModel): + """@private""" + stock_price_array: np.ndarray terminal_stock_prices: np.ndarray = Field(default_factory=init_empty_array) profit: np.ndarray = Field(default_factory=init_empty_array) @@ -371,12 +441,14 @@ class EngineDataResults(BaseModel): premium: list[float] = [] n: list[int] = [] action: list[Action | Literal["n/a"]] = [] - type: list[StrategyType] = [] + type: list[StrategyLegType] = [] model_config = ConfigDict(arbitrary_types_allowed=True) class EngineData(EngineDataResults): + """@private""" + inputs: Inputs previous_position: list[float] = [] use_bs: list[bool] = [] @@ -404,80 +476,123 @@ class EngineData(EngineDataResults): class Outputs(BaseModel): """ Defines the output data from a strategy calculation. - - Attributes - ---------- - probability_of_profit : float - Probability of the strategy yielding at least $0.01. - profit_ranges : list[Range] - A list of minimum and maximum stock prices defining ranges in which the - strategy makes at least $0.01. - expected_profit : float, optional - Expected profit when the strategy is profitable. The default is None. - expected_loss : float, optional - Expected loss when the strategy is not profitable. The default is None. - strategy_cost : float - Total strategy cost. - per_leg_cost : list[float] - A list of costs, one per strategy leg. - implied_volatility : list[float] - List of implied volatilities, one per strategy leg. - in_the_money_probability : list[float] - List of ITM probabilities, one per strategy leg. - delta : list[float] - List of Delta values, one per strategy leg. - gamma : list[float] - List of Gamma values, one per strategy leg. - theta : list[float] - List of Theta values, one per strategy leg. - vega : list[float] - List of Vega values, one per strategy leg. - rho : list[float] - List of Rho values, one per strategy leg. - minimum_return_in_the_domain : float - Minimum return of the strategy within the stock price domain. - maximum_return_in_the_domain : float - Maximum return of the strategy within the stock price domain. - probability_of_profit_target : float, optional - Probability of the strategy yielding at least the profit target. The - default is 0.0. - profit_target_ranges : list[Range], optional - List of minimum and maximum stock prices defining ranges in which the - strategy makes at least the profit target. The default is []. - probability_of_loss_limit : float, optional - Probability of the strategy losing at least the loss limit. The default - is 0.0. - loss_limit_ranges : list[Range], optional - List of minimum and maximum stock prices defining ranges where the - strategy loses at least the loss limit. The default is []. - data : EngineDataResults - Further data from the strategy calculation that can be used in the - post-processing of the outputs. - inputs : Inputs - Input data used in the strategy calculation. """ - inputs: Inputs - data: EngineDataResults probability_of_profit: float + """ + Probability of the strategy yielding at least $0.01. + """ + profit_ranges: list[Range] + """ + A list of minimum and maximum stock prices defining ranges in which the + strategy makes at least $0.01. + """ + expected_profit: Optional[float] = None + """ + Expected profit when the strategy is profitable. + + The default is `None`. + """ + expected_loss: Optional[float] = None + """ + Expected loss when the strategy is not profitable. + + The default is `None`. + """ + per_leg_cost: list[float] + """ + List of leg costs. + """ + strategy_cost: float + """ + Total strategy cost. + """ + minimum_return_in_the_domain: float + """ + Minimum return of the strategy within the stock price domain. + """ + maximum_return_in_the_domain: float + """ + Maximum return of the strategy within the stock price domain. + """ + implied_volatility: list[float] + """ + List of implied volatilities, one per strategy leg. + """ + in_the_money_probability: list[float] + """ + List of probabilities of legs expiring in-the-money (ITM). + """ + delta: list[float] + """ + List of Delta values, one per strategy leg. + """ + gamma: list[float] + """ + List of Gamma values, one per strategy leg. + """ + theta: list[float] + """ + List of Theta values, one per strategy leg. + """ + vega: list[float] + """ + List of Vega values, one per strategy leg. + """ + rho: list[float] + """ + List of Rho values, one per strategy leg. + """ + probability_of_profit_target: float = 0.0 + """ + Probability of the strategy yielding at least the profit target. + + The default is 0.0. + """ + profit_target_ranges: list[Range] = [] + """ + List of minimum and maximum stock prices defining ranges in which the + strategy makes at least the profit target. + + The default is []. + """ + probability_of_loss_limit: float = 0.0 + """ + Probability of the strategy losing at least the loss limit. + + The default is 0.0. + """ + loss_limit_ranges: list[Range] = [] + """ + List of minimum and maximum stock prices defining ranges where the + strategy loses at least the loss limit. + + The default is []. + """ + + inputs: Inputs + """@private""" + + data: EngineDataResults + """@private""" def __str__(self): s = "" @@ -495,32 +610,50 @@ def __str__(self): class PoPOutputs(BaseModel): """ Defines the output data from a probability of profit (PoP) calculation. - - Attributes - ---------- - probability_of_reaching_target : float, optional - Probability that the strategy return will be equal or greater than the - target. The default is 0.0. - probability_of_missing_target : float, optional - Probability that the strategy return will be less than the target. The - default is 0.0. - reaching_target_range : list[Range], optional - Range of stock prices where the strategy return is equal or greater than - the target. The default is []. - missing_target_range : list[Range], optional - Range of stock prices where the strategy return is less than the target. - The default is []. - expected_return_above_target : float, optional - Expected value of the strategy return when the return is equal or greater - than the target. The default is None. - expected_return_below_target : float, optional - Expected value of the strategy return when the return is less than the - target. The default is None. """ probability_of_reaching_target: float = 0.0 + """ + Probability that the strategy return will be equal or greater than the + target. + + The default is 0.0. + """ + probability_of_missing_target: float = 0.0 + """ + Probability that the strategy return will be less than the target. + + The default is 0.0. + """ + reaching_target_range: list[Range] = [] + """ + Range of stock prices where the strategy return is equal or greater than + the target. + + The default is []. + """ + missing_target_range: list[Range] = [] + """ + Range of stock prices where the strategy return is less than the target. + + The default is []. + """ + expected_return_above_target: Optional[float] = None + """ + Expected value of the strategy return when the return is equal or greater + than the target. + + The default is `None`. + """ + expected_return_below_target: Optional[float] = None + """ + Expected value of the strategy return when the return is less than the + target. + + The default is `None`. + """ diff --git a/optionlab/plot.py b/optionlab/plot.py index 97f6bf8..c23f5f4 100644 --- a/optionlab/plot.py +++ b/optionlab/plot.py @@ -1,3 +1,8 @@ +""" +This module implements the `plot_pl` function, which displays the profit/loss diagram +of an options trading strategy. +""" + from __future__ import division from __future__ import print_function @@ -14,12 +19,11 @@ def plot_pl(outputs: Outputs) -> None: Parameters ---------- - outputs : Outputs - Output data from a strategy calculation. + `outputs`: output data from a strategy calculation with `optionlab.engine.run_strategy`. Returns ------- - None. + `None`. """ st = outputs.data diff --git a/optionlab/price_array.py b/optionlab/price_array.py index 6ec7c4c..8f9781a 100644 --- a/optionlab/price_array.py +++ b/optionlab/price_array.py @@ -1,3 +1,11 @@ +""" +This module defines the `create_price_array` function, which calculates terminal +prices from numerical simulations of multiple stock paths. + +The terminal price array can later be used to calculate the probability of profit +(PoP) of a strategy using the `optionlab.engine.run_strategy` function. +""" + from functools import lru_cache import numpy as np @@ -18,18 +26,15 @@ def create_price_array( Parameters ---------- - inputs_data : BlackScholesModelInputs | LaplaceInputs | dict - Input data used to generate the terminal stock prices. See the documentation - for `BlackScholesModelInputs` and `LaplaceInputs` for more details. - n : int, optional - Number of terminal stock prices. The default is 100,000. - seed : int | None, optional - Seed for random number generation. The default is None. + `inputs_data`: input data used to generate the terminal stock prices. + + `n`: number of terminal stock prices. + + `seed`: seed for random number generation. Returns ------- - numpy.ndarray - Array of terminal prices. + Array of terminal prices. """ inputs: BlackScholesModelInputs | LaplaceInputs @@ -37,7 +42,7 @@ def create_price_array( if isinstance(inputs_data, dict): input_type = inputs_data["model"] - if input_type in ("black-scholes", "normal"): + if input_type == "black-scholes": inputs = BlackScholesModelInputs.model_validate(inputs_data) elif input_type == "laplace": inputs = LaplaceInputs.model_validate(inputs_data) @@ -55,7 +60,7 @@ def create_price_array( np_seed_number(seed) - if input_type in ("black-scholes", "normal"): + if input_type == "black-scholes": arr = _get_array_price_from_BS(inputs, n) elif input_type == "laplace": arr = _get_array_price_from_laplace(inputs, n) diff --git a/optionlab/support.py b/optionlab/support.py index 69ff2e0..bb32aad 100644 --- a/optionlab/support.py +++ b/optionlab/support.py @@ -1,3 +1,9 @@ +""" +This module implements a number of helper functions that are not intended to be +called directly by users, but rather support functionalities within the +`optionlab.engine.run_strategy` function. +""" + from __future__ import division from functools import lru_cache @@ -35,25 +41,23 @@ def get_pl_profile( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - action : str - `Action` literal value, which must be either **buy** or **sell**. - x : float - Strike price. - val : float - Option price. - n : int - Number of options. - s : numpy.ndarray - Array of stock prices. - commission : float, optional - Brokerage commission. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `action`: either *'buy'* or *'sell'*. + + `x`: strike price. + + `val`: option price. + + `n`: number of options. + + `s`: array of stock prices. + + `commission`: brokerage commission. Returns ------- - tuple[numpy.ndarray, float] - Profit/loss profile and cost of an option trade at expiration. + Profit/loss profile and cost of an option trade at expiration. """ if action == "buy": @@ -80,21 +84,19 @@ def get_pl_profile_stock( Parameters ---------- - s0 : float - Initial stock price. - action : str - `Action` literal value, which must be either **buy** or **sell**. - n : int - Number of shares. - s : numpy.ndarray - Array of stock prices. - commission : float, optional - Brokerage commission. The default is 0.0. + `s0`: initial stock price. + + `action`: either *'buy'* or *'sell'*. + + `n`: number of shares. + + `s`: array of stock prices. + + `commission`: brokerage commission. Returns ------- - tuple[numpy.ndarray, float] - Profit/loss profile and cost of a stock position. + Profit/loss profile and cost of a stock position. """ if action == "buy": @@ -126,33 +128,32 @@ def get_pl_profile_bs( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - action : str - `Action` literal value, which must be either **buy** or **sell**. - x : float - Strike price. - val : float - Initial option price. - r : float - Annualized risk-free interest rate. - target_to_maturity_years : float - Time remaining to maturity from the target date, in years. - volatility : float - Annualized volatility of the underlying asset. - n : int - Number of options. - s : numpy.ndarray - Array of stock prices. - y : float, optional - Annualized dividend yield. The default is 0.0. - commission : float, optional - Brokerage commission. The default is 0.0. + `option_type`: either *'call'* or *'put'*. + + `action`: either *'buy'* or *'sell'*. + + `x`: strike price. + + `val`: initial option price. + + `r`: annualized risk-free interest rate. + + `target_to_maturity_years`: time remaining to maturity from the target date, + in years. + + `volatility`: annualized volatility of the underlying asset. + + `n`: number of options. + + `s`: array of stock prices. + + `y`: annualized dividend yield. + + `commission`: brokerage commission. Returns ------- - tuple[numpy.ndarray, float] - Profit/loss profile and cost of an option trade before expiration. + Profit/loss profile and cost of an option trade before expiration. """ if action == "buy": @@ -182,15 +183,13 @@ def create_price_seq(min_price: float, max_price: float) -> np.ndarray: Parameters ---------- - min_price : float - Minimum stock price in the range. - max_price : float - Maximum stock price in the range. + `min_price`: minimum stock price in the range. + + `max_price`: maximum stock price in the range. Returns ------- - numpy.ndarray - Array of sequential stock prices. + Array of sequential stock prices. """ if max_price > min_price: @@ -210,20 +209,17 @@ def get_pop( Parameters ---------- - s : numpy.ndarray - Array of stock prices. - profit : numpy.ndarray - Array of profits and losses. - inputs_data : BlackScholesModelInputs | ArrayInputs - Input data used to estimate the probability of profit. See the documentation - for `BlackScholesModelInputs` and `ArrayInputs` for more details. - target : float, optional - Return target. The default is 0.01. + `s`: array of stock prices. + + `profit`: array of profits and losses. + + `inputs_data`: input data used to estimate the probability of profit. + + `target`: target return. Returns ------- - PoPOutputs - Outputs. See the documentation for `PoPOutputs` for more details. + Outputs of a probability of profit (PoP) calculation. """ probability_of_reaching_target: float @@ -270,21 +266,19 @@ def _get_pl_option( Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - opvalue : float - Option price. - action : str - `Action` literal value, which must be either **buy** or **sell**. - s : numpy.ndarray - Array of stock prices. - x : float - Strike price. + `option_type`: either *'call'* or *'put'*. + + `opvalue`: option price. + + `action`: either *'buy'* or *'sell'*. + + `s`: array of stock prices. + + `x`: strike price. Returns ------- - numpy.ndarray - Profit or loss profile of an option leg at expiration. + Profit or loss profile of an option leg at expiration. """ if action == "sell": @@ -301,17 +295,15 @@ def _get_payoff(option_type: OptionType, s: np.ndarray, x: float) -> np.ndarray: Parameters ---------- - option_type : str - `OptionType` literal value, which must be either **call** or **put**. - s : numpy.ndarray - Array of stock prices. - x : float - Strike price. + `option_type`: either *'call'* or *'put'*. + + `s`: array of stock prices. + + `x`: strike price. Returns ------- - numpy.ndarray - Payoff of an option leg at expiration. + Payoff of an option leg at expiration. """ if option_type == "call": @@ -328,17 +320,15 @@ def _get_pl_stock(s0: float, action: Action, s: np.ndarray) -> np.ndarray: Parameters ---------- - s0 : float - Spot price of the underlying asset. - action : str - `Action` literal value, which must be either **buy** or **sell**. - s : numpy.ndarray - Array of stock prices. + `s0`: spot price of the underlying asset. + + `action`: either *'buy'* or *'sell'*. + + `s`: array of stock prices. Returns ------- - numpy.ndarray - Profit or loss profile of a stock position. + Profit or loss profile of a stock position. """ if action == "sell": @@ -361,22 +351,20 @@ def _get_pop_bs( Parameters ---------- - s : numpy.ndarray - Array of stock prices. - profit : numpy.ndarray - Array of profits and losses. - inputs : BlackScholesModelInputs - Input data used to estimate the probability of profit. See the documentation - for `BlackScholesModelInputs` for more details. - profit_range : tuple[list[Range], list[Range]] - Tuple of lists of stock price pairs defining the profit and loss ranges. + `s`: array of stock prices. + + `profit`: array of profits and losses. + + `inputs`: input data used to estimate the probability of profit. + + `profit_range`: lists of stock price pairs defining the profit and loss + ranges. Returns ------- - tuple[float, float | None, float, float | None] - Probability of reaching the return target, expected value above the target, - probability of missing the return target, and expected value below the - target. + Probability of reaching the return target, expected value above the target, + probability of missing the return target, and expected value below the + target. """ expected_return_above_target = None @@ -427,18 +415,15 @@ def _get_pop_array( Parameters ---------- - inputs : ArrayInputs - Input data used to estimate the probability of profit. See the documentation - for `ArrayInputs` for more details. - target : float - Return target. + `inputs`: input data used to estimate the probability of profit. + + `target`: target return. Returns ------- - tuple[float, float | None, float, float | None] - Probability of reaching the return target, expected value above the target, - probability of missing the return target, and expected value below the - target. + Probability of reaching the target return, expected value above the target, + probability of missing the target return, and expected value below the + target. """ if inputs.array.shape[0] == 0: @@ -465,23 +450,21 @@ def _get_profit_range( s: np.ndarray, profit: np.ndarray, target: float = 0.01 ) -> tuple[list[Range], list[Range]]: """ - Returns a tuple of lists of stock price ranges: one representing the ranges - where the options trade returns are equal to or greater than the target, and - the other representing the ranges where they fall short. + Returns lists of stock price ranges: one representing the ranges where the + options trade returns are equal to or greater than the target, and the other + representing the ranges where they fall short. Parameters ---------- - s : numpy.ndarray - Array of stock prices. - profit : numpy.ndarray - Array of profits and losses. - target : float, optional - Profit target. The default is 0.01. + `s`: array of stock prices. + + `profit`: array of profits and losses. + + `target`: target profit. Returns ------- - tuple(list[Range], list[Range]) - Tuple of lists of stock price pairs. + Lists of stock price pairs. """ profit_range = [] @@ -551,15 +534,13 @@ def _get_sign_changes(profit: np.ndarray, target: float) -> list[int]: Parameters ---------- - profit : np.ndarray - Array of profits and losses. - target : float - Profit target. + `profit`: array of profits and losses. + + `target`: target profit. Returns ------- - list[int] - List of indices. + List of indices. """ p_temp = profit - target + 1e-10 diff --git a/optionlab/utils.py b/optionlab/utils.py index 3f60c0f..0ae970f 100644 --- a/optionlab/utils.py +++ b/optionlab/utils.py @@ -1,3 +1,7 @@ +""" +This module defines utility functions. +""" + from __future__ import division import datetime as dt @@ -20,19 +24,15 @@ def get_nonbusiness_days( Parameters ---------- - start_date : datetime.date - Start date. - end_date : datetime.date - End date. - country : str, optional - Country of the stock exchange. A list of available countries can be found - in the `holidays library documentation `_. - The default value is 'US'. + `start_date`: start date. + + `end_date`: end date. + + `country`: country of the stock exchange. Returns ------- - int - Number of weekends and holidays between the start and end date. + Number of weekends and holidays between the start and end date. """ if end_date > start_date: @@ -59,15 +59,14 @@ def get_pl(outputs: Outputs, leg: int | None = None) -> tuple[np.ndarray, np.nda Parameters ---------- - outputs : Outputs - Output data from a strategy calculation. - leg : int | None, optional - Index of a strategy leg. The default is None, which means the whole strategy. + `outputs`: output data from a strategy calculation. + + `leg`: index of a strategy leg. The default is `None`, which means the whole + strategy. Returns ------- - tuple[numpy.ndarray, numpy.ndarray] - Array of stock prices and array or profits/losses. + Array of stock prices and array or profits/losses. """ if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]: @@ -85,16 +84,16 @@ def pl_to_csv( Parameters ---------- - outputs : Outputs - Output data from a strategy calculation. - filename : str, optional - Name of the CSV file. The default is 'pl.csv'. - leg : int | None, optional - Index of a strategy leg. The default is None, which means the whole strategy. + `outputs`: output data from a strategy calculation. + + `filename`: name of the CSV file. + + `leg`: index of a strategy leg. The default is `None`, which means the whole + strategy. Returns ------- - None. + `None`. """ if outputs.data.profit.size > 0 and leg and leg < outputs.data.profit.shape[0]: diff --git a/pyproject.toml b/pyproject.toml index d81087f..cf63314 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "optionlab" -version = "1.4.2" +version = "1.4.3" description = "Python library for evaluating options trading strategies" authors = ["Roberto Gomes, PhD "] readme = "README.md" diff --git a/tests/test_core.py b/tests/test_core.py index 4c81ccb..76cf69d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ import pytest -from optionlab.models import Inputs, Outputs, BlackScholesModelInputs, LaplaceInputs +from optionlab import Inputs, Outputs, BlackScholesModelInputs, LaplaceInputs from optionlab.engine import run_strategy from optionlab.price_array import create_price_array from optionlab.black_scholes import get_bs_info diff --git a/tests/test_misc.py b/tests/test_misc.py index e6b90f9..40609b8 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -98,7 +98,7 @@ def test_cache_price_samples(): sample3 = create_price_array( inputs_data={ - "model": "normal", + "model": "black-scholes", "stock_price": stock_price, "volatility": volatility, "interest_rate": interest_rate, From 289f10087176fa11fbf2cc29f35f0699197bc10e Mon Sep 17 00:00:00 2001 From: rgaveiga Date: Fri, 25 Apr 2025 11:21:31 -0300 Subject: [PATCH 2/4] Added documentation using pdoc --- CHANGELOG.md | 1 + README.md | 66 +----- docs/optionlab.html | 468 +++++++++++++++++++++--------------------- docs/search.js | 2 +- optionlab.png | Bin 0 -> 29413 bytes optionlab/__init__.py | 11 +- tests/test_core.py | 6 +- 7 files changed, 254 insertions(+), 300 deletions(-) create mode 100644 optionlab.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e53304..5f649af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Removed `BaseLeg` from models.py. - Changed `StrategyType` to `StrategyLegType` in models.py for clarity. - Removed "normal" as an alias for "black-scholes" to avoid confusion with Bachelier model. +- Updated Readme.md. ## 1.4.2 (2025-01-25) diff --git a/README.md b/README.md index 13a4c79..498de44 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +![OptionLab](optionlab.png) + # OptionLab This package is a lightweight library written entirely in Python, designed to provide -quick evaluation of option strategies. +quick evaluation of option strategy ideas. The code produces various outputs, including the profit/loss profile of the strategy on a user-defined target date, the range of stock prices for which the strategy is @@ -10,15 +12,6 @@ each leg of the strategy using the Black-Sholes model, the resulting debit or cr trading account, the maximum and minimum returns within a specified lower and higher price range of the underlying asset, and an estimate of the strategy's probability of profit. -The probability of profit (PoP) on the user-defined target date for the strategy is calculated -by default using the Black-Scholes model. Alternatively, the user can provide an array of -underlying asset prices following a distribution other than the normal (e.g. Laplace) or -model other than the Black-Scholes model (e.g. Heston model) that will be used in the calculations. - -Despite the code having been developed with option strategies in mind, it can also be -used for strategies that combine options with stocks and/or take into account the -profits or losses of closed trades. - If you have any questions, corrections, comments or suggestions, just [drop a message](mailto:roberto.veiga@ufabc.edu.br). @@ -26,6 +19,9 @@ You can also reach me on [Linkedin](https://www.linkedin.com/in/roberto-gomes-ph follow me on [X](https://x.com/rgaveiga). When I have some free time, which is rare, I publish articles on [Medium](https://medium.com/@rgaveiga). +If you want to support this and other open source projects that I maintain, become a +[sponsor on Github](https://github.com/sponsors/rgaveiga). + ## Installation The easiest way to install **OptionLab** is using **pip**: @@ -34,55 +30,9 @@ The easiest way to install **OptionLab** is using **pip**: pip install optionlab ``` -## Basic usage - -The evaluation of a strategy is done by calling the `run_strategy` function provided by -the library. This function receives the input data either as a Python dictionary or an -`Inputs` object. For example, let's say we wanted to calculate the probability of profit -for naked calls on Apple stocks with maturity on December 17, 2021. The strategy setup -consisted of selling 100 175.00 strike calls for 1.15 each on November 22, 2021. - -```python -input_data = { - "stock_price": 164.04, - "start_date": "2021-11-22", - "target_date": "2021-12-17", - "volatility": 0.272, - "interest_rate": 0.0002, - "min_stock": 120, - "max_stock": 200, - "strategy": [ - { - "type": "call", - "strike": 175.0, - "premium": 1.15, - "n": 100, - "action":"sell" - } - ], -} -``` - -After defining the input data as a Python dictionary, we pass it to the `run_strategy` -function as shown below: - -```python -from optionlab import run_strategy, plot_pl - -out = run_strategy(input_data) - -print(out) - -plot_pl(out) -``` - -The variable `out` is an `Outputs` object that contains the results from the -calculations. By calling `print` with `out` as an argument, these results are -displayed on screen. The `plot_pl` function, in turn, takes an `Outputs` object as -its argument and plots the profit/loss diagram for the strategy. +## Documentation -Usage examples for a number of popular options trading strategies can be found in the -Jupyter notebooks in the **examples** directory. +You can access the API documentation for **OptionLab** on the [project's GitHub Pages site](https://rgaveiga.github.io/optionlab). ## Contributions diff --git a/docs/optionlab.html b/docs/optionlab.html index c11ceb0..155e707 100644 --- a/docs/optionlab.html +++ b/docs/optionlab.html @@ -83,12 +83,13 @@

  • an estimate of the strategy's probability of profit.

  • -

    The probability of profit (PoP) of the strategy on the user-defined target date
    -is calculated by default using the Black-Scholes model. The user can alternatively -provide an array of underlying asset prices obtained elsewhere (e.g. from the -Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) +

    The probability of profit (PoP) of the strategy on the user-defined target date +is calculated analytically by default using the Black-Scholes model. Alternatively, +the user can provide an array of terminal underlying asset prices obtained from +other sources (e.g., the Heston model, a Laplace distribution, or a Machine Learning/Deep Learning model) to be used in the calculations instead of the Black-Scholes model. This allows -OptionLab to function as a calculator using custom models.

    +OptionLab to function as a calculator that supports a variety of pricing +models.

    An advanced feature of OptionLab that provides great flexibility in building complex dynamic strategies is the ability to include previously created positions @@ -239,238 +240,239 @@

    Examples

    22 23- an estimate of the strategy's probability of profit. 24 - 25The probability of profit (PoP) of the strategy on the user-defined target date - 26is calculated by default using the Black-Scholes model. The user can alternatively - 27provide an array of underlying asset prices obtained elsewhere (e.g. from the - 28Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) + 25The probability of profit (PoP) of the strategy on the user-defined target date + 26is calculated analytically by default using the Black-Scholes model. Alternatively, + 27the user can provide an array of terminal underlying asset prices obtained from + 28other sources (e.g., the Heston model, a Laplace distribution, or a Machine Learning/Deep Learning model) 29to be used in the calculations instead of the Black-Scholes model. This allows - 30**OptionLab** to function as a calculator using custom models. - 31 - 32An advanced feature of **OptionLab** that provides great flexibility in building - 33complex dynamic strategies is the ability to include previously created positions - 34as legs in a new strategy. Popular strategies that can benefit from this feature - 35include the Wheel and Covered Call strategies. - 36 - 37## OptionLab is not... - 38 - 39... a platform for direct order execution. This capability has not been and - 40probably will not be implemented. - 41 - 42Backtesting and trade simulation using Monte Carlo have also not (yet) been - 43implemented in the API. - 44 - 45That being said, nothing prevents **OptionLab** from being integrated into an - 46options quant trader's workflow alongside other tools. - 47 - 48## Installation - 49 - 50The easiest way to install **OptionLab** is using **pip**: - 51 - 52``` - 53pip install optionlab - 54``` - 55 - 56## Quickstart - 57 - 58**OptionLab** is designed with ease of use in mind. An options strategy can be - 59defined and evaluated with just a few lines of Python code. The API is streamlined, - 60and the learning curve is minimal. - 61 - 62The evaluation of a strategy is done by calling the `optionlab.engine.run_strategy` - 63function provided by the library. This function receives the input data either - 64as a dictionary or an `optionlab.models.Inputs` object. - 65 - 66For example, let's say we wanted to calculate the probability of profit for naked - 67calls on Apple stocks expiring on December 17, 2021. The strategy setup consisted - 68of selling 100 175.00 strike calls for 1.15 each on November 22, 2021. - 69 - 70The input data for this strategy can be provided in a dictionary as follows: - 71 - 72```python - 73input_data = { - 74 "stock_price": 164.04, - 75 "start_date": "2021-11-22", - 76 "target_date": "2021-12-17", - 77 "volatility": 0.272, - 78 "interest_rate": 0.0002, - 79 "min_stock": 120, - 80 "max_stock": 200, - 81 "strategy": [ - 82 { - 83 "type": "call", - 84 "strike": 175.0, - 85 "premium": 1.15, - 86 "n": 100, - 87 "action":"sell" - 88 } - 89 ], - 90} - 91``` - 92 - 93Alternatively, the input data could be defined as the `optionlab.models.Inputs` - 94object below: - 95 - 96```python - 97from optionlab import Inputs - 98 - 99input_data = Inputs( -100 stock_price = 164.04, -101 start_date = "2021-11-22", -102 target_date = "2021-12-17", -103 volatility = 0.272, -104 interest_rate = 0.0002, -105 min_stock = 120, -106 max_stock = 200, -107 strategy = [ -108 { -109 "type": "call", -110 "strike": 175.0, -111 "premium": 1.15, -112 "n": 100, -113 "action":"sell" -114 } -115 ], -116) -117``` -118 -119In both cases, the strategy itself is a list of dictionaries, where each dictionary -120defines a leg in the strategy. The fields in a leg, depending on the type of the -121leg, are described in `optionlab.models.Stock`, `optionlab.models.Option`, and -122`optionlab.models.ClosedPosition`. -123 -124After defining the input data, we pass it to the `run_strategy` function as shown -125below: -126 -127```python -128from optionlab import run_strategy, plot_pl -129 -130out = run_strategy(input_data) -131 -132print(out) -133 -134plot_pl(out) -135``` -136 -137The variable `out` is an `optionlab.models.Outputs` object that contains the -138results from the calculations. By calling `print` with `out` as an argument, -139these results are displayed on screen. -140 -141The `optionlab.plot.plot_pl` function, in turn, takes an `optionlab.models.Outputs` -142object as its argument and plots the profit/loss diagram for the strategy. -143 -144## Examples -145 -146Examples for a number of popular options trading strategies can be found as -147Jupyter notebooks in the [examples](https://github.com/rgaveiga/optionlab/tree/main/examples) -148directory. -149""" -150 -151from .models import ( -152 Inputs, -153 OptionType, -154 Option, -155 Outputs, -156 ClosedPosition, -157 ArrayInputs, -158 TheoreticalModelInputs, -159 BlackScholesModelInputs, -160 LaplaceInputs, -161 BlackScholesInfo, -162 TheoreticalModel, -163 FloatOrNdarray, -164 StrategyLeg, -165 StrategyLegType, -166 Stock, -167 Action, -168) -169from .black_scholes import ( -170 get_itm_probability, -171 get_implied_vol, -172 get_option_price, -173 get_d1, -174 get_d2, -175 get_bs_info, -176 get_vega, -177 get_delta, -178 get_gamma, -179 get_theta, -180 get_rho, -181) -182from .engine import run_strategy -183from .plot import plot_pl -184from .price_array import create_price_array -185from .support import ( -186 get_pl_profile, -187 get_pl_profile_stock, -188 get_pl_profile_bs, -189 create_price_seq, -190 get_pop, -191) -192from .utils import ( -193 get_nonbusiness_days, -194 get_pl, -195 pl_to_csv, -196) -197 + 30**OptionLab** to function as a calculator that supports a variety of pricing + 31models. + 32 + 33An advanced feature of **OptionLab** that provides great flexibility in building + 34complex dynamic strategies is the ability to include previously created positions + 35as legs in a new strategy. Popular strategies that can benefit from this feature + 36include the Wheel and Covered Call strategies. + 37 + 38## OptionLab is not... + 39 + 40... a platform for direct order execution. This capability has not been and + 41probably will not be implemented. + 42 + 43Backtesting and trade simulation using Monte Carlo have also not (yet) been + 44implemented in the API. + 45 + 46That being said, nothing prevents **OptionLab** from being integrated into an + 47options quant trader's workflow alongside other tools. + 48 + 49## Installation + 50 + 51The easiest way to install **OptionLab** is using **pip**: + 52 + 53``` + 54pip install optionlab + 55``` + 56 + 57## Quickstart + 58 + 59**OptionLab** is designed with ease of use in mind. An options strategy can be + 60defined and evaluated with just a few lines of Python code. The API is streamlined, + 61and the learning curve is minimal. + 62 + 63The evaluation of a strategy is done by calling the `optionlab.engine.run_strategy` + 64function provided by the library. This function receives the input data either + 65as a dictionary or an `optionlab.models.Inputs` object. + 66 + 67For example, let's say we wanted to calculate the probability of profit for naked + 68calls on Apple stocks expiring on December 17, 2021. The strategy setup consisted + 69of selling 100 175.00 strike calls for 1.15 each on November 22, 2021. + 70 + 71The input data for this strategy can be provided in a dictionary as follows: + 72 + 73```python + 74input_data = { + 75 "stock_price": 164.04, + 76 "start_date": "2021-11-22", + 77 "target_date": "2021-12-17", + 78 "volatility": 0.272, + 79 "interest_rate": 0.0002, + 80 "min_stock": 120, + 81 "max_stock": 200, + 82 "strategy": [ + 83 { + 84 "type": "call", + 85 "strike": 175.0, + 86 "premium": 1.15, + 87 "n": 100, + 88 "action":"sell" + 89 } + 90 ], + 91} + 92``` + 93 + 94Alternatively, the input data could be defined as the `optionlab.models.Inputs` + 95object below: + 96 + 97```python + 98from optionlab import Inputs + 99 +100input_data = Inputs( +101 stock_price = 164.04, +102 start_date = "2021-11-22", +103 target_date = "2021-12-17", +104 volatility = 0.272, +105 interest_rate = 0.0002, +106 min_stock = 120, +107 max_stock = 200, +108 strategy = [ +109 { +110 "type": "call", +111 "strike": 175.0, +112 "premium": 1.15, +113 "n": 100, +114 "action":"sell" +115 } +116 ], +117) +118``` +119 +120In both cases, the strategy itself is a list of dictionaries, where each dictionary +121defines a leg in the strategy. The fields in a leg, depending on the type of the +122leg, are described in `optionlab.models.Stock`, `optionlab.models.Option`, and +123`optionlab.models.ClosedPosition`. +124 +125After defining the input data, we pass it to the `run_strategy` function as shown +126below: +127 +128```python +129from optionlab import run_strategy, plot_pl +130 +131out = run_strategy(input_data) +132 +133print(out) +134 +135plot_pl(out) +136``` +137 +138The variable `out` is an `optionlab.models.Outputs` object that contains the +139results from the calculations. By calling `print` with `out` as an argument, +140these results are displayed on screen. +141 +142The `optionlab.plot.plot_pl` function, in turn, takes an `optionlab.models.Outputs` +143object as its argument and plots the profit/loss diagram for the strategy. +144 +145## Examples +146 +147Examples for a number of popular options trading strategies can be found as +148Jupyter notebooks in the [examples](https://github.com/rgaveiga/optionlab/tree/main/examples) +149directory. +150""" +151 +152from .models import ( +153 Inputs, +154 OptionType, +155 Option, +156 Outputs, +157 ClosedPosition, +158 ArrayInputs, +159 TheoreticalModelInputs, +160 BlackScholesModelInputs, +161 LaplaceInputs, +162 BlackScholesInfo, +163 TheoreticalModel, +164 FloatOrNdarray, +165 StrategyLeg, +166 StrategyLegType, +167 Stock, +168 Action, +169) +170from .black_scholes import ( +171 get_itm_probability, +172 get_implied_vol, +173 get_option_price, +174 get_d1, +175 get_d2, +176 get_bs_info, +177 get_vega, +178 get_delta, +179 get_gamma, +180 get_theta, +181 get_rho, +182) +183from .engine import run_strategy +184from .plot import plot_pl +185from .price_array import create_price_array +186from .support import ( +187 get_pl_profile, +188 get_pl_profile_stock, +189 get_pl_profile_bs, +190 create_price_seq, +191 get_pop, +192) +193from .utils import ( +194 get_nonbusiness_days, +195 get_pl, +196 pl_to_csv, +197) 198 -199VERSION = "1.4.3" -200 -201__docformat__ = "markdown" -202__version__ = VERSION -203 +199 +200VERSION = "1.4.3" +201 +202__docformat__ = "markdown" +203__version__ = VERSION 204 -205ALL = ( -206 # models -207 "Inputs", -208 "OptionType", -209 "Option", -210 "Outputs", -211 "ClosedPosition", -212 "ArrayInputs", -213 "TheoreticalModelInputs", -214 "BlackScholesModelInputs", -215 "LaplaceInputs", -216 "BlackScholesInfo", -217 "TheoreticalModel", -218 "FloatOrNdarray", -219 "StrategyLeg", -220 "StrategyLegType", -221 "Stock", -222 "Action", -223 # engine -224 "run_strategy", -225 # support -226 "get_pl_profile", -227 "get_pl_profile_stock", -228 "get_pl_profile_bs", -229 "create_price_seq", -230 "get_pop", -231 # black_scholes -232 "get_d1", -233 "get_d2", -234 "get_option_price", -235 "get_itm_probability", -236 "get_implied_vol", -237 "get_bs_info", -238 "get_vega", -239 "get_delta", -240 "get_gamma", -241 "get_theta", -242 "get_rho", -243 # plot -244 "plot_pl", -245 # price_array -246 "create_price_array", -247 # utils -248 "get_nonbusiness_days", -249 "get_pl", -250 "pl_to_csv", -251) -252"""@private""" -253 +205 +206ALL = ( +207 # models +208 "Inputs", +209 "OptionType", +210 "Option", +211 "Outputs", +212 "ClosedPosition", +213 "ArrayInputs", +214 "TheoreticalModelInputs", +215 "BlackScholesModelInputs", +216 "LaplaceInputs", +217 "BlackScholesInfo", +218 "TheoreticalModel", +219 "FloatOrNdarray", +220 "StrategyLeg", +221 "StrategyLegType", +222 "Stock", +223 "Action", +224 # engine +225 "run_strategy", +226 # support +227 "get_pl_profile", +228 "get_pl_profile_stock", +229 "get_pl_profile_bs", +230 "create_price_seq", +231 "get_pop", +232 # black_scholes +233 "get_d1", +234 "get_d2", +235 "get_option_price", +236 "get_itm_probability", +237 "get_implied_vol", +238 "get_bs_info", +239 "get_vega", +240 "get_delta", +241 "get_gamma", +242 "get_theta", +243 "get_rho", +244 # plot +245 "plot_pl", +246 # price_array +247 "create_price_array", +248 # utils +249 "get_nonbusiness_days", +250 "get_pl", +251 "pl_to_csv", +252) +253"""@private""" 254 -255def __dir__() -> "list[str]": -256 return list(ALL) +255 +256def __dir__() -> "list[str]": +257 return list(ALL) diff --git a/docs/search.js b/docs/search.js index e3ff565..435f68a 100644 --- a/docs/search.js +++ b/docs/search.js @@ -1,6 +1,6 @@ window.pdocSearch = (function(){ /** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oOptionLab is...\n\n

    ... a Python library designed as a research tool for quickly evaluating options \nstrategy ideas. It is intended for a wide range of users, from individuals learning \nabout options trading to developers of quantitative strategies.

    \n\n

    OptionLab calculations can produce a number of useful outputs:

    \n\n
      \n
    • the profit/loss profile of the strategy on a user-defined target date,

    • \n
    • the range of stock prices for which the strategy is profitable,

    • \n
    • the Greeks associated with each leg of the strategy,

    • \n
    • the resulting debit or credit on the trading account,

    • \n
    • the maximum and minimum returns within a specified lower and higher price range \nof the underlying asset,

    • \n
    • the expected profit and expected loss, and

    • \n
    • an estimate of the strategy's probability of profit.

    • \n
    \n\n

    The probability of profit (PoP) of the strategy on the user-defined target date
    \nis calculated by default using the Black-Scholes model. The user can alternatively \nprovide an array of underlying asset prices obtained elsewhere (e.g. from the \nHeston model, a Laplace distribution or a Machine Learning/Deep Learning model) \nto be used in the calculations instead of the Black-Scholes model. This allows \nOptionLab to function as a calculator using custom models.

    \n\n

    An advanced feature of OptionLab that provides great flexibility in building \ncomplex dynamic strategies is the ability to include previously created positions \nas legs in a new strategy. Popular strategies that can benefit from this feature \ninclude the Wheel and Covered Call strategies.

    \n\n

    OptionLab is not...

    \n\n

    ... a platform for direct order execution. This capability has not been and \nprobably will not be implemented.

    \n\n

    Backtesting and trade simulation using Monte Carlo have also not (yet) been \nimplemented in the API.

    \n\n

    That being said, nothing prevents OptionLab from being integrated into an \noptions quant trader's workflow alongside other tools.

    \n\n

    Installation

    \n\n

    The easiest way to install OptionLab is using pip:

    \n\n
    pip install optionlab\n
    \n\n

    Quickstart

    \n\n

    OptionLab is designed with ease of use in mind. An options strategy can be \ndefined and evaluated with just a few lines of Python code. The API is streamlined, \nand the learning curve is minimal.

    \n\n

    The evaluation of a strategy is done by calling the optionlab.engine.run_strategy \nfunction provided by the library. This function receives the input data either \nas a dictionary or an optionlab.models.Inputs object.

    \n\n

    For example, let's say we wanted to calculate the probability of profit for naked \ncalls on Apple stocks expiring on December 17, 2021. The strategy setup consisted \nof selling 100 175.00 strike calls for 1.15 each on November 22, 2021.

    \n\n

    The input data for this strategy can be provided in a dictionary as follows:

    \n\n
    \n
    input_data = {\n    "stock_price": 164.04,\n    "start_date": "2021-11-22",\n    "target_date": "2021-12-17",\n    "volatility": 0.272,\n    "interest_rate": 0.0002,\n    "min_stock": 120,\n    "max_stock": 200,\n    "strategy": [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n}\n
    \n
    \n\n

    Alternatively, the input data could be defined as the optionlab.models.Inputs \nobject below:

    \n\n
    \n
    from optionlab import Inputs\n\ninput_data = Inputs(\n    stock_price = 164.04,\n    start_date = "2021-11-22",\n    target_date = "2021-12-17",\n    volatility = 0.272,\n    interest_rate = 0.0002,\n    min_stock = 120,\n    max_stock = 200,\n    strategy = [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n)\n
    \n
    \n\n

    In both cases, the strategy itself is a list of dictionaries, where each dictionary\ndefines a leg in the strategy. The fields in a leg, depending on the type of the\nleg, are described in optionlab.models.Stock, optionlab.models.Option, and\noptionlab.models.ClosedPosition.

    \n\n

    After defining the input data, we pass it to the run_strategy function as shown \nbelow:

    \n\n
    \n
    from optionlab import run_strategy, plot_pl\n\nout = run_strategy(input_data)\n\nprint(out)\n\nplot_pl(out)\n
    \n
    \n\n

    The variable out is an optionlab.models.Outputs object that contains the \nresults from the calculations. By calling print with out as an argument, \nthese results are displayed on screen.

    \n\n

    The optionlab.plot.plot_pl function, in turn, takes an optionlab.models.Outputs \nobject as its argument and plots the profit/loss diagram for the strategy.

    \n\n

    Examples

    \n\n

    Examples for a number of popular options trading strategies can be found as \nJupyter notebooks in the examples \ndirectory.

    \n"}, {"fullname": "optionlab.VERSION", "modulename": "optionlab", "qualname": "VERSION", "kind": "variable", "doc": "

    \n", "default_value": "'1.4.3'"}, {"fullname": "optionlab.black_scholes", "modulename": "optionlab.black_scholes", "kind": "module", "doc": "

    This module defines functions that calculate quantities, such as option prices\nand the Greeks, related to the Black-Scholes model.

    \n"}, {"fullname": "optionlab.black_scholes.get_bs_info", "modulename": "optionlab.black_scholes", "qualname": "get_bs_info", "kind": "function", "doc": "

    Provides information about call and put options calculated using the Black-Scholes\nformula.

    \n\n

    Parameters

    \n\n

    s: stock price.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Information calculated using the Black-Scholes formula.

    \n", "signature": "(\ts: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\ty: float = 0.0) -> optionlab.models.BlackScholesInfo:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_option_price", "modulename": "optionlab.black_scholes", "qualname": "get_option_price", "kind": "function", "doc": "

    Returns the price of an option.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualize risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option price(s).

    \n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_delta", "modulename": "optionlab.black_scholes", "qualname": "get_delta", "kind": "function", "doc": "

    Returns the option's Greek Delta.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Delta.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\td1: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_gamma", "modulename": "optionlab.black_scholes", "qualname": "get_gamma", "kind": "function", "doc": "

    Returns the option's Greek Gamma.

    \n\n

    Parameters

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    vol: annualized volatitily.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    Option's Greek Gamma.

    \n", "signature": "(\ts0: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_theta", "modulename": "optionlab.black_scholes", "qualname": "get_theta", "kind": "function", "doc": "

    Returns the option's Greek Theta.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Theta.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_vega", "modulename": "optionlab.black_scholes", "qualname": "get_vega", "kind": "function", "doc": "

    Returns the option's Greek Vega.

    \n\n

    Parameters

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Vega.

    \n", "signature": "(\ts0: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_rho", "modulename": "optionlab.black_scholes", "qualname": "get_rho", "kind": "function", "doc": "

    Returns the option's Greek Rho.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    Returns

    \n\n

    Option's Greek Rho.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td2: float | numpy.ndarray) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d1", "modulename": "optionlab.black_scholes", "qualname": "get_d1", "kind": "function", "doc": "

    Returns d1 used in Black-Scholes formula.

    \n\n

    Parameters

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility(ies).

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    d1 in Black-Scholes formula.

    \n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d2", "modulename": "optionlab.black_scholes", "qualname": "get_d2", "kind": "function", "doc": "

    Returns d2 used in Black-Scholes formula.

    \n\n

    Parameters

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility(ies).

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    d2 in Black-Scholes formula.

    \n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_implied_vol", "modulename": "optionlab.black_scholes", "qualname": "get_implied_vol", "kind": "function", "doc": "

    Returns the implied volatility of an option.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    oprice: market price of an option.

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    x: strike price.

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's implied volatility.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\toprice: float,\ts0: float,\tx: float,\tr: float,\tyears_to_maturity: float,\ty: float = 0.0) -> float:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_itm_probability", "modulename": "optionlab.black_scholes", "qualname": "get_itm_probability", "kind": "function", "doc": "

    Returns the probability(ies) that the option(s) will expire in-the-money (ITM).

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Probability(ies) that the option(s) will expire in-the-money (ITM).

    \n", "signature": "(\toption_type: Literal['call', 'put'],\td2: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.engine", "modulename": "optionlab.engine", "kind": "module", "doc": "

    This module defines the run_strategy function.

    \n\n

    Given input data provided as either an optionlab.models.Inputs object or a dictionary,\nrun_strategy returns the results of an options strategy calculation (e.g., the\nprobability of profit on the target date) as an optionlab.models.Outputs object.

    \n"}, {"fullname": "optionlab.engine.run_strategy", "modulename": "optionlab.engine", "qualname": "run_strategy", "kind": "function", "doc": "

    Runs the calculation for a strategy.

    \n\n

    Parameters

    \n\n

    inputs_data: input data used in the strategy calculation.

    \n\n

    Returns

    \n\n

    Output data from the strategy calculation.

    \n", "signature": "(inputs_data: optionlab.models.Inputs | dict) -> optionlab.models.Outputs:", "funcdef": "def"}, {"fullname": "optionlab.models", "modulename": "optionlab.models", "kind": "module", "doc": "

    This module primarily implements Pydantic models that represent inputs and outputs \nof strategy calculations. It also implements constants and custom types.

    \n\n

    From the user's point of view, the two most important classes that they will use \nto provide input and subsequently process calculation results are Inputs and \nOutputs, respectively.

    \n"}, {"fullname": "optionlab.models.OptionType", "modulename": "optionlab.models", "qualname": "OptionType", "kind": "variable", "doc": "

    Option type in a strategy leg.

    \n", "default_value": "typing.Literal['call', 'put']"}, {"fullname": "optionlab.models.Action", "modulename": "optionlab.models", "qualname": "Action", "kind": "variable", "doc": "

    Action taken in in a strategy leg.

    \n", "default_value": "typing.Literal['buy', 'sell']"}, {"fullname": "optionlab.models.StrategyLegType", "modulename": "optionlab.models", "qualname": "StrategyLegType", "kind": "variable", "doc": "

    Type of strategy leg.

    \n", "default_value": "typing.Union[typing.Literal['stock'], typing.Literal['call', 'put'], typing.Literal['closed']]"}, {"fullname": "optionlab.models.TheoreticalModel", "modulename": "optionlab.models", "qualname": "TheoreticalModel", "kind": "variable", "doc": "

    Theoretical model used in probability of profit (PoP) calculations.

    \n", "default_value": "typing.Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Range", "modulename": "optionlab.models", "qualname": "Range", "kind": "variable", "doc": "

    Range boundaries.

    \n", "default_value": "tuple[float, float]"}, {"fullname": "optionlab.models.FloatOrNdarray", "modulename": "optionlab.models", "qualname": "FloatOrNdarray", "kind": "variable", "doc": "

    Float or numpy array custom type.

    \n", "default_value": "float | numpy.ndarray"}, {"fullname": "optionlab.models.Stock", "modulename": "optionlab.models", "qualname": "Stock", "kind": "class", "doc": "

    Defines the attributes of a stock leg in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Stock.type", "modulename": "optionlab.models", "qualname": "Stock.type", "kind": "variable", "doc": "

    It must be 'stock'.

    \n", "annotation": ": Literal['stock']"}, {"fullname": "optionlab.models.Stock.n", "modulename": "optionlab.models", "qualname": "Stock.n", "kind": "variable", "doc": "

    Number of shares.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Stock.action", "modulename": "optionlab.models", "qualname": "Stock.action", "kind": "variable", "doc": "

    Either 'buy' or 'sell'.

    \n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Stock.prev_pos", "modulename": "optionlab.models", "qualname": "Stock.prev_pos", "kind": "variable", "doc": "

    Stock price effectively paid or received in a previously opened position.

    \n\n
      \n
    • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current stock price.

    • \n
    • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

    • \n
    \n\n

    The default is None, which means this stock position is not a previously \nopened position.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Stock.model_config", "modulename": "optionlab.models", "qualname": "Stock.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Stock.model_fields", "modulename": "optionlab.models", "qualname": "Stock.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['stock'], required=False, default='stock'), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Stock.model_computed_fields", "modulename": "optionlab.models", "qualname": "Stock.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Option", "modulename": "optionlab.models", "qualname": "Option", "kind": "class", "doc": "

    Defines the attributes of an option leg in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Option.type", "modulename": "optionlab.models", "qualname": "Option.type", "kind": "variable", "doc": "

    Either 'call' or 'put'.

    \n", "annotation": ": Literal['call', 'put']"}, {"fullname": "optionlab.models.Option.strike", "modulename": "optionlab.models", "qualname": "Option.strike", "kind": "variable", "doc": "

    Strike price.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.premium", "modulename": "optionlab.models", "qualname": "Option.premium", "kind": "variable", "doc": "

    Option premium.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.action", "modulename": "optionlab.models", "qualname": "Option.action", "kind": "variable", "doc": "

    Either 'buy' or 'sell'.

    \n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Option.n", "modulename": "optionlab.models", "qualname": "Option.n", "kind": "variable", "doc": "

    Number of options.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Option.prev_pos", "modulename": "optionlab.models", "qualname": "Option.prev_pos", "kind": "variable", "doc": "

    Premium effectively paid or received in a previously opened position.

    \n\n
      \n
    • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current price of the option.

    • \n
    • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

    • \n
    \n\n

    The default is None, which means this option position is not a previously \nopened position.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Option.expiration", "modulename": "optionlab.models", "qualname": "Option.expiration", "kind": "variable", "doc": "

    Expiration date or number of days remaining to expiration.

    \n\n

    The default is None, which means the expiration is the same as Inputs.target_date \nor Inputs.days_to_target_date.

    \n", "annotation": ": datetime.date | int | None"}, {"fullname": "optionlab.models.Option.model_config", "modulename": "optionlab.models", "qualname": "Option.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Option.model_fields", "modulename": "optionlab.models", "qualname": "Option.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['call', 'put'], required=True), 'strike': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'premium': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expiration': FieldInfo(annotation=Union[date, int, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Option.model_computed_fields", "modulename": "optionlab.models", "qualname": "Option.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition", "modulename": "optionlab.models", "qualname": "ClosedPosition", "kind": "class", "doc": "

    Defines the attributes of a previously closed position in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ClosedPosition.type", "modulename": "optionlab.models", "qualname": "ClosedPosition.type", "kind": "variable", "doc": "

    It must be 'closed'.

    \n", "annotation": ": Literal['closed']"}, {"fullname": "optionlab.models.ClosedPosition.prev_pos", "modulename": "optionlab.models", "qualname": "ClosedPosition.prev_pos", "kind": "variable", "doc": "

    The total amount of the closed position.

    \n\n
      \n
    • If positive, it resulted in a profit.

    • \n
    • If negative, it incurred a loss.

    • \n
    \n\n

    This amount will be added to the payoff and taken into account in the strategy \ncalculations.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.ClosedPosition.model_config", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition.model_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['closed'], required=False, default='closed'), 'prev_pos': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.ClosedPosition.model_computed_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.StrategyLeg", "modulename": "optionlab.models", "qualname": "StrategyLeg", "kind": "variable", "doc": "

    Leg in a strategy.

    \n", "default_value": "optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition"}, {"fullname": "optionlab.models.TheoreticalModelInputs", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs", "kind": "class", "doc": "

    Inputs for calculations, such as the probability of profit (PoP).

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.TheoreticalModelInputs.stock_price", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.stock_price", "kind": "variable", "doc": "

    Stock price.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.volatility", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.volatility", "kind": "variable", "doc": "

    Annualized volatility of the underlying asset.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.years_to_target_date", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.years_to_target_date", "kind": "variable", "doc": "

    Time remaining until target date, in years.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_config", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)])}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs", "kind": "class", "doc": "

    Defines the input data for the calculations using the Black-Scholes model.

    \n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model", "kind": "variable", "doc": "

    It must be 'black-scholes'.

    \n", "annotation": ": Literal['black-scholes']"}, {"fullname": "optionlab.models.BlackScholesModelInputs.interest_rate", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.interest_rate", "kind": "variable", "doc": "

    Annualized risk-free interest rate.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.dividend_yield", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.dividend_yield", "kind": "variable", "doc": "

    Annualized dividend yield.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['black-scholes'], required=False, default='black-scholes'), 'interest_rate': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0), Le(le=1.0)])}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs", "modulename": "optionlab.models", "qualname": "LaplaceInputs", "kind": "class", "doc": "

    Defines the input data for the calculations using a log-Laplace distribution of\nstock prices.

    \n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.LaplaceInputs.model", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model", "kind": "variable", "doc": "

    It must be 'laplace'.

    \n", "annotation": ": Literal['laplace']"}, {"fullname": "optionlab.models.LaplaceInputs.mu", "modulename": "optionlab.models", "qualname": "LaplaceInputs.mu", "kind": "variable", "doc": "

    Annualized return of the underlying asset.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.LaplaceInputs.model_config", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs.model_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['laplace'], required=False, default='laplace'), 'mu': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.LaplaceInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ArrayInputs", "modulename": "optionlab.models", "qualname": "ArrayInputs", "kind": "class", "doc": "

    Defines the input data for the calculations when using an array of strategy\nreturns.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ArrayInputs.model", "modulename": "optionlab.models", "qualname": "ArrayInputs.model", "kind": "variable", "doc": "

    It must be 'array'.

    \n", "annotation": ": Literal['array']"}, {"fullname": "optionlab.models.ArrayInputs.array", "modulename": "optionlab.models", "qualname": "ArrayInputs.array", "kind": "variable", "doc": "

    Array of strategy returns.

    \n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.ArrayInputs.model_config", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.ArrayInputs.model_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'model': FieldInfo(annotation=Literal['array'], required=False, default='array'), 'array': FieldInfo(annotation=ndarray, required=True)}"}, {"fullname": "optionlab.models.ArrayInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Inputs", "modulename": "optionlab.models", "qualname": "Inputs", "kind": "class", "doc": "

    Defines the input data for a strategy calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Inputs.stock_price", "modulename": "optionlab.models", "qualname": "Inputs.stock_price", "kind": "variable", "doc": "

    Spot price of the underlying.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.volatility", "modulename": "optionlab.models", "qualname": "Inputs.volatility", "kind": "variable", "doc": "

    Annualized volatility.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.interest_rate", "modulename": "optionlab.models", "qualname": "Inputs.interest_rate", "kind": "variable", "doc": "

    Annualized risk-free interest rate.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.min_stock", "modulename": "optionlab.models", "qualname": "Inputs.min_stock", "kind": "variable", "doc": "

    Minimum value of the stock in the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.max_stock", "modulename": "optionlab.models", "qualname": "Inputs.max_stock", "kind": "variable", "doc": "

    Maximum value of the stock in the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.strategy", "modulename": "optionlab.models", "qualname": "Inputs.strategy", "kind": "variable", "doc": "

    A list of strategy legs.

    \n", "annotation": ": list[optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition]"}, {"fullname": "optionlab.models.Inputs.dividend_yield", "modulename": "optionlab.models", "qualname": "Inputs.dividend_yield", "kind": "variable", "doc": "

    Annualized dividend yield.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.profit_target", "modulename": "optionlab.models", "qualname": "Inputs.profit_target", "kind": "variable", "doc": "

    Target profit level.

    \n\n

    The default is None, which means it is not calculated.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.loss_limit", "modulename": "optionlab.models", "qualname": "Inputs.loss_limit", "kind": "variable", "doc": "

    Limit loss level.

    \n\n

    The default is None, which means it is not calculated.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.opt_commission", "modulename": "optionlab.models", "qualname": "Inputs.opt_commission", "kind": "variable", "doc": "

    Brokerage commission for options transactions.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.stock_commission", "modulename": "optionlab.models", "qualname": "Inputs.stock_commission", "kind": "variable", "doc": "

    Brokerage commission for stocks transactions.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.discard_nonbusiness_days", "modulename": "optionlab.models", "qualname": "Inputs.discard_nonbusiness_days", "kind": "variable", "doc": "

    Discards weekends and holidays when counting the number of days between\ntwo dates.

    \n\n

    The default is True.

    \n", "annotation": ": bool"}, {"fullname": "optionlab.models.Inputs.business_days_in_year", "modulename": "optionlab.models", "qualname": "Inputs.business_days_in_year", "kind": "variable", "doc": "

    Number of business days in a year.

    \n\n

    The default is 252.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.country", "modulename": "optionlab.models", "qualname": "Inputs.country", "kind": "variable", "doc": "

    Country whose holidays will be counted if discard_nonbusinessdays is\nset to True.

    \n\n

    The default is 'US'.

    \n", "annotation": ": str"}, {"fullname": "optionlab.models.Inputs.start_date", "modulename": "optionlab.models", "qualname": "Inputs.start_date", "kind": "variable", "doc": "

    Start date in the calculations.

    \n\n

    If not provided, days_to_target_date must be provided.

    \n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.target_date", "modulename": "optionlab.models", "qualname": "Inputs.target_date", "kind": "variable", "doc": "

    Target date in the calculations.

    \n\n

    If not provided, days_to_target_date must be provided.

    \n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.days_to_target_date", "modulename": "optionlab.models", "qualname": "Inputs.days_to_target_date", "kind": "variable", "doc": "

    Days remaining to the target date.

    \n\n

    If not provided, start_date and target_date must be provided.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.model", "modulename": "optionlab.models", "qualname": "Inputs.model", "kind": "variable", "doc": "

    Theoretical model used in the calculations of probability of profit.

    \n\n

    It can be 'black-scholes' or 'array'.

    \n", "annotation": ": Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Inputs.array", "modulename": "optionlab.models", "qualname": "Inputs.array", "kind": "variable", "doc": "

    Array of terminal stock prices.

    \n\n

    The default is an empty array.

    \n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.Inputs.model_config", "modulename": "optionlab.models", "qualname": "Inputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.Inputs.model_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'interest_rate': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'min_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'max_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'strategy': FieldInfo(annotation=list[Union[Stock, Option, ClosedPosition]], required=True, metadata=[MinLen(min_length=1)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'profit_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'loss_limit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opt_commission': FieldInfo(annotation=float, required=False, default=0.0), 'stock_commission': FieldInfo(annotation=float, required=False, default=0.0), 'discard_nonbusiness_days': FieldInfo(annotation=bool, required=False, default=True), 'business_days_in_year': FieldInfo(annotation=int, required=False, default=252), 'country': FieldInfo(annotation=str, required=False, default='US'), 'start_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'target_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'days_to_target_date': FieldInfo(annotation=int, required=False, default=0, metadata=[Ge(ge=0)]), 'model': FieldInfo(annotation=Literal['black-scholes', 'array'], required=False, default='black-scholes'), 'array': FieldInfo(annotation=ndarray, required=False, default_factory=init_empty_array)}"}, {"fullname": "optionlab.models.Inputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesInfo", "modulename": "optionlab.models", "qualname": "BlackScholesInfo", "kind": "class", "doc": "

    Defines the data returned by a calculation using the Black-Scholes model.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.BlackScholesInfo.call_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_price", "kind": "variable", "doc": "

    Price of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_price", "kind": "variable", "doc": "

    Price of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_delta", "kind": "variable", "doc": "

    Delta of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_delta", "kind": "variable", "doc": "

    Delta of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_theta", "kind": "variable", "doc": "

    Theta of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_theta", "kind": "variable", "doc": "

    Theta of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.gamma", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.gamma", "kind": "variable", "doc": "

    Gamma of an option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.vega", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.vega", "kind": "variable", "doc": "

    Vega of an option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_rho", "kind": "variable", "doc": "

    Rho of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_rho", "kind": "variable", "doc": "

    Rho of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_itm_prob", "kind": "variable", "doc": "

    Probability of expiring in-the-money probability of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_itm_prob", "kind": "variable", "doc": "

    Probability of expiring in-the-money of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'call_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'gamma': FieldInfo(annotation=Union[float, ndarray], required=True), 'vega': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True)}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs", "modulename": "optionlab.models", "qualname": "Outputs", "kind": "class", "doc": "

    Defines the output data from a strategy calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Outputs.probability_of_profit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit", "kind": "variable", "doc": "

    Probability of the strategy yielding at least $0.01.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_ranges", "kind": "variable", "doc": "

    A list of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least $0.01.

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.expected_profit", "modulename": "optionlab.models", "qualname": "Outputs.expected_profit", "kind": "variable", "doc": "

    Expected profit when the strategy is profitable.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.expected_loss", "modulename": "optionlab.models", "qualname": "Outputs.expected_loss", "kind": "variable", "doc": "

    Expected loss when the strategy is not profitable.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.per_leg_cost", "modulename": "optionlab.models", "qualname": "Outputs.per_leg_cost", "kind": "variable", "doc": "

    List of leg costs.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.strategy_cost", "modulename": "optionlab.models", "qualname": "Outputs.strategy_cost", "kind": "variable", "doc": "

    Total strategy cost.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.minimum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.minimum_return_in_the_domain", "kind": "variable", "doc": "

    Minimum return of the strategy within the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.maximum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.maximum_return_in_the_domain", "kind": "variable", "doc": "

    Maximum return of the strategy within the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.implied_volatility", "modulename": "optionlab.models", "qualname": "Outputs.implied_volatility", "kind": "variable", "doc": "

    List of implied volatilities, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.in_the_money_probability", "modulename": "optionlab.models", "qualname": "Outputs.in_the_money_probability", "kind": "variable", "doc": "

    List of probabilities of legs expiring in-the-money (ITM).

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.delta", "modulename": "optionlab.models", "qualname": "Outputs.delta", "kind": "variable", "doc": "

    List of Delta values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.gamma", "modulename": "optionlab.models", "qualname": "Outputs.gamma", "kind": "variable", "doc": "

    List of Gamma values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.theta", "modulename": "optionlab.models", "qualname": "Outputs.theta", "kind": "variable", "doc": "

    List of Theta values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.vega", "modulename": "optionlab.models", "qualname": "Outputs.vega", "kind": "variable", "doc": "

    List of Vega values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.rho", "modulename": "optionlab.models", "qualname": "Outputs.rho", "kind": "variable", "doc": "

    List of Rho values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.probability_of_profit_target", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit_target", "kind": "variable", "doc": "

    Probability of the strategy yielding at least the profit target.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_target_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_target_ranges", "kind": "variable", "doc": "

    List of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least the profit target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.probability_of_loss_limit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_loss_limit", "kind": "variable", "doc": "

    Probability of the strategy losing at least the loss limit.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.loss_limit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.loss_limit_ranges", "kind": "variable", "doc": "

    List of minimum and maximum stock prices defining ranges where the\nstrategy loses at least the loss limit.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.model_config", "modulename": "optionlab.models", "qualname": "Outputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs.model_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_profit': FieldInfo(annotation=float, required=True), 'profit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=True), 'expected_profit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_loss': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'per_leg_cost': FieldInfo(annotation=list[float], required=True), 'strategy_cost': FieldInfo(annotation=float, required=True), 'minimum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'maximum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'implied_volatility': FieldInfo(annotation=list[float], required=True), 'in_the_money_probability': FieldInfo(annotation=list[float], required=True), 'delta': FieldInfo(annotation=list[float], required=True), 'gamma': FieldInfo(annotation=list[float], required=True), 'theta': FieldInfo(annotation=list[float], required=True), 'vega': FieldInfo(annotation=list[float], required=True), 'rho': FieldInfo(annotation=list[float], required=True), 'probability_of_profit_target': FieldInfo(annotation=float, required=False, default=0.0), 'profit_target_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'probability_of_loss_limit': FieldInfo(annotation=float, required=False, default=0.0), 'loss_limit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'inputs': FieldInfo(annotation=Inputs, required=True), 'data': FieldInfo(annotation=EngineDataResults, required=True)}"}, {"fullname": "optionlab.models.Outputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs", "modulename": "optionlab.models", "qualname": "PoPOutputs", "kind": "class", "doc": "

    Defines the output data from a probability of profit (PoP) calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_reaching_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_reaching_target", "kind": "variable", "doc": "

    Probability that the strategy return will be equal or greater than the\ntarget.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_missing_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_missing_target", "kind": "variable", "doc": "

    Probability that the strategy return will be less than the target.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.reaching_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.reaching_target_range", "kind": "variable", "doc": "

    Range of stock prices where the strategy return is equal or greater than\nthe target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.missing_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.missing_target_range", "kind": "variable", "doc": "

    Range of stock prices where the strategy return is less than the target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_above_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_above_target", "kind": "variable", "doc": "

    Expected value of the strategy return when the return is equal or greater\nthan the target.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_below_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_below_target", "kind": "variable", "doc": "

    Expected value of the strategy return when the return is less than the\ntarget.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.model_config", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs.model_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_reaching_target': FieldInfo(annotation=float, required=False, default=0.0), 'probability_of_missing_target': FieldInfo(annotation=float, required=False, default=0.0), 'reaching_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'missing_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'expected_return_above_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_return_below_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.PoPOutputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.plot", "modulename": "optionlab.plot", "kind": "module", "doc": "

    This module implements the plot_pl function, which displays the profit/loss diagram \nof an options trading strategy.

    \n"}, {"fullname": "optionlab.plot.plot_pl", "modulename": "optionlab.plot", "qualname": "plot_pl", "kind": "function", "doc": "

    Displays the strategy's profit/loss diagram.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation with optionlab.engine.run_strategy.

    \n\n

    Returns

    \n\n

    None.

    \n", "signature": "(outputs: optionlab.models.Outputs) -> None:", "funcdef": "def"}, {"fullname": "optionlab.price_array", "modulename": "optionlab.price_array", "kind": "module", "doc": "

    This module defines the create_price_array function, which calculates terminal \nprices from numerical simulations of multiple stock paths.

    \n\n

    The terminal price array can later be used to calculate the probability of profit \n(PoP) of a strategy using the optionlab.engine.run_strategy function.

    \n"}, {"fullname": "optionlab.price_array.create_price_array", "modulename": "optionlab.price_array", "qualname": "create_price_array", "kind": "function", "doc": "

    Generates terminal stock prices.

    \n\n

    Parameters

    \n\n

    inputs_data: input data used to generate the terminal stock prices.

    \n\n

    n: number of terminal stock prices.

    \n\n

    seed: seed for random number generation.

    \n\n

    Returns

    \n\n

    Array of terminal prices.

    \n", "signature": "(\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.LaplaceInputs | dict,\tn: int = 100000,\tseed: int | None = None) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support", "modulename": "optionlab.support", "kind": "module", "doc": "

    This module implements a number of helper functions that are not intended to be \ncalled directly by users, but rather support functionalities within the \noptionlab.engine.run_strategy function.

    \n"}, {"fullname": "optionlab.support.get_pl_profile", "modulename": "optionlab.support", "qualname": "get_pl_profile", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of an options trade at expiration.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    x: strike price.

    \n\n

    val: option price.

    \n\n

    n: number of options.

    \n\n

    s: array of stock prices.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of an option trade at expiration.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_stock", "modulename": "optionlab.support", "qualname": "get_pl_profile_stock", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of a stock position.

    \n\n

    Parameters

    \n\n

    s0: initial stock price.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    n: number of shares.

    \n\n

    s: array of stock prices.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of a stock position.

    \n", "signature": "(\ts0: float,\taction: Literal['buy', 'sell'],\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_bs", "modulename": "optionlab.support", "qualname": "get_pl_profile_bs", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of an options trade on a target date\nbefore expiration using the Black-Scholes model for option pricing.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    x: strike price.

    \n\n

    val: initial option price.

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    target_to_maturity_years: time remaining to maturity from the target date,\nin years.

    \n\n

    volatility: annualized volatility of the underlying asset.

    \n\n

    n: number of options.

    \n\n

    s: array of stock prices.

    \n\n

    y: annualized dividend yield.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of an option trade before expiration.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tr: float,\ttarget_to_maturity_years: float,\tvolatility: float,\tn: int,\ts: numpy.ndarray,\ty: float = 0.0,\tcommission: float = 0.0) -> tuple[float | numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.create_price_seq", "modulename": "optionlab.support", "qualname": "create_price_seq", "kind": "function", "doc": "

    Generates a sequence of stock prices from a minimum to a maximum price with\nincrement $0.01.

    \n\n

    Parameters

    \n\n

    min_price: minimum stock price in the range.

    \n\n

    max_price: maximum stock price in the range.

    \n\n

    Returns

    \n\n

    Array of sequential stock prices.

    \n", "signature": "(min_price: float, max_price: float) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pop", "modulename": "optionlab.support", "qualname": "get_pop", "kind": "function", "doc": "

    Estimates the probability of profit (PoP) of an options trading strategy.

    \n\n

    Parameters

    \n\n

    s: array of stock prices.

    \n\n

    profit: array of profits and losses.

    \n\n

    inputs_data: input data used to estimate the probability of profit.

    \n\n

    target: target return.

    \n\n

    Returns

    \n\n

    Outputs of a probability of profit (PoP) calculation.

    \n", "signature": "(\ts: numpy.ndarray,\tprofit: numpy.ndarray,\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.ArrayInputs,\ttarget: float = 0.01) -> optionlab.models.PoPOutputs:", "funcdef": "def"}, {"fullname": "optionlab.utils", "modulename": "optionlab.utils", "kind": "module", "doc": "

    This module defines utility functions.

    \n"}, {"fullname": "optionlab.utils.get_nonbusiness_days", "modulename": "optionlab.utils", "qualname": "get_nonbusiness_days", "kind": "function", "doc": "

    Returns the number of non-business days (i.e., weekends and holidays) between\nthe start and end date.

    \n\n

    Parameters

    \n\n

    start_date: start date.

    \n\n

    end_date: end date.

    \n\n

    country: country of the stock exchange.

    \n\n

    Returns

    \n\n

    Number of weekends and holidays between the start and end date.

    \n", "signature": "(\tstart_date: datetime.date,\tend_date: datetime.date,\tcountry: str = 'US') -> int:", "funcdef": "def"}, {"fullname": "optionlab.utils.get_pl", "modulename": "optionlab.utils", "qualname": "get_pl", "kind": "function", "doc": "

    Returns the stock prices and the corresponding profit/loss profile of either\na leg or the whole strategy.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation.

    \n\n

    leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

    \n\n

    Returns

    \n\n

    Array of stock prices and array or profits/losses.

    \n", "signature": "(\toutputs: optionlab.models.Outputs,\tleg: int | None = None) -> tuple[numpy.ndarray, numpy.ndarray]:", "funcdef": "def"}, {"fullname": "optionlab.utils.pl_to_csv", "modulename": "optionlab.utils", "qualname": "pl_to_csv", "kind": "function", "doc": "

    Saves the stock prices and corresponding profit/loss profile of either a leg\nor the whole strategy to a CSV file.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation.

    \n\n

    filename: name of the CSV file.

    \n\n

    leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

    \n\n

    Returns

    \n\n

    None.

    \n", "signature": "(\toutputs: optionlab.models.Outputs,\tfilename: str = 'pl.csv',\tleg: int | None = None) -> None:", "funcdef": "def"}]; + /** pdoc search index */const docs = [{"fullname": "optionlab", "modulename": "optionlab", "kind": "module", "doc": "

    OptionLab is...

    \n\n

    ... a Python library designed as a research tool for quickly evaluating options \nstrategy ideas. It is intended for a wide range of users, from individuals learning \nabout options trading to developers of quantitative strategies.

    \n\n

    OptionLab calculations can produce a number of useful outputs:

    \n\n
      \n
    • the profit/loss profile of the strategy on a user-defined target date,

    • \n
    • the range of stock prices for which the strategy is profitable,

    • \n
    • the Greeks associated with each leg of the strategy,

    • \n
    • the resulting debit or credit on the trading account,

    • \n
    • the maximum and minimum returns within a specified lower and higher price range \nof the underlying asset,

    • \n
    • the expected profit and expected loss, and

    • \n
    • an estimate of the strategy's probability of profit.

    • \n
    \n\n

    The probability of profit (PoP) of the strategy on the user-defined target date \nis calculated analytically by default using the Black-Scholes model. Alternatively, \nthe user can provide an array of terminal underlying asset prices obtained from \nother sources (e.g., the Heston model, a Laplace distribution, or a Machine Learning/Deep Learning model) \nto be used in the calculations instead of the Black-Scholes model. This allows \nOptionLab to function as a calculator that supports a variety of pricing \nmodels.

    \n\n

    An advanced feature of OptionLab that provides great flexibility in building \ncomplex dynamic strategies is the ability to include previously created positions \nas legs in a new strategy. Popular strategies that can benefit from this feature \ninclude the Wheel and Covered Call strategies.

    \n\n

    OptionLab is not...

    \n\n

    ... a platform for direct order execution. This capability has not been and \nprobably will not be implemented.

    \n\n

    Backtesting and trade simulation using Monte Carlo have also not (yet) been \nimplemented in the API.

    \n\n

    That being said, nothing prevents OptionLab from being integrated into an \noptions quant trader's workflow alongside other tools.

    \n\n

    Installation

    \n\n

    The easiest way to install OptionLab is using pip:

    \n\n
    pip install optionlab\n
    \n\n

    Quickstart

    \n\n

    OptionLab is designed with ease of use in mind. An options strategy can be \ndefined and evaluated with just a few lines of Python code. The API is streamlined, \nand the learning curve is minimal.

    \n\n

    The evaluation of a strategy is done by calling the optionlab.engine.run_strategy \nfunction provided by the library. This function receives the input data either \nas a dictionary or an optionlab.models.Inputs object.

    \n\n

    For example, let's say we wanted to calculate the probability of profit for naked \ncalls on Apple stocks expiring on December 17, 2021. The strategy setup consisted \nof selling 100 175.00 strike calls for 1.15 each on November 22, 2021.

    \n\n

    The input data for this strategy can be provided in a dictionary as follows:

    \n\n
    \n
    input_data = {\n    "stock_price": 164.04,\n    "start_date": "2021-11-22",\n    "target_date": "2021-12-17",\n    "volatility": 0.272,\n    "interest_rate": 0.0002,\n    "min_stock": 120,\n    "max_stock": 200,\n    "strategy": [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n}\n
    \n
    \n\n

    Alternatively, the input data could be defined as the optionlab.models.Inputs \nobject below:

    \n\n
    \n
    from optionlab import Inputs\n\ninput_data = Inputs(\n    stock_price = 164.04,\n    start_date = "2021-11-22",\n    target_date = "2021-12-17",\n    volatility = 0.272,\n    interest_rate = 0.0002,\n    min_stock = 120,\n    max_stock = 200,\n    strategy = [\n        {\n            "type": "call",\n            "strike": 175.0,\n            "premium": 1.15,\n            "n": 100,\n            "action":"sell"\n        }\n    ],\n)\n
    \n
    \n\n

    In both cases, the strategy itself is a list of dictionaries, where each dictionary\ndefines a leg in the strategy. The fields in a leg, depending on the type of the\nleg, are described in optionlab.models.Stock, optionlab.models.Option, and\noptionlab.models.ClosedPosition.

    \n\n

    After defining the input data, we pass it to the run_strategy function as shown \nbelow:

    \n\n
    \n
    from optionlab import run_strategy, plot_pl\n\nout = run_strategy(input_data)\n\nprint(out)\n\nplot_pl(out)\n
    \n
    \n\n

    The variable out is an optionlab.models.Outputs object that contains the \nresults from the calculations. By calling print with out as an argument, \nthese results are displayed on screen.

    \n\n

    The optionlab.plot.plot_pl function, in turn, takes an optionlab.models.Outputs \nobject as its argument and plots the profit/loss diagram for the strategy.

    \n\n

    Examples

    \n\n

    Examples for a number of popular options trading strategies can be found as \nJupyter notebooks in the examples \ndirectory.

    \n"}, {"fullname": "optionlab.VERSION", "modulename": "optionlab", "qualname": "VERSION", "kind": "variable", "doc": "

    \n", "default_value": "'1.4.3'"}, {"fullname": "optionlab.black_scholes", "modulename": "optionlab.black_scholes", "kind": "module", "doc": "

    This module defines functions that calculate quantities, such as option prices\nand the Greeks, related to the Black-Scholes model.

    \n"}, {"fullname": "optionlab.black_scholes.get_bs_info", "modulename": "optionlab.black_scholes", "qualname": "get_bs_info", "kind": "function", "doc": "

    Provides information about call and put options calculated using the Black-Scholes\nformula.

    \n\n

    Parameters

    \n\n

    s: stock price.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Information calculated using the Black-Scholes formula.

    \n", "signature": "(\ts: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\ty: float = 0.0) -> optionlab.models.BlackScholesInfo:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_option_price", "modulename": "optionlab.black_scholes", "qualname": "get_option_price", "kind": "function", "doc": "

    Returns the price of an option.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualize risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option price(s).

    \n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_delta", "modulename": "optionlab.black_scholes", "qualname": "get_delta", "kind": "function", "doc": "

    Returns the option's Greek Delta.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Delta.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\td1: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_gamma", "modulename": "optionlab.black_scholes", "qualname": "get_gamma", "kind": "function", "doc": "

    Returns the option's Greek Gamma.

    \n\n

    Parameters

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    vol: annualized volatitily.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    Option's Greek Gamma.

    \n", "signature": "(\ts0: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_theta", "modulename": "optionlab.black_scholes", "qualname": "get_theta", "kind": "function", "doc": "

    Returns the option's Greek Theta.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Theta.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\ts0: float,\tx: float | numpy.ndarray,\tr: float,\tvol: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\td2: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_vega", "modulename": "optionlab.black_scholes", "qualname": "get_vega", "kind": "function", "doc": "

    Returns the option's Greek Vega.

    \n\n

    Parameters

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d1: d1 in Black-Scholes formula.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's Greek Vega.

    \n", "signature": "(\ts0: float,\tyears_to_maturity: float,\td1: float | numpy.ndarray,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_rho", "modulename": "optionlab.black_scholes", "qualname": "get_rho", "kind": "function", "doc": "

    Returns the option's Greek Rho.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    Returns

    \n\n

    Option's Greek Rho.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\tx: float | numpy.ndarray,\tr: float,\tyears_to_maturity: float,\td2: float | numpy.ndarray) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d1", "modulename": "optionlab.black_scholes", "qualname": "get_d1", "kind": "function", "doc": "

    Returns d1 used in Black-Scholes formula.

    \n\n

    Parameters

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility(ies).

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    d1 in Black-Scholes formula.

    \n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_d2", "modulename": "optionlab.black_scholes", "qualname": "get_d2", "kind": "function", "doc": "

    Returns d2 used in Black-Scholes formula.

    \n\n

    Parameters

    \n\n

    s0: spot price(s) of the underlying asset.

    \n\n

    x: strike price(s).

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    vol: annualized volatility(ies).

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized divident yield.

    \n\n

    Returns

    \n\n

    d2 in Black-Scholes formula.

    \n", "signature": "(\ts0: float | numpy.ndarray,\tx: float | numpy.ndarray,\tr: float,\tvol: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_implied_vol", "modulename": "optionlab.black_scholes", "qualname": "get_implied_vol", "kind": "function", "doc": "

    Returns the implied volatility of an option.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    oprice: market price of an option.

    \n\n

    s0: spot price of the underlying asset.

    \n\n

    x: strike price.

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Option's implied volatility.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\toprice: float,\ts0: float,\tx: float,\tr: float,\tyears_to_maturity: float,\ty: float = 0.0) -> float:", "funcdef": "def"}, {"fullname": "optionlab.black_scholes.get_itm_probability", "modulename": "optionlab.black_scholes", "qualname": "get_itm_probability", "kind": "function", "doc": "

    Returns the probability(ies) that the option(s) will expire in-the-money (ITM).

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    d2: d2 in Black-Scholes formula.

    \n\n

    years_to_maturity: time remaining to maturity, in years.

    \n\n

    y: annualized dividend yield.

    \n\n

    Returns

    \n\n

    Probability(ies) that the option(s) will expire in-the-money (ITM).

    \n", "signature": "(\toption_type: Literal['call', 'put'],\td2: float | numpy.ndarray,\tyears_to_maturity: float,\ty: float = 0.0) -> float | numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.engine", "modulename": "optionlab.engine", "kind": "module", "doc": "

    This module defines the run_strategy function.

    \n\n

    Given input data provided as either an optionlab.models.Inputs object or a dictionary,\nrun_strategy returns the results of an options strategy calculation (e.g., the\nprobability of profit on the target date) as an optionlab.models.Outputs object.

    \n"}, {"fullname": "optionlab.engine.run_strategy", "modulename": "optionlab.engine", "qualname": "run_strategy", "kind": "function", "doc": "

    Runs the calculation for a strategy.

    \n\n

    Parameters

    \n\n

    inputs_data: input data used in the strategy calculation.

    \n\n

    Returns

    \n\n

    Output data from the strategy calculation.

    \n", "signature": "(inputs_data: optionlab.models.Inputs | dict) -> optionlab.models.Outputs:", "funcdef": "def"}, {"fullname": "optionlab.models", "modulename": "optionlab.models", "kind": "module", "doc": "

    This module primarily implements Pydantic models that represent inputs and outputs \nof strategy calculations. It also implements constants and custom types.

    \n\n

    From the user's point of view, the two most important classes that they will use \nto provide input and subsequently process calculation results are Inputs and \nOutputs, respectively.

    \n"}, {"fullname": "optionlab.models.OptionType", "modulename": "optionlab.models", "qualname": "OptionType", "kind": "variable", "doc": "

    Option type in a strategy leg.

    \n", "default_value": "typing.Literal['call', 'put']"}, {"fullname": "optionlab.models.Action", "modulename": "optionlab.models", "qualname": "Action", "kind": "variable", "doc": "

    Action taken in in a strategy leg.

    \n", "default_value": "typing.Literal['buy', 'sell']"}, {"fullname": "optionlab.models.StrategyLegType", "modulename": "optionlab.models", "qualname": "StrategyLegType", "kind": "variable", "doc": "

    Type of strategy leg.

    \n", "default_value": "typing.Union[typing.Literal['stock'], typing.Literal['call', 'put'], typing.Literal['closed']]"}, {"fullname": "optionlab.models.TheoreticalModel", "modulename": "optionlab.models", "qualname": "TheoreticalModel", "kind": "variable", "doc": "

    Theoretical model used in probability of profit (PoP) calculations.

    \n", "default_value": "typing.Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Range", "modulename": "optionlab.models", "qualname": "Range", "kind": "variable", "doc": "

    Range boundaries.

    \n", "default_value": "tuple[float, float]"}, {"fullname": "optionlab.models.FloatOrNdarray", "modulename": "optionlab.models", "qualname": "FloatOrNdarray", "kind": "variable", "doc": "

    Float or numpy array custom type.

    \n", "default_value": "float | numpy.ndarray"}, {"fullname": "optionlab.models.Stock", "modulename": "optionlab.models", "qualname": "Stock", "kind": "class", "doc": "

    Defines the attributes of a stock leg in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Stock.type", "modulename": "optionlab.models", "qualname": "Stock.type", "kind": "variable", "doc": "

    It must be 'stock'.

    \n", "annotation": ": Literal['stock']"}, {"fullname": "optionlab.models.Stock.n", "modulename": "optionlab.models", "qualname": "Stock.n", "kind": "variable", "doc": "

    Number of shares.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Stock.action", "modulename": "optionlab.models", "qualname": "Stock.action", "kind": "variable", "doc": "

    Either 'buy' or 'sell'.

    \n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Stock.prev_pos", "modulename": "optionlab.models", "qualname": "Stock.prev_pos", "kind": "variable", "doc": "

    Stock price effectively paid or received in a previously opened position.

    \n\n
      \n
    • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current stock price.

    • \n
    • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

    • \n
    \n\n

    The default is None, which means this stock position is not a previously \nopened position.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Stock.model_config", "modulename": "optionlab.models", "qualname": "Stock.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Stock.model_fields", "modulename": "optionlab.models", "qualname": "Stock.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['stock'], required=False, default='stock'), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Stock.model_computed_fields", "modulename": "optionlab.models", "qualname": "Stock.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Option", "modulename": "optionlab.models", "qualname": "Option", "kind": "class", "doc": "

    Defines the attributes of an option leg in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Option.type", "modulename": "optionlab.models", "qualname": "Option.type", "kind": "variable", "doc": "

    Either 'call' or 'put'.

    \n", "annotation": ": Literal['call', 'put']"}, {"fullname": "optionlab.models.Option.strike", "modulename": "optionlab.models", "qualname": "Option.strike", "kind": "variable", "doc": "

    Strike price.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.premium", "modulename": "optionlab.models", "qualname": "Option.premium", "kind": "variable", "doc": "

    Option premium.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Option.action", "modulename": "optionlab.models", "qualname": "Option.action", "kind": "variable", "doc": "

    Either 'buy' or 'sell'.

    \n", "annotation": ": Literal['buy', 'sell']"}, {"fullname": "optionlab.models.Option.n", "modulename": "optionlab.models", "qualname": "Option.n", "kind": "variable", "doc": "

    Number of options.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Option.prev_pos", "modulename": "optionlab.models", "qualname": "Option.prev_pos", "kind": "variable", "doc": "

    Premium effectively paid or received in a previously opened position.

    \n\n
      \n
    • If positive, the position remains open and the payoff calculation considers\nthis price instead of the current price of the option.

    • \n
    • If negative, the position is closed and the difference between this price \nand the current price is included in the payoff calculation.

    • \n
    \n\n

    The default is None, which means this option position is not a previously \nopened position.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Option.expiration", "modulename": "optionlab.models", "qualname": "Option.expiration", "kind": "variable", "doc": "

    Expiration date or number of days remaining to expiration.

    \n\n

    The default is None, which means the expiration is the same as Inputs.target_date \nor Inputs.days_to_target_date.

    \n", "annotation": ": datetime.date | int | None"}, {"fullname": "optionlab.models.Option.model_config", "modulename": "optionlab.models", "qualname": "Option.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Option.model_fields", "modulename": "optionlab.models", "qualname": "Option.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['call', 'put'], required=True), 'strike': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'premium': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0)]), 'action': FieldInfo(annotation=Literal['buy', 'sell'], required=True), 'n': FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)]), 'prev_pos': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expiration': FieldInfo(annotation=Union[date, int, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.Option.model_computed_fields", "modulename": "optionlab.models", "qualname": "Option.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition", "modulename": "optionlab.models", "qualname": "ClosedPosition", "kind": "class", "doc": "

    Defines the attributes of a previously closed position in a strategy.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ClosedPosition.type", "modulename": "optionlab.models", "qualname": "ClosedPosition.type", "kind": "variable", "doc": "

    It must be 'closed'.

    \n", "annotation": ": Literal['closed']"}, {"fullname": "optionlab.models.ClosedPosition.prev_pos", "modulename": "optionlab.models", "qualname": "ClosedPosition.prev_pos", "kind": "variable", "doc": "

    The total amount of the closed position.

    \n\n
      \n
    • If positive, it resulted in a profit.

    • \n
    • If negative, it incurred a loss.

    • \n
    \n\n

    This amount will be added to the payoff and taken into account in the strategy \ncalculations.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.ClosedPosition.model_config", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.ClosedPosition.model_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'type': FieldInfo(annotation=Literal['closed'], required=False, default='closed'), 'prev_pos': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.ClosedPosition.model_computed_fields", "modulename": "optionlab.models", "qualname": "ClosedPosition.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.StrategyLeg", "modulename": "optionlab.models", "qualname": "StrategyLeg", "kind": "variable", "doc": "

    Leg in a strategy.

    \n", "default_value": "optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition"}, {"fullname": "optionlab.models.TheoreticalModelInputs", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs", "kind": "class", "doc": "

    Inputs for calculations, such as the probability of profit (PoP).

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.TheoreticalModelInputs.stock_price", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.stock_price", "kind": "variable", "doc": "

    Stock price.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.volatility", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.volatility", "kind": "variable", "doc": "

    Annualized volatility of the underlying asset.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.years_to_target_date", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.years_to_target_date", "kind": "variable", "doc": "

    Time remaining until target date, in years.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_config", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)])}"}, {"fullname": "optionlab.models.TheoreticalModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "TheoreticalModelInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs", "kind": "class", "doc": "

    Defines the input data for the calculations using the Black-Scholes model.

    \n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model", "kind": "variable", "doc": "

    It must be 'black-scholes'.

    \n", "annotation": ": Literal['black-scholes']"}, {"fullname": "optionlab.models.BlackScholesModelInputs.interest_rate", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.interest_rate", "kind": "variable", "doc": "

    Annualized risk-free interest rate.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.dividend_yield", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.dividend_yield", "kind": "variable", "doc": "

    Annualized dividend yield.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['black-scholes'], required=False, default='black-scholes'), 'interest_rate': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0), Le(le=1.0)])}"}, {"fullname": "optionlab.models.BlackScholesModelInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesModelInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs", "modulename": "optionlab.models", "qualname": "LaplaceInputs", "kind": "class", "doc": "

    Defines the input data for the calculations using a log-Laplace distribution of\nstock prices.

    \n", "bases": "TheoreticalModelInputs"}, {"fullname": "optionlab.models.LaplaceInputs.model", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model", "kind": "variable", "doc": "

    It must be 'laplace'.

    \n", "annotation": ": Literal['laplace']"}, {"fullname": "optionlab.models.LaplaceInputs.mu", "modulename": "optionlab.models", "qualname": "LaplaceInputs.mu", "kind": "variable", "doc": "

    Annualized return of the underlying asset.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.LaplaceInputs.model_config", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.LaplaceInputs.model_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'years_to_target_date': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'model': FieldInfo(annotation=Literal['laplace'], required=False, default='laplace'), 'mu': FieldInfo(annotation=float, required=True)}"}, {"fullname": "optionlab.models.LaplaceInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "LaplaceInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.ArrayInputs", "modulename": "optionlab.models", "qualname": "ArrayInputs", "kind": "class", "doc": "

    Defines the input data for the calculations when using an array of strategy\nreturns.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.ArrayInputs.model", "modulename": "optionlab.models", "qualname": "ArrayInputs.model", "kind": "variable", "doc": "

    It must be 'array'.

    \n", "annotation": ": Literal['array']"}, {"fullname": "optionlab.models.ArrayInputs.array", "modulename": "optionlab.models", "qualname": "ArrayInputs.array", "kind": "variable", "doc": "

    Array of strategy returns.

    \n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.ArrayInputs.model_config", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.ArrayInputs.model_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'model': FieldInfo(annotation=Literal['array'], required=False, default='array'), 'array': FieldInfo(annotation=ndarray, required=True)}"}, {"fullname": "optionlab.models.ArrayInputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "ArrayInputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Inputs", "modulename": "optionlab.models", "qualname": "Inputs", "kind": "class", "doc": "

    Defines the input data for a strategy calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Inputs.stock_price", "modulename": "optionlab.models", "qualname": "Inputs.stock_price", "kind": "variable", "doc": "

    Spot price of the underlying.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.volatility", "modulename": "optionlab.models", "qualname": "Inputs.volatility", "kind": "variable", "doc": "

    Annualized volatility.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.interest_rate", "modulename": "optionlab.models", "qualname": "Inputs.interest_rate", "kind": "variable", "doc": "

    Annualized risk-free interest rate.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.min_stock", "modulename": "optionlab.models", "qualname": "Inputs.min_stock", "kind": "variable", "doc": "

    Minimum value of the stock in the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.max_stock", "modulename": "optionlab.models", "qualname": "Inputs.max_stock", "kind": "variable", "doc": "

    Maximum value of the stock in the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.strategy", "modulename": "optionlab.models", "qualname": "Inputs.strategy", "kind": "variable", "doc": "

    A list of strategy legs.

    \n", "annotation": ": list[optionlab.models.Stock | optionlab.models.Option | optionlab.models.ClosedPosition]"}, {"fullname": "optionlab.models.Inputs.dividend_yield", "modulename": "optionlab.models", "qualname": "Inputs.dividend_yield", "kind": "variable", "doc": "

    Annualized dividend yield.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.profit_target", "modulename": "optionlab.models", "qualname": "Inputs.profit_target", "kind": "variable", "doc": "

    Target profit level.

    \n\n

    The default is None, which means it is not calculated.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.loss_limit", "modulename": "optionlab.models", "qualname": "Inputs.loss_limit", "kind": "variable", "doc": "

    Limit loss level.

    \n\n

    The default is None, which means it is not calculated.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Inputs.opt_commission", "modulename": "optionlab.models", "qualname": "Inputs.opt_commission", "kind": "variable", "doc": "

    Brokerage commission for options transactions.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.stock_commission", "modulename": "optionlab.models", "qualname": "Inputs.stock_commission", "kind": "variable", "doc": "

    Brokerage commission for stocks transactions.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Inputs.discard_nonbusiness_days", "modulename": "optionlab.models", "qualname": "Inputs.discard_nonbusiness_days", "kind": "variable", "doc": "

    Discards weekends and holidays when counting the number of days between\ntwo dates.

    \n\n

    The default is True.

    \n", "annotation": ": bool"}, {"fullname": "optionlab.models.Inputs.business_days_in_year", "modulename": "optionlab.models", "qualname": "Inputs.business_days_in_year", "kind": "variable", "doc": "

    Number of business days in a year.

    \n\n

    The default is 252.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.country", "modulename": "optionlab.models", "qualname": "Inputs.country", "kind": "variable", "doc": "

    Country whose holidays will be counted if discard_nonbusinessdays is\nset to True.

    \n\n

    The default is 'US'.

    \n", "annotation": ": str"}, {"fullname": "optionlab.models.Inputs.start_date", "modulename": "optionlab.models", "qualname": "Inputs.start_date", "kind": "variable", "doc": "

    Start date in the calculations.

    \n\n

    If not provided, days_to_target_date must be provided.

    \n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.target_date", "modulename": "optionlab.models", "qualname": "Inputs.target_date", "kind": "variable", "doc": "

    Target date in the calculations.

    \n\n

    If not provided, days_to_target_date must be provided.

    \n", "annotation": ": datetime.date | None"}, {"fullname": "optionlab.models.Inputs.days_to_target_date", "modulename": "optionlab.models", "qualname": "Inputs.days_to_target_date", "kind": "variable", "doc": "

    Days remaining to the target date.

    \n\n

    If not provided, start_date and target_date must be provided.

    \n", "annotation": ": int"}, {"fullname": "optionlab.models.Inputs.model", "modulename": "optionlab.models", "qualname": "Inputs.model", "kind": "variable", "doc": "

    Theoretical model used in the calculations of probability of profit.

    \n\n

    It can be 'black-scholes' or 'array'.

    \n", "annotation": ": Literal['black-scholes', 'array']"}, {"fullname": "optionlab.models.Inputs.array", "modulename": "optionlab.models", "qualname": "Inputs.array", "kind": "variable", "doc": "

    Array of terminal stock prices.

    \n\n

    The default is an empty array.

    \n", "annotation": ": numpy.ndarray"}, {"fullname": "optionlab.models.Inputs.model_config", "modulename": "optionlab.models", "qualname": "Inputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.Inputs.model_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'stock_price': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0.0)]), 'volatility': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'interest_rate': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'min_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'max_stock': FieldInfo(annotation=float, required=True, metadata=[Ge(ge=0.0)]), 'strategy': FieldInfo(annotation=list[Union[Stock, Option, ClosedPosition]], required=True, metadata=[MinLen(min_length=1)]), 'dividend_yield': FieldInfo(annotation=float, required=False, default=0.0, metadata=[Ge(ge=0.0)]), 'profit_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'loss_limit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opt_commission': FieldInfo(annotation=float, required=False, default=0.0), 'stock_commission': FieldInfo(annotation=float, required=False, default=0.0), 'discard_nonbusiness_days': FieldInfo(annotation=bool, required=False, default=True), 'business_days_in_year': FieldInfo(annotation=int, required=False, default=252), 'country': FieldInfo(annotation=str, required=False, default='US'), 'start_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'target_date': FieldInfo(annotation=Union[date, NoneType], required=False, default=None), 'days_to_target_date': FieldInfo(annotation=int, required=False, default=0, metadata=[Ge(ge=0)]), 'model': FieldInfo(annotation=Literal['black-scholes', 'array'], required=False, default='black-scholes'), 'array': FieldInfo(annotation=ndarray, required=False, default_factory=init_empty_array)}"}, {"fullname": "optionlab.models.Inputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Inputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.BlackScholesInfo", "modulename": "optionlab.models", "qualname": "BlackScholesInfo", "kind": "class", "doc": "

    Defines the data returned by a calculation using the Black-Scholes model.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.BlackScholesInfo.call_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_price", "kind": "variable", "doc": "

    Price of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_price", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_price", "kind": "variable", "doc": "

    Price of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_delta", "kind": "variable", "doc": "

    Delta of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_delta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_delta", "kind": "variable", "doc": "

    Delta of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_theta", "kind": "variable", "doc": "

    Theta of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_theta", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_theta", "kind": "variable", "doc": "

    Theta of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.gamma", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.gamma", "kind": "variable", "doc": "

    Gamma of an option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.vega", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.vega", "kind": "variable", "doc": "

    Vega of an option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_rho", "kind": "variable", "doc": "

    Rho of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_rho", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_rho", "kind": "variable", "doc": "

    Rho of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.call_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.call_itm_prob", "kind": "variable", "doc": "

    Probability of expiring in-the-money probability of a call option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.put_itm_prob", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.put_itm_prob", "kind": "variable", "doc": "

    Probability of expiring in-the-money of a put option.

    \n", "annotation": ": float | numpy.ndarray"}, {"fullname": "optionlab.models.BlackScholesInfo.model_config", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "default_value": "{'arbitrary_types_allowed': True}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'call_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_price': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_delta': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_theta': FieldInfo(annotation=Union[float, ndarray], required=True), 'gamma': FieldInfo(annotation=Union[float, ndarray], required=True), 'vega': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_rho': FieldInfo(annotation=Union[float, ndarray], required=True), 'call_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True), 'put_itm_prob': FieldInfo(annotation=Union[float, ndarray], required=True)}"}, {"fullname": "optionlab.models.BlackScholesInfo.model_computed_fields", "modulename": "optionlab.models", "qualname": "BlackScholesInfo.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs", "modulename": "optionlab.models", "qualname": "Outputs", "kind": "class", "doc": "

    Defines the output data from a strategy calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.Outputs.probability_of_profit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit", "kind": "variable", "doc": "

    Probability of the strategy yielding at least $0.01.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_ranges", "kind": "variable", "doc": "

    A list of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least $0.01.

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.expected_profit", "modulename": "optionlab.models", "qualname": "Outputs.expected_profit", "kind": "variable", "doc": "

    Expected profit when the strategy is profitable.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.expected_loss", "modulename": "optionlab.models", "qualname": "Outputs.expected_loss", "kind": "variable", "doc": "

    Expected loss when the strategy is not profitable.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.Outputs.per_leg_cost", "modulename": "optionlab.models", "qualname": "Outputs.per_leg_cost", "kind": "variable", "doc": "

    List of leg costs.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.strategy_cost", "modulename": "optionlab.models", "qualname": "Outputs.strategy_cost", "kind": "variable", "doc": "

    Total strategy cost.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.minimum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.minimum_return_in_the_domain", "kind": "variable", "doc": "

    Minimum return of the strategy within the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.maximum_return_in_the_domain", "modulename": "optionlab.models", "qualname": "Outputs.maximum_return_in_the_domain", "kind": "variable", "doc": "

    Maximum return of the strategy within the stock price domain.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.implied_volatility", "modulename": "optionlab.models", "qualname": "Outputs.implied_volatility", "kind": "variable", "doc": "

    List of implied volatilities, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.in_the_money_probability", "modulename": "optionlab.models", "qualname": "Outputs.in_the_money_probability", "kind": "variable", "doc": "

    List of probabilities of legs expiring in-the-money (ITM).

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.delta", "modulename": "optionlab.models", "qualname": "Outputs.delta", "kind": "variable", "doc": "

    List of Delta values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.gamma", "modulename": "optionlab.models", "qualname": "Outputs.gamma", "kind": "variable", "doc": "

    List of Gamma values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.theta", "modulename": "optionlab.models", "qualname": "Outputs.theta", "kind": "variable", "doc": "

    List of Theta values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.vega", "modulename": "optionlab.models", "qualname": "Outputs.vega", "kind": "variable", "doc": "

    List of Vega values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.rho", "modulename": "optionlab.models", "qualname": "Outputs.rho", "kind": "variable", "doc": "

    List of Rho values, one per strategy leg.

    \n", "annotation": ": list[float]"}, {"fullname": "optionlab.models.Outputs.probability_of_profit_target", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_profit_target", "kind": "variable", "doc": "

    Probability of the strategy yielding at least the profit target.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.profit_target_ranges", "modulename": "optionlab.models", "qualname": "Outputs.profit_target_ranges", "kind": "variable", "doc": "

    List of minimum and maximum stock prices defining ranges in which the\nstrategy makes at least the profit target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.probability_of_loss_limit", "modulename": "optionlab.models", "qualname": "Outputs.probability_of_loss_limit", "kind": "variable", "doc": "

    Probability of the strategy losing at least the loss limit.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.Outputs.loss_limit_ranges", "modulename": "optionlab.models", "qualname": "Outputs.loss_limit_ranges", "kind": "variable", "doc": "

    List of minimum and maximum stock prices defining ranges where the\nstrategy loses at least the loss limit.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.Outputs.model_config", "modulename": "optionlab.models", "qualname": "Outputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.Outputs.model_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_profit': FieldInfo(annotation=float, required=True), 'profit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=True), 'expected_profit': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_loss': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'per_leg_cost': FieldInfo(annotation=list[float], required=True), 'strategy_cost': FieldInfo(annotation=float, required=True), 'minimum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'maximum_return_in_the_domain': FieldInfo(annotation=float, required=True), 'implied_volatility': FieldInfo(annotation=list[float], required=True), 'in_the_money_probability': FieldInfo(annotation=list[float], required=True), 'delta': FieldInfo(annotation=list[float], required=True), 'gamma': FieldInfo(annotation=list[float], required=True), 'theta': FieldInfo(annotation=list[float], required=True), 'vega': FieldInfo(annotation=list[float], required=True), 'rho': FieldInfo(annotation=list[float], required=True), 'probability_of_profit_target': FieldInfo(annotation=float, required=False, default=0.0), 'profit_target_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'probability_of_loss_limit': FieldInfo(annotation=float, required=False, default=0.0), 'loss_limit_ranges': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'inputs': FieldInfo(annotation=Inputs, required=True), 'data': FieldInfo(annotation=EngineDataResults, required=True)}"}, {"fullname": "optionlab.models.Outputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "Outputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs", "modulename": "optionlab.models", "qualname": "PoPOutputs", "kind": "class", "doc": "

    Defines the output data from a probability of profit (PoP) calculation.

    \n", "bases": "pydantic.main.BaseModel"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_reaching_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_reaching_target", "kind": "variable", "doc": "

    Probability that the strategy return will be equal or greater than the\ntarget.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.probability_of_missing_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.probability_of_missing_target", "kind": "variable", "doc": "

    Probability that the strategy return will be less than the target.

    \n\n

    The default is 0.0.

    \n", "annotation": ": float"}, {"fullname": "optionlab.models.PoPOutputs.reaching_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.reaching_target_range", "kind": "variable", "doc": "

    Range of stock prices where the strategy return is equal or greater than\nthe target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.missing_target_range", "modulename": "optionlab.models", "qualname": "PoPOutputs.missing_target_range", "kind": "variable", "doc": "

    Range of stock prices where the strategy return is less than the target.

    \n\n

    The default is [].

    \n", "annotation": ": list[tuple[float, float]]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_above_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_above_target", "kind": "variable", "doc": "

    Expected value of the strategy return when the return is equal or greater\nthan the target.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.expected_return_below_target", "modulename": "optionlab.models", "qualname": "PoPOutputs.expected_return_below_target", "kind": "variable", "doc": "

    Expected value of the strategy return when the return is less than the\ntarget.

    \n\n

    The default is None.

    \n", "annotation": ": Optional[float]"}, {"fullname": "optionlab.models.PoPOutputs.model_config", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_config", "kind": "variable", "doc": "

    Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

    \n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "optionlab.models.PoPOutputs.model_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_fields", "kind": "variable", "doc": "

    Metadata about the fields defined on the model,\nmapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.

    \n\n

    This replaces Model.__fields__ from Pydantic V1.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.FieldInfo]]", "default_value": "{'probability_of_reaching_target': FieldInfo(annotation=float, required=False, default=0.0), 'probability_of_missing_target': FieldInfo(annotation=float, required=False, default=0.0), 'reaching_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'missing_target_range': FieldInfo(annotation=list[tuple[float, float]], required=False, default=[]), 'expected_return_above_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'expected_return_below_target': FieldInfo(annotation=Union[float, NoneType], required=False, default=None)}"}, {"fullname": "optionlab.models.PoPOutputs.model_computed_fields", "modulename": "optionlab.models", "qualname": "PoPOutputs.model_computed_fields", "kind": "variable", "doc": "

    A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

    \n", "annotation": ": ClassVar[Dict[str, pydantic.fields.ComputedFieldInfo]]", "default_value": "{}"}, {"fullname": "optionlab.plot", "modulename": "optionlab.plot", "kind": "module", "doc": "

    This module implements the plot_pl function, which displays the profit/loss diagram \nof an options trading strategy.

    \n"}, {"fullname": "optionlab.plot.plot_pl", "modulename": "optionlab.plot", "qualname": "plot_pl", "kind": "function", "doc": "

    Displays the strategy's profit/loss diagram.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation with optionlab.engine.run_strategy.

    \n\n

    Returns

    \n\n

    None.

    \n", "signature": "(outputs: optionlab.models.Outputs) -> None:", "funcdef": "def"}, {"fullname": "optionlab.price_array", "modulename": "optionlab.price_array", "kind": "module", "doc": "

    This module defines the create_price_array function, which calculates terminal \nprices from numerical simulations of multiple stock paths.

    \n\n

    The terminal price array can later be used to calculate the probability of profit \n(PoP) of a strategy using the optionlab.engine.run_strategy function.

    \n"}, {"fullname": "optionlab.price_array.create_price_array", "modulename": "optionlab.price_array", "qualname": "create_price_array", "kind": "function", "doc": "

    Generates terminal stock prices.

    \n\n

    Parameters

    \n\n

    inputs_data: input data used to generate the terminal stock prices.

    \n\n

    n: number of terminal stock prices.

    \n\n

    seed: seed for random number generation.

    \n\n

    Returns

    \n\n

    Array of terminal prices.

    \n", "signature": "(\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.LaplaceInputs | dict,\tn: int = 100000,\tseed: int | None = None) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support", "modulename": "optionlab.support", "kind": "module", "doc": "

    This module implements a number of helper functions that are not intended to be \ncalled directly by users, but rather support functionalities within the \noptionlab.engine.run_strategy function.

    \n"}, {"fullname": "optionlab.support.get_pl_profile", "modulename": "optionlab.support", "qualname": "get_pl_profile", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of an options trade at expiration.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    x: strike price.

    \n\n

    val: option price.

    \n\n

    n: number of options.

    \n\n

    s: array of stock prices.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of an option trade at expiration.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_stock", "modulename": "optionlab.support", "qualname": "get_pl_profile_stock", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of a stock position.

    \n\n

    Parameters

    \n\n

    s0: initial stock price.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    n: number of shares.

    \n\n

    s: array of stock prices.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of a stock position.

    \n", "signature": "(\ts0: float,\taction: Literal['buy', 'sell'],\tn: int,\ts: numpy.ndarray,\tcommission: float = 0.0) -> tuple[numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pl_profile_bs", "modulename": "optionlab.support", "qualname": "get_pl_profile_bs", "kind": "function", "doc": "

    Returns the profit/loss profile and cost of an options trade on a target date\nbefore expiration using the Black-Scholes model for option pricing.

    \n\n

    Parameters

    \n\n

    option_type: either 'call' or 'put'.

    \n\n

    action: either 'buy' or 'sell'.

    \n\n

    x: strike price.

    \n\n

    val: initial option price.

    \n\n

    r: annualized risk-free interest rate.

    \n\n

    target_to_maturity_years: time remaining to maturity from the target date,\nin years.

    \n\n

    volatility: annualized volatility of the underlying asset.

    \n\n

    n: number of options.

    \n\n

    s: array of stock prices.

    \n\n

    y: annualized dividend yield.

    \n\n

    commission: brokerage commission.

    \n\n

    Returns

    \n\n

    Profit/loss profile and cost of an option trade before expiration.

    \n", "signature": "(\toption_type: Literal['call', 'put'],\taction: Literal['buy', 'sell'],\tx: float,\tval: float,\tr: float,\ttarget_to_maturity_years: float,\tvolatility: float,\tn: int,\ts: numpy.ndarray,\ty: float = 0.0,\tcommission: float = 0.0) -> tuple[float | numpy.ndarray, float]:", "funcdef": "def"}, {"fullname": "optionlab.support.create_price_seq", "modulename": "optionlab.support", "qualname": "create_price_seq", "kind": "function", "doc": "

    Generates a sequence of stock prices from a minimum to a maximum price with\nincrement $0.01.

    \n\n

    Parameters

    \n\n

    min_price: minimum stock price in the range.

    \n\n

    max_price: maximum stock price in the range.

    \n\n

    Returns

    \n\n

    Array of sequential stock prices.

    \n", "signature": "(min_price: float, max_price: float) -> numpy.ndarray:", "funcdef": "def"}, {"fullname": "optionlab.support.get_pop", "modulename": "optionlab.support", "qualname": "get_pop", "kind": "function", "doc": "

    Estimates the probability of profit (PoP) of an options trading strategy.

    \n\n

    Parameters

    \n\n

    s: array of stock prices.

    \n\n

    profit: array of profits and losses.

    \n\n

    inputs_data: input data used to estimate the probability of profit.

    \n\n

    target: target return.

    \n\n

    Returns

    \n\n

    Outputs of a probability of profit (PoP) calculation.

    \n", "signature": "(\ts: numpy.ndarray,\tprofit: numpy.ndarray,\tinputs_data: optionlab.models.BlackScholesModelInputs | optionlab.models.ArrayInputs,\ttarget: float = 0.01) -> optionlab.models.PoPOutputs:", "funcdef": "def"}, {"fullname": "optionlab.utils", "modulename": "optionlab.utils", "kind": "module", "doc": "

    This module defines utility functions.

    \n"}, {"fullname": "optionlab.utils.get_nonbusiness_days", "modulename": "optionlab.utils", "qualname": "get_nonbusiness_days", "kind": "function", "doc": "

    Returns the number of non-business days (i.e., weekends and holidays) between\nthe start and end date.

    \n\n

    Parameters

    \n\n

    start_date: start date.

    \n\n

    end_date: end date.

    \n\n

    country: country of the stock exchange.

    \n\n

    Returns

    \n\n

    Number of weekends and holidays between the start and end date.

    \n", "signature": "(\tstart_date: datetime.date,\tend_date: datetime.date,\tcountry: str = 'US') -> int:", "funcdef": "def"}, {"fullname": "optionlab.utils.get_pl", "modulename": "optionlab.utils", "qualname": "get_pl", "kind": "function", "doc": "

    Returns the stock prices and the corresponding profit/loss profile of either\na leg or the whole strategy.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation.

    \n\n

    leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

    \n\n

    Returns

    \n\n

    Array of stock prices and array or profits/losses.

    \n", "signature": "(\toutputs: optionlab.models.Outputs,\tleg: int | None = None) -> tuple[numpy.ndarray, numpy.ndarray]:", "funcdef": "def"}, {"fullname": "optionlab.utils.pl_to_csv", "modulename": "optionlab.utils", "qualname": "pl_to_csv", "kind": "function", "doc": "

    Saves the stock prices and corresponding profit/loss profile of either a leg\nor the whole strategy to a CSV file.

    \n\n

    Parameters

    \n\n

    outputs: output data from a strategy calculation.

    \n\n

    filename: name of the CSV file.

    \n\n

    leg: index of a strategy leg. The default is None, which means the whole\nstrategy.

    \n\n

    Returns

    \n\n

    None.

    \n", "signature": "(\toutputs: optionlab.models.Outputs,\tfilename: str = 'pl.csv',\tleg: int | None = None) -> None:", "funcdef": "def"}]; // mirrored in build-search-index.js (part 1) // Also split on html tags. this is a cheap heuristic, but good enough. diff --git a/optionlab.png b/optionlab.png new file mode 100644 index 0000000000000000000000000000000000000000..bea8efff70a53066869aeda1932f4ef469205c2a GIT binary patch literal 29413 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 W+A22a^3jwnoE%*3_m1`X?tcI|d(8y^ literal 0 HcmV?d00001 diff --git a/optionlab/__init__.py b/optionlab/__init__.py index 529a671..2ace2d7 100644 --- a/optionlab/__init__.py +++ b/optionlab/__init__.py @@ -22,12 +22,13 @@ - an estimate of the strategy's probability of profit. -The probability of profit (PoP) of the strategy on the user-defined target date -is calculated by default using the Black-Scholes model. The user can alternatively -provide an array of underlying asset prices obtained elsewhere (e.g. from the -Heston model, a Laplace distribution or a Machine Learning/Deep Learning model) +The probability of profit (PoP) of the strategy on the user-defined target date +is calculated analytically by default using the Black-Scholes model. Alternatively, +the user can provide an array of terminal underlying asset prices obtained from +other sources (e.g., the Heston model, a Laplace distribution, or a Machine Learning/Deep Learning model) to be used in the calculations instead of the Black-Scholes model. This allows -**OptionLab** to function as a calculator using custom models. +**OptionLab** to function as a calculator that supports a variety of pricing +models. An advanced feature of **OptionLab** that provides great flexibility in building complex dynamic strategies is the ability to include previously created positions diff --git a/tests/test_core.py b/tests/test_core.py index 76cf69d..85d9811 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,9 +1,9 @@ import pytest from optionlab import Inputs, Outputs, BlackScholesModelInputs, LaplaceInputs -from optionlab.engine import run_strategy -from optionlab.price_array import create_price_array -from optionlab.black_scholes import get_bs_info +from optionlab import run_strategy +from optionlab import create_price_array +from optionlab import get_bs_info COVERED_CALL_RESULT = { From 5a9d0782e12ae50aa16aaab30b181531870828b4 Mon Sep 17 00:00:00 2001 From: rgaveiga Date: Fri, 25 Apr 2025 11:51:44 -0300 Subject: [PATCH 3/4] Update github actions --- .github/actions/cached-venv/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/cached-venv/action.yml b/.github/actions/cached-venv/action.yml index 6c7c1bb..7b40ef1 100644 --- a/.github/actions/cached-venv/action.yml +++ b/.github/actions/cached-venv/action.yml @@ -5,7 +5,7 @@ runs: using: composite steps: - name: Cache virtual environment - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: cache-venv-1 with: From 380e81e286eb03944e296fbdd2f9f85e06029f4f Mon Sep 17 00:00:00 2001 From: rgaveiga Date: Fri, 25 Apr 2025 18:46:32 -0300 Subject: [PATCH 4/4] Update examples --- examples/black_scholes_calculator.ipynb | 4 ++-- examples/calendar_spread.ipynb | 6 +++--- examples/call_spread.ipynb | 12 ++++++------ examples/covered_call.ipynb | 8 ++++---- examples/naked_call.ipynb | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/black_scholes_calculator.ipynb b/examples/black_scholes_calculator.ipynb index e3eaa4f..23a46d7 100644 --- a/examples/black_scholes_calculator.ipynb +++ b/examples/black_scholes_calculator.ipynb @@ -43,7 +43,7 @@ "output_type": "stream", "text": [ "Python version: 3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]\n", - "OptionLab version: 1.4.2\n" + "OptionLab version: 1.4.3\n" ] } ], @@ -106,7 +106,7 @@ "output_type": "stream", "text": [ "CPU times: total: 0 ns\n", - "Wall time: 0 ns\n" + "Wall time: 4 ms\n" ] } ], diff --git a/examples/calendar_spread.ipynb b/examples/calendar_spread.ipynb index 9def504..61f4d24 100644 --- a/examples/calendar_spread.ipynb +++ b/examples/calendar_spread.ipynb @@ -47,7 +47,7 @@ "output_type": "stream", "text": [ "Python version: 3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]\n", - "OptionLab version: 1.4.2\n" + "OptionLab version: 1.4.3\n" ] } ], @@ -119,8 +119,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: total: 406 ms\n", - "Wall time: 492 ms\n" + "CPU times: total: 375 ms\n", + "Wall time: 485 ms\n" ] } ], diff --git a/examples/call_spread.ipynb b/examples/call_spread.ipynb index 53478e0..444aad2 100644 --- a/examples/call_spread.ipynb +++ b/examples/call_spread.ipynb @@ -164,8 +164,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: total: 29.4 s\n", - "Wall time: 30.4 s\n" + "CPU times: total: 39.8 s\n", + "Wall time: 42.4 s\n" ] } ], @@ -269,7 +269,7 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, "execution_count": 9, @@ -383,8 +383,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: total: 29.6 s\n", - "Wall time: 30.9 s\n" + "CPU times: total: 39.5 s\n", + "Wall time: 42.3 s\n" ] } ], @@ -488,7 +488,7 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, "execution_count": 15, diff --git a/examples/covered_call.ipynb b/examples/covered_call.ipynb index 7070cce..cdb97de 100644 --- a/examples/covered_call.ipynb +++ b/examples/covered_call.ipynb @@ -54,7 +54,7 @@ "output_type": "stream", "text": [ "Python version: 3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]\n", - "OptionLab version: 1.4.2\n" + "OptionLab version: 1.4.3\n" ] } ], @@ -119,8 +119,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: total: 203 ms\n", - "Wall time: 205 ms\n" + "CPU times: total: 281 ms\n", + "Wall time: 258 ms\n" ] } ], @@ -142,7 +142,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 5, diff --git a/examples/naked_call.ipynb b/examples/naked_call.ipynb index c49af50..56ab09c 100644 --- a/examples/naked_call.ipynb +++ b/examples/naked_call.ipynb @@ -46,7 +46,7 @@ "output_type": "stream", "text": [ "Python version: 3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]\n", - "OptionLab version: 1.4.2\n" + "OptionLab version: 1.4.3\n" ] } ], @@ -116,8 +116,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: total: 141 ms\n", - "Wall time: 125 ms\n" + "CPU times: total: 328 ms\n", + "Wall time: 338 ms\n" ] } ],