Exemplo n.º 1
0
 def put_pricer(k_):
     return bs_price(forward=forward,
                     strike=k_,
                     r_counter=r_counter,
                     tau=self.tau,
                     vola=self(k_),
                     is_call=False)
Exemplo n.º 2
0
 def func_to_diff(x_):
     c_ = bs_price(strike=x_,
                   r_counter=r_counter,
                   tau=self.tau,
                   vola=self(x_),
                   forward=forward,
                   is_call=True)
     return c_
Exemplo n.º 3
0
        def main_obj(par_):
            sigma_ss = par_[3:]
            sabr_par = par_[:3]
            sabr_ = get_sabr(sabr_par)

            # functions
            def k_obj(x_):
                k_ = strike_from_delta(np.concatenate(list(zip(d, -d))),
                                       tau=tau,
                                       vola=sabr_(x_),
                                       is_call=np.array([True, False] * n),
                                       is_forward=is_forward,
                                       is_premiumadj=is_premiumadj,
                                       **data_rest)
                res__ = x_ - k_
                return res__

            # solve for market strangle strikes
            k = fsolve(k_obj, x0=np.array([
                k_atm,
            ] * 2 * n))

            sigma_x_atm = sabr_(k_atm)
            sigma_x = sabr_(k)
            sigma_x_rr = sigma_x[0::2] - sigma_x[1::2]
            sigma_x_ss = 0.5 * (sigma_x[0::2] + sigma_x[1::2]) - sigma_x_atm

            v_x = bs_price(strike=k_ms, tau=tau, vola=sabr_(k_ms),
                           is_call=np.array([True, False] * n),
                           forward=forward, r_counter=r_counter) \
                .reshape(-1, 2) \
                .sum(axis=1)

            val_model = np.concatenate(
                ([sigma_x_atm], sigma_x_rr, sigma_x_ss, v_x))
            val_observed = np.concatenate(([v_atm], sigma_rr, sigma_ss, v_tgt))
            res_ = val_model - val_observed

            return res_
Exemplo n.º 4
0
    def fit_to_fx(cls,
                  tau,
                  v_atm: float,
                  contracts: dict,
                  spot=None,
                  forward=None,
                  r_counter=None,
                  r_base=None,
                  delta_conventions=None,
                  beta=1.0):
        """Fit to FX contracts (ATM, market strangles and risk reversals).

        Fully based on the algorithm in Clark (2011), ch. 3.7.1

        Parameters
        ----------
        tau : float
            maturity, in years
        v_atm : float
            at-the-money volatility
        contracts : dict
            {delta: {'ms': float, 'rr': float}}, where
                - delta: float, in frac of 1;
                - 'ms' keys the quote of the market strangle (usually as a
                premium to the atm vola);
                - 'rr' keys the quoteof the risk reversal (call vola minus
                put vola);
            follows 'Foreign Exchange Option Pricing' by Clark (2011), ch.3
        spot : float
            spot quote
        forward : float
            forward outright quote
        r_counter : float
            risk-free rate in the counter currency, cont. comp.,
            in frac of 1 p.a.
        r_base : float
            risk-free rate in the base currency, cont. comp.,
            in frac of 1 p.a.
        delta_conventions : dict
            {'atm_def': str, 'is_forward': bool, 'is_premiumadj': bool}
        beta : float
            beta parameter in SABR
        """
        # number of different deltas
        n = len(contracts)

        # unpack conventions
        is_forward = delta_conventions["is_forward"]
        is_premiumadj = delta_conventions["is_premiumadj"]
        atm_def = delta_conventions["atm_def"]

        # pack all the other data as a shorthand
        data_rest = {
            "spot": spot,
            "forward": forward,
            "r_counter": r_counter,
            "r_base": r_base
        }

        # determine atm strike; SABR IV at this level should match `v_atm`
        k_atm = strike_from_atm(atm_def,
                                is_premiumadj=is_premiumadj,
                                spot=spot,
                                forward=forward,
                                vola=v_atm,
                                tau=tau)

        d = []  # deltas
        k_ms = []  # market strangle strikes as [d1 call, put; d2 call, put...]
        v_tgt = []  # market strangle price (premium) as [d1 v; d2 v...]
        sigma_ms = []  # market strangle quote; same
        sigma_rr = []  # risk reversal quote; same

        for d_, c_ in contracts.items():
            # for each delta, evaluate the above
            sigma_ms_ = v_atm + c_["ms"]

            # call delta is d_, put delta is -d_
            k_ms_ = strike_from_delta(delta=np.array([d_, -d_]),
                                      tau=tau,
                                      vola=sigma_ms_,
                                      is_call=np.array([True, False]),
                                      is_forward=is_forward,
                                      is_premiumadj=is_premiumadj,
                                      **data_rest)

            # market strangle price (premium) is the sum of call and put;
            v_tgt_ = bs_price(strike=k_ms_,
                              tau=tau,
                              vola=sigma_ms_,
                              is_call=np.array([True, False]),
                              forward=forward,
                              r_counter=r_counter).sum()

            d.append(d_)
            k_ms += k_ms_.tolist()
            v_tgt.append(v_tgt_)
            sigma_ms.append(sigma_ms_)
            sigma_rr.append(c_["rr"])

        # convert every list to array
        d = np.array(d)
        k_ms = np.array(k_ms)
        v_tgt = np.array(v_tgt)
        sigma_ms = np.array(sigma_ms)
        sigma_rr = np.array(sigma_rr)

        def get_sabr(par_) -> callable:
            """SABR getter.

            Parameters
            ----------
            par_: tuple
                (init_vola, volvol, rho)
            """
            res_ = SABR(forward=data_rest["forward"],
                        tau=tau,
                        beta=beta,
                        init_vola=par_[0],
                        volvol=par_[1],
                        rho=par_[2])

            return res_

        # main objective
        def main_obj(par_):
            sigma_ss = par_[3:]
            sabr_par = par_[:3]
            sabr_ = get_sabr(sabr_par)

            # functions
            def k_obj(x_):
                k_ = strike_from_delta(np.concatenate(list(zip(d, -d))),
                                       tau=tau,
                                       vola=sabr_(x_),
                                       is_call=np.array([True, False] * n),
                                       is_forward=is_forward,
                                       is_premiumadj=is_premiumadj,
                                       **data_rest)
                res__ = x_ - k_
                return res__

            # solve for market strangle strikes
            k = fsolve(k_obj, x0=np.array([
                k_atm,
            ] * 2 * n))

            sigma_x_atm = sabr_(k_atm)
            sigma_x = sabr_(k)
            sigma_x_rr = sigma_x[0::2] - sigma_x[1::2]
            sigma_x_ss = 0.5 * (sigma_x[0::2] + sigma_x[1::2]) - sigma_x_atm

            v_x = bs_price(strike=k_ms, tau=tau, vola=sabr_(k_ms),
                           is_call=np.array([True, False] * n),
                           forward=forward, r_counter=r_counter) \
                .reshape(-1, 2) \
                .sum(axis=1)

            val_model = np.concatenate(
                ([sigma_x_atm], sigma_x_rr, sigma_x_ss, v_x))
            val_observed = np.concatenate(([v_atm], sigma_rr, sigma_ss, v_tgt))
            res_ = val_model - val_observed

            return res_

        # bounds: vol and volvol are positive; -1 < rho < 1
        bounds = [(0, 0, -1, *np.zeros(n).tolist()),
                  (np.inf, np.inf, 1, *(np.ones(n) + (np.inf)).tolist())]

        # start with the default optimization method
        par_t = least_squares(main_obj,
                              x0=np.array([v_atm, v_atm, 0.0, *sigma_ms]),
                              bounds=bounds)

        # check if the solution makes sense (-1 < rho < 1);
        # if not, use levenberg-marquardt
        if (not par_t.success) or (np.abs(par_t.x[2]) > 0.99):
            par_t = least_squares(main_obj,
                                  x0=np.array([v_atm, 0.5, 0.0, *sigma_ms]),
                                  method="lm")

        # the resulting SABR
        sabr = get_sabr(par_t.x)

        # # checks
        # v_trial = bs_price(strike=k_ms, tau=tau,
        #                    vola=sabr(k_ms),
        #                    is_call=np.array([True, False] * n),
        #                    forward=data_rest["forward"],
        #                    r_counter=data_rest["r_counter"]) \
        #     .reshape(-1, 2) \
        #     .sum(axis=1)
        #
        # print("v_trial:\n")
        # print(v_trial)
        # print("v_tgt:\n")
        # print(v_tgt)

        return sabr