0

Currently, I've some code that looks like this, with the irrelevant methods removed.

import math
import numpy as np
from decimal import Decimal
from dataclasses import dataclass, field
from typing import Optional, List

@dataclass
class A:
    S0: int
    K: int
    r: float = 0.05
    T: int = 1
    N: int = 2
    StockTrees: List[float] = field(init=False, default_factory=list)
    pu: Optional[float] = 0
    pd: Optional[float] = 0
    div: Optional[float] = 0
    sigma: Optional[float] = 0
    is_put: Optional[bool] = field(default=False)
    is_american: Optional[bool] = field(default=False)
    is_call: Optional[bool] = field(init=False)
    is_european: Optional[bool] = field(init=False)
    
    def __post_init__(self):
        self.is_call = not self.is_put
        self.is_european = not self.is_american
        
    @property
    def dt(self):
        return self.T/float(self.N)
    
    @property
    def df(self):
        return math.exp(-(self.r - self.div) * self.dt)

@dataclass
class B(A):

    u: float = field(init=False)
    d: float = field(init=False)
    qu: float = field(init=False)
    qd: float = field(init=False)
    
    def __post_init__(self):
        super().__post_init__()
        self.u = 1 + self.pu
        self.d = 1 - self.pd
        self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu
    
    
@dataclass
class C(B):
    def __post_init__(self):
        super().__post_init__()
        self.u = math.exp(self.sigma * math.sqrt(self.dt))
        self.d = 1/self.u
        self.qu = (math.exp((self.r - self.div)*self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu

Basically, I have a class A where it defines some attributes that all of its child classes will share, so it's only really meant to be initialised via the instantiation of its child classes and its attributes are to be inherited by its child classes. The child class B is meant to be a process which does some calculation which is inherited by C which does a variation of the same calculation. C basically inherits all the methods from B and its only difference is that its calculation of self.u and self.d are different.

One can run the code by either using B calculation which requires arguments pu and pd or C calculation which requires argument sigma, as below

if __name__ == "__main__":
    
    am_option = B(50, 52, r=0.05, T=2, N=2, pu=0.2, pd=0.2, is_put=True, is_american=True)
    print(f"{am_option.sigma = }")
    print(f"{am_option.pu = }")
    print(f"{am_option.pd = }")
    print(f"{am_option.qu = }")
    print(f"{am_option.qd = }")
    
    eu_option2 = C(50, 52, r=0.05, T=2, N=2, sigma=0.3, is_put=True)
    print(f"{am_option.sigma = }")
    print(f"{am_option.pu = }")
    print(f"{am_option.pd = }")
    print(f"{am_option.qu = }")
    print(f"{am_option.qd = }")

which gives the output

am_option.pu = 0.2
am_option.pd = 0.2
am_option.qu = 0.6281777409400603
am_option.qd = 0.3718222590599397
Traceback (most recent call last):
  File "/home/dazza/option_pricer/test.py", line 136, in <module>
    eu_option2 = C(50, 52, r=0.05, T=2, N=2, sigma=0.3, is_put=True)
  File "<string>", line 15, in __init__
  File "/home/dazza/option_pricer/test.py", line 109, in __post_init__
    super().__post_init__()
  File "/home/dazza/option_pricer/test.py", line 55, in __post_init__
    self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
ZeroDivisionError: float division by zero

So instantiating B works fine since it successfully calculated the values pu,pd,qu and qd. However, my problem comes when the instantiation of C is unable to calculate qu since pu and pd are zeros by default, making it divide by 0.

My question: How can I fix this so that C inherits all the attributes initialisation (including __post_init__) of A and all methods of B, and at the same time have its calculation of self.u = math.exp(self.sigma * math.sqrt(self.dt)) and self.d = 1/self.u overwriting self.u = 1 + self.pu and self.d = 1 - self.pd of B, as well as keeping self.qu and self.qd the same?(they're the same for B and C)

9
  • Would you really want to call super().__post_init__() in class C if the calculation differs from that in class B? Commented Aug 22, 2021 at 14:18
  • What would be the alternative here? Calling A().__post_init__() instead? If I do that I get A().__post_init__() TypeError: __init__() missing 2 required positional arguments: 'S0' and 'K' and I don't know where to go from there? I'm still learning my way around OOP. Commented Aug 22, 2021 at 14:58
  • When you instantiate C you don't get a new instance of B and A - it is simply that C also has the attributes of B and A. So there is no way to overwrite self.qu on B becuase that doesn't exist. self.qu and self.qd are on C, and when you run the method you inherit from B it uses the attributes on your instance of C. Commented Aug 22, 2021 at 15:14
  • @TonySuffolk66 Instantiation of C would've inherited the attributes from A and B, including u and d of B right?. Is there not a way to write it so that instantiating C will have its u and d replacing the inherited u and d from B? Commented Aug 22, 2021 at 15:29
  • Can you set your non-init attributes independently of the other values? I question whether any of them should be instance attributes rather than (read-only) properties. Commented Aug 22, 2021 at 15:33

2 Answers 2

2

Define another method to initialize u and d, so that you can override that part of B without overriding how qu and qd are defined.

@dataclass
class B(A):

    u: float = field(init=False)
    d: float = field(init=False)
    qu: float = field(init=False)
    qd: float = field(init=False)
    
    def __post_init__(self):
        super().__post_init__()
        self._define_u_and_d()
        self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu

    def _define_u_and_d(self):
        self.u = 1 + self.pu
        self.d = 1 - self.pd



@dataclass
class C(B):
    def _define_u_and_d(self):
        self.u = math.exp(self.sigma * math.sqrt(self.dt))
        self.d = 1/self.u
Sign up to request clarification or add additional context in comments.

2 Comments

This almost looks like what I need. However, I don't think that C is inheriting self.is_call from the __post_init__ of A, since in my actual code where self.is_call is used in one of the methods of B(that gets inherited by C), it says AttributeError: 'C' object has no attribute 'is_call'
Impossible to diagnose given the code you've shown. C inherits __post_init__ from B, which calls A.__post_init__ via super.
0

Python supports multiple inheritance. You can inherit from A before B, which means any overlapping methods will be taken from A (such as __post_init__). Any code you write in class C will overwrite what's inherited from A and B. If you need to have more control over which methods come from which class, you can always define the method in C and make a function call to A or B (like A.dt(self)).

class C(A, B):
    ...

ANOTHER EDIT: I just saw that A initializes some stuff you want in C. Because C's parent is now A (if you used my code above), you can add back in the super().__post_init__() line to C's __post_init__ so that it calls A's __post_init__. If this doesn't work, you can always just put A.__post_init__(self) in the __post_init__ of C.

7 Comments

The division by 0 shouldn't be a problem if C was able to overwrite self.u and self.d of B, which is why I'm asking if there's a way to do that.
If B already inherits from A, making C inherit from A directly as well seems to be pointless to be honest.
Thanks :) . It however now gives AttributeError: 'C' object has no attribute 'is_call'. I think the problem here is that is_call is defined in the __post_init__ of A which is somehow not initiated. Should it?
Also is_call is used in one of the methods of B but I just didn't put the methods in above.
I have C inherit from A because they want everything all the methods from A, but also some methods from B, so A needs to be inherited first so B doesn't override it in C.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.