Q: Best practice to calculate the Delta of an option An option is a derivative contract based on the value of some underlying security (usually a stock's price). Formally, under a pricing model, the option price is a function of s0, the current underlying security price, a lot of other args (volatility, interest rate, dividend rate etc.),  and some possible kwargs.
The Delta of an option is the rate of change of its price with regard to the underlying security's price. In practice, this is implemented like 
(calc_price(s0 + ds, **kwargs) - calc_price(s0 - ds, **kwargs)) / (2 * ds)
What I want to do is implement get_delta as a method of the Option class  So now I have implemented a pricing model for the option as:
class Option:
    def __init__(self, s0, lots_of_args, **kwargs):
        self.s0 = s0

        # actually there's a list of lots of args, not packed
        self.lots_of_args = lots_of_args
        # actually kwargs are unpacked either,
        self.kwargs = kwargs

    def calc_price(self):
        price = ...
        return price

    def get_delta(self, ds=.05):
        cd = Option(self.s0 - ds, self.lots_of_args, **self.kwargs).calc_price()
        cu = Option(self.s0 + ds, self.lots_of_args, **self.kwargs).calc_price()   
        return (cu - cd) / (2 * ds)

The problem is that there are really a lot of args other than s0 (represented as lots_of_args, but actually they aren't a single arg, but there are a dozen of them), so I really hate to copy them one by one into the get_delta function. I need a way to get a dict of the args that are passed to __init__. The first thought, of course, is to use locals(). However, there're some problems:


*

*an extra self arg is included.

*kwargs are not unpacked.

*The doc on locals() advises against modifying the dict returned by locals(), so I don't think it's a good idea to del the self arg and update the unpacked kwargs.
Can anyone help? Thanks.

A: how about copying?
import copy

class Option:
    def __init__(self, s0, lots_of_args, **kwargs):
        self.s0 = s0

    def calc_price(self):
        price = ...
        return price

    def get_delta(self, ds=.05):
        cd = copy.copy(self)
        cd.s0 -= ds
        cu = copy.copy(self)
        cu.s0 += ds

        return (cu.calc_price() - cd.calc_price()) / (2 * ds)


If your class has some dict/list attrs (or attrs using other mutable classes), then you might want to use copy.deepcopy instead (for example if calc_price() changes those attributes).
And you can define __copy__ method to customize how copy is being created.

A: If you have just to copy those "lots of args", without changing them, then you can use an approach similar to namedtuple's _replace method:
>>> import collections
>>> Option = collections.namedtuple('Option', 'a b c d e f')
>>> opt = Option(a=1, b=2, c=3, d=4, e=5, f=6)
>>> opt
Option(a=1, b=2, c=3, d=4, e=5, f=6)
>>> new_opt = opt._replace(a=74)
>>> new_opt
Option(a=74, b=2, c=3, d=4, e=5, f=6)  # only 'a' changed, all other args remained the same

Then your get_delta() method would look like this:
def get_delta(self, ds=.05):
    cd = self._replace(s0=self.s0 - ds).calc_price()
    cu = self._replace(s0=self.s0 + ds).calc_price()   
    return (cu - cd) / (2 * ds)

Whether to inherit directly from a namedtuple or to create your own _replace-like method is up to you.

A: Why not modify the calc_price method to include a ds?
The get_delta then simplifies to calling calc_price with positive and negative ds:
def calc_price(self, ds=0):
    s0 = self.s0 + ds 
    price = ... return price 

def get_delta(self, ds=.05): 
    cd = self.calc_price(-ds)
    cu = self.calc_price(ds) 
    return (cu - cd) / (2 * ds) here

