aboutsummaryrefslogtreecommitdiffstats
path: root/utils/engine.py
blob: e64d599fd1cc41d69c16339a8d012d5cb3641a99 (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
# 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

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


__all__ = ['MCResult', 'PricingEngine']


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


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

    Attributes
    ----------
    payoff : Payoff object for calculating the options payoff.
    path : PathGenerator object for generating the evolution of the underlying.

    Methods
    ----------
    price()

    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)

    """
    payoff: BasePayoff
    path: PathGenerator

    def price(self, T: List[float], ntrials: int = 1E4,
              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.

        Returns
        ----------
        MCResult : Price of the option.

        """
        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 = [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)