Skip to content

Commit

Permalink
Remove strategy class (#26)
Browse files Browse the repository at this point in the history
* Update version in pyproject.toml

* Update version in __init__.py

* Update getPLoption in support.py

* Add run_check directory and move run scripts to it

* Change .gitignore

* Update .gitignore

* Update test_core.py

* Add test_black_scholes() to test_core.py

* Update tests

* Update .gitignore

* Commented StrategyEngine class

* Updata README.MD

* Update CHANGELOG.md

* Update version to 1.3.0

* Update __init.py__

* Update engine.py

* Black source
  • Loading branch information
rgaveiga authored Sep 13, 2024
1 parent bf94e57 commit 3e6e201
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 133 deletions.
11 changes: 8 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
dist/
.mypy_cache
.pytest_cache
__pycache__
run_*.py
.ipynb_checkpoints
dist
run_check
examples/.ipynb_checkpoints
optionlab/__pycache__
tests/__pycache__
tests/.benchmarks
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 1.3.0 (2024-09-13)

- Remove the deprecated `StrategyEngine` class (it remains commented in the code).
- Update the README.md file to reflect the current state of the library.

## 1.2.1 (2024-06-03)

- Add 1 to `time_to_target` and `time_to_maturity` in `engine.py` to consider the target and expiration dates as trading days in the calculations
Expand Down
41 changes: 16 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,13 @@ pip install optionlab

## Basic usage

Usage examples for several strategies can be found in the **examples** directory.
Usage examples for several strategies can be found in Jupyter notebooks in the **examples**
directory.

To evaluate an option strategy, an `Inputs` model needs to be created:
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.

```python
from optionlab import Inputs
inputs = Inputs.model_validate(inputs_data)
```

The input data passed to `model_validate` above needs to be of the following structure:
The description of the input data follows:

---

Expand Down Expand Up @@ -194,7 +191,7 @@ calls on Apple stocks with maturity on December 17, 2021. The strategy setup con
of selling 100 175.00 strike calls for 1.15 each on November 22, 2021.

```python
inputs_data = {
input_data = {
"stock_price": 164.04,
"start_date": "2021-11-22",
"target_date": "2021-12-17",
Expand All @@ -214,27 +211,18 @@ inputs_data = {
}
```

The simplest way to perform the calculations is by calling the `run_strategy` function
as follows:
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

out = run_strategy(inputs_data)
```

Alternatively, an `Inputs` object can be passed to the `StrategyEngine` object and
the calculations are performed by calling the `run` method of the `StrategyEngine`
object:
from optionlab import run_strategy, plot_pl

```python
from optionlab import StrategyEngine
out = run_strategy(input_data)

st = StrategyEngine(Inputs.model_validate(inputs_data))
out = st.run()
plot_pl(out)
```

In both cases, `out` contains an `Outputs` object with the following structure:
The variable `out` contains an `Outputs` object with the following structure:

---

Expand Down Expand Up @@ -301,9 +289,12 @@ In both cases, `out` contains an `Outputs` object with the following structure:
To obtain the probability of profit of the naked call example above:

```python
print("Probability of Profit (PoP): %.1f%%" % (out.probability_of_profit * 100.0)) # 84.5%, according to the calculations
print(f"Probability of Profit (PoP): {(out.probability_of_profit * 100.0):.1f}%%") # 84.5%, according to the calculations
```

The `plot_pl` function takes an `Outputs` object as its argument and plots the profit/loss
diagram for the strategy.

## Contributions

### Dev setup
Expand Down
6 changes: 2 additions & 4 deletions optionlab/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing


VERSION = "1.2.1"
VERSION = "1.3.0"


if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -35,7 +35,7 @@
get_gamma,
get_theta,
)
from .engine import StrategyEngine, run_strategy
from .engine import run_strategy
from .plot import plot_pl
from .support import (
get_pl_profile,
Expand Down Expand Up @@ -67,7 +67,6 @@
"Action",
# engine
"run_strategy",
"StrategyEngine",
# support
"get_pl_profile",
"get_pl_profile_stock",
Expand Down Expand Up @@ -109,7 +108,6 @@
"Country": (__package__, ".models"),
"Action": (__package__, ".models"),
# engine
"StrategyEngine": (__package__, ".engine"),
"run_strategy": (__package__, ".engine"),
# support
"get_pl_profile": (__package__, ".support"),
Expand Down
147 changes: 76 additions & 71 deletions optionlab/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import datetime as dt
from typing import Any

from numpy import array, ndarray, zeros
from numpy import array, zeros

# from numpy import ndarray

from optionlab.black_scholes import get_bs_info, get_implied_vol
from optionlab.models import (
Expand All @@ -28,7 +30,9 @@
create_price_samples,
get_pop,
)
from optionlab.utils import get_nonbusiness_days, get_pl, pl_to_csv
from optionlab.utils import get_nonbusiness_days

# from optionlab.utils import get_pl, pl_to_csv


def run_strategy(inputs_data: Inputs | dict) -> Outputs:
Expand Down Expand Up @@ -202,7 +206,7 @@ def _run(data: EngineData) -> EngineData:
data.profit_target_probability = get_pop(data._profit_target_range, pop_inputs)

if inputs.loss_limit is not None:
data._loss_limit_rangesm = get_profit_range(
data._loss_limit_ranges = get_profit_range(
data.stock_price_array, data.strategy_profit, inputs.loss_limit + 0.01
)
data.loss_limit_probability = 1.0 - get_pop(data._loss_limit_ranges, pop_inputs)
Expand Down Expand Up @@ -461,71 +465,72 @@ def _generate_outputs(data: EngineData) -> Outputs:
)


class StrategyEngine:
def __init__(self, inputs_data: Inputs | dict):
"""
__init__ -> initializes class variables.
Returns
-------
None.
"""
inputs = (
inputs_data
if isinstance(inputs_data, Inputs)
else Inputs.model_validate(inputs_data)
)

self.data = _init_inputs(inputs)

def run(self) -> Outputs:
"""
run -> runs calculations for an options strategy.
Returns
-------
output : Outputs
An Outputs object containing the output of a calculation.
"""

self.data = _run(self.data)

return _generate_outputs(self.data)

def get_pl(self, leg: int | None = None) -> tuple[ndarray, ndarray]:
"""
get_pl -> returns the profit/loss profile of either a leg or the whole
strategy.
Parameters
----------
leg : int, optional
Index of the leg. Default is None (whole strategy).
Returns
-------
stock prices : numpy array
Sequence of stock prices within the bounds of the stock price domain.
P/L profile : numpy array
Profit/loss profile of either a leg or the whole strategy.
"""

return get_pl(self.data, leg)

def pl_to_csv(self, filename: str = "pl.csv", leg: int | None = None) -> None:
"""
pl_to_csv -> saves the profit/loss data to a .csv file.
Parameters
----------
filename : string, optional
Name of the .csv file. Default is 'pl.csv'.
leg : int, optional
Index of the leg. Default is None (whole strategy).
Returns
-------
None.
"""

pl_to_csv(self.data, filename, leg)
# TODO: Delete this class in the next version; for now, leave it as a comment
# class StrategyEngine:
# def __init__(self, inputs_data: Inputs | dict):
# """
# __init__ -> initializes class variables.

# Returns
# -------
# None.
# """
# inputs = (
# inputs_data
# if isinstance(inputs_data, Inputs)
# else Inputs.model_validate(inputs_data)
# )

# self.data = _init_inputs(inputs)

# def run(self) -> Outputs:
# """
# run -> runs calculations for an options strategy.

# Returns
# -------
# output : Outputs
# An Outputs object containing the output of a calculation.
# """

# self.data = _run(self.data)

# return _generate_outputs(self.data)

# def get_pl(self, leg: int | None = None) -> tuple[ndarray, ndarray]:
# """
# get_pl -> returns the profit/loss profile of either a leg or the whole
# strategy.

# Parameters
# ----------
# leg : int, optional
# Index of the leg. Default is None (whole strategy).

# Returns
# -------
# stock prices : numpy array
# Sequence of stock prices within the bounds of the stock price domain.
# P/L profile : numpy array
# Profit/loss profile of either a leg or the whole strategy.
# """

# return get_pl(self.data, leg)

# def pl_to_csv(self, filename: str = "pl.csv", leg: int | None = None) -> None:
# """
# pl_to_csv -> saves the profit/loss data to a .csv file.

# Parameters
# ----------
# filename : string, optional
# Name of the .csv file. Default is 'pl.csv'.
# leg : int, optional
# Index of the leg. Default is None (whole strategy).

# Returns
# -------
# None.
# """

# pl_to_csv(self.data, filename, leg)
2 changes: 0 additions & 2 deletions optionlab/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,6 @@ def _get_pl_option(
s: a numpy array of stock prices.
x: strike price.
"""
if not isinstance(s, ndarray):
raise TypeError("'s' must be a numpy array!")

if action == "sell":
return opvalue - _get_payoff(option_type, s, x)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "optionlab"
version = "1.2.1"
version = "1.3.0"
description = "Evaluate option strategies"
authors = ["rgaveiga"]
readme = "README.md"
Expand Down
Loading

0 comments on commit 3e6e201

Please sign in to comment.