aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShivesh Mandalia <shivesh.mandalia@outlook.com>2020-04-20 00:03:47 +0100
committerShivesh Mandalia <shivesh.mandalia@outlook.com>2020-04-20 00:03:47 +0100
commit1c8649fdcd9f56cca5b191ae3cbaec4977569380 (patch)
tree14299d85de895df774a5c2b5b44ee3d10e0f5f7f
parent92375e2d232538c72e9dfe7d6f067dfcc7e5979f (diff)
downloadMCOptionPricing-1c8649fdcd9f56cca5b191ae3cbaec4977569380.tar.gz
MCOptionPricing-1c8649fdcd9f56cca5b191ae3cbaec4977569380.zip
Linting, type checking
-rw-r--r--.flake8128
-rw-r--r--.gitignore6
-rw-r--r--.pylintrc175
-rwxr-xr-xrun.py3
-rw-r--r--utils/engine.py62
-rw-r--r--utils/enums.py1
-rw-r--r--utils/misc.py36
-rw-r--r--utils/path.py87
-rw-r--r--utils/payoff.py237
9 files changed, 516 insertions, 219 deletions
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..b4520cd
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,128 @@
+# .flake8
+#
+# DESCRIPTION
+# Configuration file for the python linter flake8.
+#
+# This configuration is based on the generic
+# configuration published on GitHub.
+#
+# AUTHOR
+# krnd
+#
+# VERSION
+# 1.0
+#
+# SEE ALSO
+# http://flake8.pycqa.org/en/latest/user/options.html
+# http://flake8.pycqa.org/en/latest/user/error-codes.html
+# https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes
+# https://gist.github.com/krnd
+#
+
+
+[flake8]
+
+################### PROGRAM ################################
+
+# Specify the number of subprocesses that Flake8 will use to run checks in parallel.
+jobs = auto
+
+
+################### OUTPUT #################################
+
+########## VERBOSITY ##########
+
+# Increase the verbosity of Flake8’s output.
+verbose = 0
+# Decrease the verbosity of Flake8’s output.
+quiet = 0
+
+
+########## FORMATTING ##########
+
+# Select the formatter used to display errors to the user.
+format = default
+
+# Print the total number of errors.
+count = True
+# Print the source code generating the error/warning in question.
+show-source = True
+# Count the number of occurrences of each error/warning code and print a report.
+statistics = True
+
+
+########## TARGETS ##########
+
+# Redirect all output to the specified file.
+output-file = .flake8.log
+# Also print output to stdout if output-file has been configured.
+tee = True
+
+
+################### FILE PATTERNS ##########################
+
+# Provide a comma-separated list of glob patterns to exclude from checks.
+exclude =
+ # git folder
+ .git,
+ # python cache
+ __pycache__,
+# Provide a comma-separate list of glob patterns to include for checks.
+filename =
+ *.py
+
+
+################### LINTING ################################
+
+########## ENVIRONMENT ##########
+
+# Provide a custom list of builtin functions, objects, names, etc.
+builtins =
+
+
+########## OPTIONS ##########
+
+# Report all errors, even if it is on the same line as a `# NOQA` comment.
+disable-noqa = False
+
+# Set the maximum length that any line (with some exceptions) may be.
+max-line-length = 100
+# Set the maximum allowed McCabe complexity value for a block of code.
+max-complexity = 10
+# Toggle whether pycodestyle should enforce matching the indentation of the opening bracket’s line.
+# incluences E131 and E133
+hang-closing = True
+
+
+########## RULES ##########
+
+# ERROR CODES
+#
+# E/W - PEP8 errors/warnings (pycodestyle)
+# F - linting errors (pyflakes)
+# C - McCabe complexity error (mccabe)
+#
+# W503 - line break before binary operator
+
+# Specify a list of codes to ignore.
+ignore =
+ W503
+# Specify the list of error codes you wish Flake8 to report.
+select =
+ E,
+ W,
+ F,
+ C
+# Enable off-by-default extensions.
+enable-extensions =
+
+
+########## DOCSTRING ##########
+
+# Enable PyFlakes syntax checking of doctests in docstrings.
+doctests = True
+
+# Specify which files are checked by PyFlakes for doctest syntax.
+include-in-doctest =
+# Specify which files are not to be checked by PyFlakes for doctest syntax.
+exclude-in-doctest =
diff --git a/.gitignore b/.gitignore
index 749ccda..3721f1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,9 @@
__pycache__/
*.py[cod]
*$py.class
+
+# mypy cache
+.mypy_cache/
+
+# flake8 log
+.flake8.log
diff --git a/.pylintrc b/.pylintrc
index abc3182..787b604 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -51,6 +51,13 @@ unsafe-load-any-extension=no
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+# enable=c-extension-no-member
+enable=all
+
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
@@ -61,92 +68,86 @@ confidence=
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=invalid-name,
- import-outside-toplevel,
- print-statement,
- parameter-unpacking,
- unpacking-in-except,
- old-raise-syntax,
- backtick,
- long-suffix,
- old-ne-operator,
- old-octal-literal,
- import-star-module-level,
- non-ascii-bytes-literal,
- raw-checker-failed,
- bad-inline-option,
- locally-disabled,
- file-ignored,
- suppressed-message,
- useless-suppression,
- deprecated-pragma,
- use-symbolic-message-instead,
- apply-builtin,
- basestring-builtin,
- buffer-builtin,
- cmp-builtin,
- coerce-builtin,
- execfile-builtin,
- file-builtin,
- long-builtin,
- raw_input-builtin,
- reduce-builtin,
- standarderror-builtin,
- unicode-builtin,
- xrange-builtin,
- coerce-method,
- delslice-method,
- getslice-method,
- setslice-method,
- no-absolute-import,
- old-division,
- dict-iter-method,
- dict-view-method,
- next-method-called,
- metaclass-assignment,
- indexing-exception,
- raising-string,
- reload-builtin,
- oct-method,
- hex-method,
- nonzero-method,
- cmp-method,
- input-builtin,
- round-builtin,
- intern-builtin,
- unichr-builtin,
- map-builtin-not-iterating,
- zip-builtin-not-iterating,
- range-builtin-not-iterating,
- filter-builtin-not-iterating,
- using-cmp-argument,
- eq-without-hash,
- div-method,
- idiv-method,
- rdiv-method,
- exception-message-attribute,
- invalid-str-codec,
- sys-max-int,
- bad-python3-import,
- deprecated-string-function,
- deprecated-str-translate-call,
- deprecated-itertools-function,
- deprecated-types-field,
- next-method-defined,
- dict-items-not-iterating,
- dict-keys-not-iterating,
- dict-values-not-iterating,
- deprecated-operator-function,
- deprecated-urllib-function,
- xreadlines-attribute,
- deprecated-sys-function,
- exception-escape,
- comprehension-escape
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-enable=c-extension-no-member
+# import-outside-toplevel,
+# print-statement,
+# parameter-unpacking,
+# unpacking-in-except,
+# old-raise-syntax,
+# backtick,
+# long-suffix,
+# old-ne-operator,
+# old-octal-literal,
+# import-star-module-level,
+# non-ascii-bytes-literal,
+# raw-checker-failed,
+# bad-inline-option,
+# locally-disabled,
+# file-ignored,
+# suppressed-message,
+# useless-suppression,
+# deprecated-pragma,
+# use-symbolic-message-instead,
+# apply-builtin,
+# basestring-builtin,
+# buffer-builtin,
+# cmp-builtin,
+# coerce-builtin,
+# execfile-builtin,
+# file-builtin,
+# long-builtin,
+# raw_input-builtin,
+# reduce-builtin,
+# standarderror-builtin,
+# unicode-builtin,
+# xrange-builtin,
+# coerce-method,
+# delslice-method,
+# getslice-method,
+# setslice-method,
+# no-absolute-import,
+# old-division,
+# dict-iter-method,
+# dict-view-method,
+# next-method-called,
+# metaclass-assignment,
+# indexing-exception,
+# raising-string,
+# reload-builtin,
+# oct-method,
+# hex-method,
+# nonzero-method,
+# cmp-method,
+# input-builtin,
+# round-builtin,
+# intern-builtin,
+# unichr-builtin,
+# map-builtin-not-iterating,
+# zip-builtin-not-iterating,
+# range-builtin-not-iterating,
+# filter-builtin-not-iterating,
+# using-cmp-argument,
+# eq-without-hash,
+# div-method,
+# idiv-method,
+# rdiv-method,
+# exception-message-attribute,
+# invalid-str-codec,
+# sys-max-int,
+# bad-python3-import,
+# deprecated-string-function,
+# deprecated-str-translate-call,
+# deprecated-itertools-function,
+# deprecated-types-field,
+# next-method-defined,
+# dict-items-not-iterating,
+# dict-keys-not-iterating,
+# dict-values-not-iterating,
+# deprecated-operator-function,
+# deprecated-urllib-function,
+# xreadlines-attribute,
+# deprecated-sys-function,
+# exception-escape,
+# comprehension-escape
[REPORTS]
@@ -350,7 +351,7 @@ min-similarity-lines=4
# Format style used to check logging format string. `old` means using %
# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings.
-logging-format-style=old
+logging-format-style=new
# Logging modules to check that the string format arguments are in logging
# function parameter format.
diff --git a/run.py b/run.py
index 6a280e8..e8ec3f1 100755
--- a/run.py
+++ b/run.py
@@ -210,11 +210,10 @@ options, and thus this is priced in.''')
def main() -> None:
- """Main function."""
random.seed(1)
# Define number of trials to run
- ntrials_arr = [1E4, 1E5, 1E6]
+ ntrials_arr = list(map(int, [1e4, 1e5, 1e6]))
# Pricing Asian options
asian_options(ntrials_arr)
diff --git a/utils/engine.py b/utils/engine.py
index e64d599..f4169ec 100644
--- a/utils/engine.py
+++ b/utils/engine.py
@@ -10,8 +10,9 @@ Pricing engine for exotic options.
import math
from statistics import mean, stdev
from dataclasses import dataclass
-from typing import List
+from typing import List, Sequence
+from utils.misc import Number
from utils.path import PathGenerator
from utils.payoff import BasePayoff
@@ -23,6 +24,14 @@ __all__ = ['MCResult', 'PricingEngine']
class MCResult:
"""
Price of option along with its MC error.
+
+ Attributes
+ ----------
+ price : float
+ Spot Price.
+ path : float
+ MC standard error.
+
"""
price: float
stderr: float
@@ -35,42 +44,65 @@ class PricingEngine:
Attributes
----------
- payoff : Payoff object for calculating the options payoff.
- path : PathGenerator object for generating the evolution of the underlying.
+ payoff : BasePayoff
+ Payoff object for calculating the options payoff.
+ path : PathGenerator
+ PathGenerator object for generating the evolution of the underlying.
Methods
- ----------
+ -------
price()
+ Price the option using MC techniques.
Examples
- ----------
+ --------
>>> from utils.engine import PricingEngine
>>> from utils.path import PathGenerator
>>> from utils.payoff import AsianArithmeticPayOff
>>> path = PathGenerator(S=100., r=0.1, div=0.01, vol=0.3)
>>> payoff = AsianArithmeticPayOff(option_right='Call', K=110)
>>> engine = PricingEngine(payoff=payoff, path=path)
- >>> print(engine.price(T=range(4)))
- MCResult(price=12.003704847790525, stderr=0.2327352760696234)
+ >>> print(engine)
+ PricingEngine(payoff=AsianArithmeticPayOff(K=110, option_right=Call),
+ path=PathGenerator(S=100.0, r=0.1, div=0.01, vol=0.3))
"""
payoff: BasePayoff
path: PathGenerator
- def price(self, T: List[float], ntrials: int = 1E4,
- antithetic: bool = True) -> MCResult:
+ def price(
+ self,
+ T: Sequence[Number],
+ ntrials: int = 10_000,
+ antithetic: bool = True
+ ) -> MCResult:
"""
Price the option using MC techniques.
Parameters
----------
- T : Set of times {t1, t2, ..., tn} in years.
- ntrials : Number of trials to simulate.
- antithetic : Use antithetic variates technique.
+ T : Sequence of Numbers
+ Set of times {t1, t2, ..., tn} in years.
+ ntrials : int
+ Number of trials to simulate.
+ antithetic : bool
+ Use antithetic variates technique.
Returns
- ----------
- MCResult : Price of the option.
+ -------
+ MCResult
+ Price of the option.
+
+ Examples
+ --------
+ >>> from utils.engine import PricingEngine
+ >>> from utils.path import PathGenerator
+ >>> from utils.payoff import AsianArithmeticPayOff
+ >>> path = PathGenerator(S=100., r=0.1, div=0.01, vol=0.3)
+ >>> payoff = AsianArithmeticPayOff(option_right='Call', K=110)
+ >>> engine = PricingEngine(payoff=payoff, path=path)
+ >>> print(engine.price(T=range(4)))
+ MCResult(price=12.003704847790525, stderr=0.2327352760696234)
"""
if ntrials < len(T):
@@ -79,7 +111,7 @@ class PricingEngine:
# Generation start
ntrials = int(ntrials // len(T))
- payoffs = [0] * ntrials
+ payoffs: List[float] = [0] * ntrials
for idx in range(ntrials):
# Generate a random path
if not antithetic:
diff --git a/utils/enums.py b/utils/enums.py
index 6a607ea..aed4a97 100644
--- a/utils/enums.py
+++ b/utils/enums.py
@@ -9,6 +9,7 @@ Enumeration utility classes.
from enum import Enum, auto
+
__all__ = ['OptionRight', 'BarrierUpDown', 'BarrierInOut']
diff --git a/utils/misc.py b/utils/misc.py
index 708d759..f7743d1 100644
--- a/utils/misc.py
+++ b/utils/misc.py
@@ -7,24 +7,31 @@
Miscellaneous utility methods.
"""
+from typing import Any, Union
-__all__ = ['is_num', 'is_pos']
+__all__ = ['Number', 'is_num', 'is_pos']
-def is_num(val: (int, float)) -> bool:
+
+_T = (int, float)
+Number = Union[int, float]
+
+
+def is_num(val: Any) -> bool:
"""
- Check if the input value is a non-infinite number.
+ Check if the input value is a finite number.
Parameters
----------
- val : Value to check.
+ val : object
+ Value to check.
Returns
- ----------
- is_num : Whether it is a non-infinite number.
+ -------
+ bool
Examples
- ----------
+ --------
>>> from utils.misc import is_num
>>> print(is_num(10))
True
@@ -32,25 +39,26 @@ def is_num(val: (int, float)) -> bool:
False
"""
- if not isinstance(val, (int, float)):
+ if not isinstance(val, _T):
return False
return True
-def is_pos(val: (int, float)) -> bool:
+def is_pos(val: Any) -> bool:
"""
- Check if the input value is a non-infinite positive number.
+ Check if the input value is a finite positive number.
Parameters
----------
- val : Value to check.
+ val : object
+ Value to check.
Returns
- ----------
- is_pos : Whether it is a non-infinite positive number.
+ -------
+ bool
Examples
- ----------
+ --------
>>> from utils.misc import is_pos
>>> print(is_pos(10))
True
diff --git a/utils/path.py b/utils/path.py
index 2fbb48f..41474ef 100644
--- a/utils/path.py
+++ b/utils/path.py
@@ -11,7 +11,9 @@ import math
import random
from copy import deepcopy
from dataclasses import dataclass
-from typing import List, Tuple
+from typing import List, Sequence, Tuple
+
+from utils.misc import Number
__all__ = ['PathGenerator']
@@ -24,14 +26,18 @@ class PathGenerator:
Attributes
----------
- S : Spot price.
- r : Risk-free interest rate.
- div : Dividend yield.
- vol : Volatility.
- net_r : Net risk free rate.
+ S : Number
+ Spot price.
+ r : Number
+ Risk-free interest rate.
+ div : Number
+ Dividend yield.
+ vol : Number
+ Volatility.
+ net_r
Methods
- ----------
+ -------
generate(T)
Generate a random path {S_t1, S_t2, ..., S_tn}.
generate_antithetic(T)
@@ -39,42 +45,51 @@ class PathGenerator:
[{S_t1, S_t2, ..., S_tn}, {S'_t1, S'_t2, ..., S'_tn}].
Examples
- ----------
+ --------
>>> from utils.path import PathGenerator
>>> path = PathGenerator(S=100., r=0.1, div=0.01, vol=0.3)
- >>> print(path.generate(T=range(4)))
- [100.0, 100.33539853588853, 122.76017088387074, 142.29540684005462]
- >>> print(path.generate(T=range(4)))
- [100.0, 73.03094019139712, 77.37310245438943, 66.54240939439934]
+ >>> print(path)
+ PathGenerator(S=100.0, r=0.1, div=0.01, vol=0.3)
"""
- S: float
- r: float
- div: float
- vol: float
+ S: Number
+ r: Number
+ div: Number
+ vol: Number
@property
def net_r(self) -> float:
"""Net risk free rate."""
- return self.r - self.div
+ return float(self.r - self.div)
- def generate(self, T: List[float]) -> List[float]:
+ def generate(self, T: Sequence[Number]) -> List[float]:
"""
Generate a random path {S_t1, S_t2, ..., S_tn}.
Parameters
----------
- T : Set of times {t1, t2, ..., tn} in years.
+ T : Sequence of Numbers
+ Set of times {t1, t2, ..., tn} in years.
Returns
- ----------
- spot_prices : Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
+ -------
+ spot_prices : List of floats
+ Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
+
+ Examples
+ --------
+ >>> from utils.path import PathGenerator
+ >>> path = PathGenerator(S=100., r=0.1, div=0.01, vol=0.3)
+ >>> print(path.generate(T=range(4)))
+ [100.0, 100.33539853588853, 122.76017088387074, 142.29540684005462]
+ >>> print(path.generate(T=range(4)))
+ [100.0, 73.03094019139712, 77.37310245438943, 66.54240939439934]
"""
# Calculate dt time differences
dts = [T[idx + 1] - T[idx] for idx in range(len(T) - 1)]
- spot_prices = [0] * len(T)
+ spot_prices: List[float] = [0] * len(T)
spot_prices[0] = self.S
for idx, dt in enumerate(dts):
# Calculate the drift e^{(r - (1/2) σ²) Δt}
@@ -89,27 +104,41 @@ class PathGenerator:
spot_prices[idx + 1] = S_t
return spot_prices
- def generate_antithetic(self, T: List[float]) -> Tuple[List[float],
- List[float]]:
+ def generate_antithetic(
+ self, T: Sequence[Number]
+ ) -> Tuple[List[float], List[float]]:
"""
Generate a random plus antithetic path
[{S_t1, S_t2, ..., S_tn}, {S'_t1, S'_t2, ..., S'_tn}].
Parameters
----------
- T : Set of times {t1, t2, ..., tn} in years.
+ T : Sequence of Numbers
+ Set of times {t1, t2, ..., tn} in years.
Returns
- ----------
- prices_tuple : Set of prices for the underlying
- [{S_t1, S_t2, ..., S_tn}, {S'_t1, S'_t2, ..., S'_tn}].
+ -------
+ prices_tuple : Tuple of two Lists of floats
+ Set of prices for the underlying
+ [{S_t1, S_t2, ..., S_tn}, {S'_t1, S'_t2, ..., S'_tn}].
+
+ Examples
+ --------
+ >>> from utils.path import PathGenerator
+ >>> path = PathGenerator(S=100., r=0.1, div=0.01, vol=0.3)
+ >>> print(path.generate_antithetic(T=range(4)))
+ ([100.0, 106.30304144532359, 122.02501765852367, 108.71120035365013],
+ [100.0, 102.92972513566257, 98.11245153613649, 120.49949282794978])
+ >>> print(path.generate_antithetic(T=range(4)))
+ ([100.0, 130.1595970941697, 103.35241529329348, 93.58800177914543],
+ [100.0, 84.06404968460234, 115.83835362960282, 139.97140935058962])
"""
# Calculate dt time differences
dts = [T[idx + 1] - T[idx] for idx in range(len(T) - 1)]
# Create data structures
- spot_prices = [0] * len(T)
+ spot_prices: List[float] = [0] * len(T)
spot_prices[0] = self.S
a_spot_prices = deepcopy(spot_prices)
diff --git a/utils/payoff.py b/utils/payoff.py
index fffbdba..7edeb87 100644
--- a/utils/payoff.py
+++ b/utils/payoff.py
@@ -8,21 +8,36 @@ Payoff of an option.
"""
from abc import ABC, abstractmethod
-from dataclasses import dataclass
-from typing import List
+from typing import AnyStr, List, Union, Sequence
from utils.enums import OptionRight, BarrierUpDown, BarrierInOut
+from utils.misc import Number
-__all__ = ['BasePayoff', 'VanillaPayOff', 'AsianArithmeticPayOff',
- 'DiscreteBarrierPayOff']
+__all__ = [
+ 'BasePayoff',
+ 'VanillaPayOff',
+ 'AsianArithmeticPayOff',
+ 'DiscreteBarrierPayOff'
+]
-@dataclass
class BasePayoff(ABC):
"""Base class for calculating the payoff."""
- K: float
- option_right: (str, OptionRight)
+
+ def __init__(self, K: Number,
+ option_right: Union[AnyStr, OptionRight]) -> None:
+ self.K: Number = K
+ # https://github.com/python/mypy/issues/3004
+ self.option_right = option_right # type: ignore
+
+ def __repr__(self) -> str:
+ return (
+ f'{self.__class__.__name__}('
+ f'K={self.K!r}, '
+ f'option_right={self.option_right!r}'
+ ')'
+ )
@property
def option_right(self) -> OptionRight:
@@ -30,7 +45,7 @@ class BasePayoff(ABC):
return self._option_right
@option_right.setter
- def option_right(self, val: (str, OptionRight)) -> None:
+ def option_right(self, val: Union[AnyStr, OptionRight]) -> None:
"""Set the option_right of the option."""
if isinstance(val, str):
if not hasattr(OptionRight, val):
@@ -44,17 +59,43 @@ class BasePayoff(ABC):
f'Expected str or OptionRight, instead got type {type(val)}!'
)
- def _calculate_call(self, S: float) -> float:
- """Call option."""
- return max(S - self.K, 0.)
+ def _calculate_call(self, S: Number) -> float:
+ """
+ Price call option for a given underlying price.
+
+ Parameters
+ ----------
+ S : Number
+ Price of underlying.
+
+ Returns
+ -------
+ float
+ Price of the call option.
+
+ """
+ return float(max(S - self.K, 0.0))
+
+ def _calculate_put(self, S: Number) -> float:
+ """
+ Price put option for a given underlying price.
+
+ Parameters
+ ----------
+ S : Number
+ Price of underlying.
- def _calculate_put(self, S: float) -> float:
- """Put option."""
- return max(self.K - S, 0.)
+ Returns
+ -------
+ float
+ Price of the put option.
+
+ """
+ return float(max(self.K - S, 0.0))
@abstractmethod
- def calculate(self, S: float) -> float:
- """Calulate the payoff for a given spot."""
+ def calculate(self, S: Sequence[Number]) -> float:
+ """Calulate the payoff for a given path."""
class VanillaPayOff(BasePayoff):
@@ -63,46 +104,56 @@ class VanillaPayOff(BasePayoff):
Attributes
----------
- option_right : Right of the option.
- K : Strike price.
+ option_right : OptionRight
+ Right of the option.
+ K : Number
+ Strike price.
Methods
- ----------
+ -------
calculate(S)
- Calulate the payoff given a spot price.
+ Calulate the payoff for a given path.
Examples
- ----------
+ --------
>>> from utils.payoff import VanillaPayOff
>>> payoff = VanillaPayOff(option_right='Call', K=150.)
- >>> print(payoff.calculate(160.))
- 10.0
+ >>> print(payoff)
+ VanillaPayOff(K=150.0, option_right=Call)
"""
- def calculate(self, S: (float, List[float])) -> float:
+ def calculate(self, S: Sequence[Number]) -> float:
"""
- Calulate the payoff given a spot price.
+ Calulate the payoff for a given path.
Parameters
----------
- S : Spot price or list of spot prices.
+ S : Sequence of Numbers
+ Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
Returns
- ----------
- payoff : Payoff.
+ -------
+ payoff : float
+ Payoff.
Notes
- ----------
- If a list is given as input, the final entry will be taken to evaluate.
+ -----
+ The final entry will be taken to evaluate the payoff.
+
+ Examples
+ --------
+ >>> from utils.payoff import VanillaPayOff
+ >>> payoff = VanillaPayOff(option_right='Call', K=150.)
+ >>> print(payoff.calculate([160.]))
+ 10.0
"""
- if not isinstance(S, float):
- S = S[-1]
+ # Calculate payoff using final price
if self.option_right == OptionRight.Call:
- payoff = self._calculate_call(S)
+ payoff = self._calculate_call(S[-1])
else:
- payoff = self._calculate_put(S)
+ payoff = self._calculate_put(S[-1])
return payoff
@@ -112,34 +163,45 @@ class AsianArithmeticPayOff(BasePayoff):
Attributes
----------
- option_right : Right of the option.
- K : Strike price.
+ option_right : OptionRight
+ Right of the option.
+ K : Number
+ Strike price.
Methods
- ----------
+ -------
calculate(S)
Calulate the payoff given a set of prices for the underlying.
Examples
- ----------
+ --------
>>> from utils.payoff import AsianArithmeticPayOff
>>> payoff = AsianArithmeticPayOff(option_right='Call', K=150)
- >>> print(payoff.calculate([140, 150, 160, 170, 180]))
- 10.0
+ >>> print(payoff)
+ AsianArithmeticPayOff(K=150, option_right=Call)
"""
- def calculate(self, S: List[float]) -> float:
+ def calculate(self, S: Sequence[Number]) -> float:
"""
Calulate the payoff given a set of prices for the underlying.
Parameters
----------
- S : Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
+ S : Sequence of Numbers
+ Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
Returns
- ----------
- payoff : Payoff.
+ -------
+ payoff : float
+ Payoff.
+
+ Examples
+ --------
+ >>> from utils.payoff import AsianArithmeticPayOff
+ >>> payoff = AsianArithmeticPayOff(option_right='Call', K=150)
+ >>> print(payoff.calculate([140, 150, 160, 170, 180]))
+ 10.0
"""
avg_sum = sum(S) / len(S)
@@ -150,7 +212,6 @@ class AsianArithmeticPayOff(BasePayoff):
return payoff
-@dataclass(init=False)
class DiscreteBarrierPayOff(BasePayoff):
"""
Class for calculating the payoff of a discrete barrier European style
@@ -158,39 +219,56 @@ class DiscreteBarrierPayOff(BasePayoff):
Attributes
----------
- option_right : Right of the option.
- K : Strike price.
- B : Barrier price.
- barrier_updown : Up or down type barrier option.
- barrier_inout : In or out type barrier option.
+ option_right : OptionRight
+ Right of the option.
+ K : Number
+ Strike price.
+ B : Number
+ Barrier price.
+ barrier_updown : BarrierUpDown
+ Up or down type barrier option.
+ barrier_inout : BarrierInOut
+ In or out type barrier option.
Methods
- ----------
+ -------
calculate(S)
Calulate the payoff given a set of prices for the underlying.
Examples
- ----------
+ --------
>>> from utils.payoff import DiscreteBarrierPayOff
- >>> payoff = DiscreteBarrierPayOff(option_right='Call', K=100, B=90,
+ >>> payoff = DiscreteBarrierPayOff(option_right='Call', K=100, B=90, \
barrier_updown='Down', barrier_inout='Out')
- >>> print(payoff.calculate([100., 110., 120.]))
- 20.0
- >>> print(payoff.calculate([100., 110., 120., 80., 110.]))
- 0.0
+ >>> print(payoff)
+ DiscreteBarrierPayOff(K=100, option_right=Call, B=90, barrier_updown=Down, barrier_inout=Out)
"""
- B: float
- barrier_updown: (str, BarrierUpDown)
- barrier_inout: (str, BarrierInOut)
- def __init__(self, option_right: (str, OptionRight), K: float, B: float,
- barrier_updown: (str, BarrierUpDown),
- barrier_inout: (str, BarrierInOut)):
+ def __init__(
+ self,
+ option_right: Union[AnyStr, OptionRight],
+ K: Number,
+ B: Number,
+ barrier_updown: Union[AnyStr, BarrierUpDown],
+ barrier_inout: Union[AnyStr, BarrierInOut]
+ ) -> None:
super().__init__(K, option_right)
- self.B = B
- self.barrier_updown = barrier_updown
- self.barrier_inout = barrier_inout
+ self.B: Number = B
+ # https://github.com/python/mypy/issues/3004
+ self.barrier_updown = barrier_updown # type: ignore
+ self.barrier_inout = barrier_inout # type: ignore
+
+ def __repr__(self) -> str:
+ return (
+ f'{self.__class__.__name__}('
+ f'K={self.K!r}, '
+ f'option_right={self.option_right!r}, '
+ f'B={self.B!r}, '
+ f'barrier_updown={self.barrier_updown!r}, '
+ f'barrier_inout={self.barrier_inout!r}'
+ ')'
+ )
@property
def barrier_updown(self) -> BarrierUpDown:
@@ -198,7 +276,7 @@ class DiscreteBarrierPayOff(BasePayoff):
return self._barrier_updown
@barrier_updown.setter
- def barrier_updown(self, val: (str, BarrierUpDown)) -> None:
+ def barrier_updown(self, val: Union[AnyStr, BarrierUpDown]) -> None:
"""Set either up or down type barrier option."""
if isinstance(val, str):
if not hasattr(BarrierUpDown, val):
@@ -218,7 +296,7 @@ class DiscreteBarrierPayOff(BasePayoff):
return self._barrier_inout
@barrier_inout.setter
- def barrier_inout(self, val: (str, BarrierInOut)) -> None:
+ def barrier_inout(self, val: Union[AnyStr, BarrierInOut]) -> None:
"""Set either up or down type barrier option."""
if isinstance(val, str):
if not hasattr(BarrierInOut, val):
@@ -232,32 +310,47 @@ class DiscreteBarrierPayOff(BasePayoff):
f'Expected str or BarrierInOut, instead got type {type(val)}!'
)
- def calculate(self, S: List[float]) -> float:
+ def calculate(self, S: Sequence[Number]) -> float:
"""
Calulate the payoff given a set of prices for the underlying.
Parameters
----------
- S : Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
+ S : Sequence of Numbers
+ Set of prices for the underlying {S_t1, S_t2, ..., S_tn}.
Returns
- ----------
- payoff : Payoff.
+ -------
+ payoff : float
+ Payoff.
+
+ Examples
+ --------
+ >>> from utils.payoff import DiscreteBarrierPayOff
+ >>> payoff = DiscreteBarrierPayOff(option_right='Call', K=100, B=90, \
+ barrier_updown='Down', barrier_inout='Out')
+ >>> print(payoff.calculate([100., 110., 120.]))
+ 20.0
+ >>> print(payoff.calculate([100., 110., 120., 80., 110.]))
+ 0.0
"""
# Calculate the heavyside
+ H: List[int]
if self.barrier_updown == BarrierUpDown.Up:
H = [1 if self.B - x > 0 else 0 for x in S]
else:
H = [1 if x - self.B > 0 else 0 for x in S]
# Calculate whether it has been activated
+ activation: int
if self.barrier_inout == BarrierInOut.In:
activation = 1 if min(H) == 0 else 0
else:
activation = min(H)
# Calculate payoff using final price
+ payoff: float
if self.option_right == OptionRight.Call:
payoff = activation * self._calculate_call(S[-1])
else: