aboutsummaryrefslogtreecommitdiffstats
path: root/utils/engine.py
blob: f4169ec2f16b49cb73f16e4ab247461bc2bdf3ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# author : S. Mandalia
#          shivesh.mandalia@outlook.com
#
# date   : March 19, 2020

"""
Pricing engine for exotic options.
"""

import math
from statistics import mean, stdev
from dataclasses import dataclass
from typing import List, Sequence

from utils.misc import Number
from utils.path import PathGenerator
from utils.payoff import BasePayoff


__all__ = ['MCResult', 'PricingEngine']


@dataclass
class MCResult:
    """
    Price of option along with its MC error.

    Attributes
    ----------
    price : float
        Spot Price.
    path : float
        MC standard error.

    """
    price: float
    stderr: float


@dataclass
class PricingEngine:
    """
    Class for generating underlying prices using MC techniques.

    Attributes
    ----------
    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)
    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: Sequence[Number],
            ntrials: int = 10_000,
            antithetic: bool = True
    ) -> MCResult:
        """
        Price the option using MC techniques.

        Parameters
        ----------
        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.

        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):
            raise AssertionError('Number of trials cannot be less than the '
                                 'number of setting dates!')

        # Generation start
        ntrials = int(ntrials // len(T))
        payoffs: List[float] = [0] * ntrials
        for idx in range(ntrials):
            # Generate a random path
            if not antithetic:
                spot_prices = self.path.generate(T)
            else:
                prices_tuple = self.path.generate_antithetic(T)
                spot_prices, a_spot_prices = prices_tuple

            # Calculate the payoff
            payoff = self.payoff.calculate(spot_prices)
            if antithetic:
                a_po = self.payoff.calculate(a_spot_prices)
                payoff = (payoff + a_po) / 2
            payoffs[idx] = payoff

        # Discount to current time
        df = math.exp(-self.path.net_r * (T[-1] - T[0]))
        dis_payoffs = [x * df for x in payoffs]

        # Payoff expectation and standard error
        exp_payoff = mean(dis_payoffs)
        stderr = stdev(dis_payoffs, exp_payoff) / math.sqrt(ntrials)

        return MCResult(exp_payoff, stderr)