diff options
| author | Shivesh Mandalia <shivesh.mandalia@outlook.com> | 2020-04-20 00:03:47 +0100 |
|---|---|---|
| committer | Shivesh Mandalia <shivesh.mandalia@outlook.com> | 2020-04-20 00:03:47 +0100 |
| commit | 1c8649fdcd9f56cca5b191ae3cbaec4977569380 (patch) | |
| tree | 14299d85de895df774a5c2b5b44ee3d10e0f5f7f | |
| parent | 92375e2d232538c72e9dfe7d6f067dfcc7e5979f (diff) | |
| download | MCOptionPricing-1c8649fdcd9f56cca5b191ae3cbaec4977569380.tar.gz MCOptionPricing-1c8649fdcd9f56cca5b191ae3cbaec4977569380.zip | |
Linting, type checking
| -rw-r--r-- | .flake8 | 128 | ||||
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | .pylintrc | 175 | ||||
| -rwxr-xr-x | run.py | 3 | ||||
| -rw-r--r-- | utils/engine.py | 62 | ||||
| -rw-r--r-- | utils/enums.py | 1 | ||||
| -rw-r--r-- | utils/misc.py | 36 | ||||
| -rw-r--r-- | utils/path.py | 87 | ||||
| -rw-r--r-- | utils/payoff.py | 237 |
9 files changed, 516 insertions, 219 deletions
@@ -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 = @@ -2,3 +2,9 @@ __pycache__/ *.py[cod] *$py.class + +# mypy cache +.mypy_cache/ + +# flake8 log +.flake8.log @@ -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. @@ -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: |
