#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
import math
import numpy as np
from scipy.stats import norm

from pricelib.common.utilities.enums import CallPut, InOut, UpDown, ExerciseType, PaymentType
from pricelib.common.processes import StochProcessBase
from pricelib.common.pricing_engine_base.analytic_engine_base import (AnalyticEngine, bs_formula, euro_cash_or_nothing,
                                                                      euro_asset_or_nothing)
from pricelib.common.date_handler import global_evaluation_date
from .analytic_barrier_engine import AnalyticBarrierEngine


class AnalyticDoubleBarrierEngine(AnalyticEngine):
    """双边障碍期权闭式解定价引擎
    欧式：
        可以由欧式资产或无和欧式现金或无期权的解析解组合而成，亦支持年化的敲入/未敲出现金返还和保底收益率。
    美式：
        双边敲出期权，到期时间 T 之前，满足L<S<U，则到期回报为max(S-K,0)，否则到期回报为0。双边敲入期权等于香草期权多头与双边敲出期权空头的组合。
        注意: 下面的两种美式解析解，都不支持立即支付；都没有现金返还，即rebate=0；也不支持保底收益率，即floor_yield=0，
        Haug(1998) Put-Call Barrier Transformations 对于标的持有成本为0的期权(期货期权)，利用认购-认沽障碍期权对称性，
                                                    利用单边障碍期权解析解的组合给双障碍期权定价。
        Ikeda and Kunitomo(1992)将双障碍期权表示为加权正态分布函数的无限集，但是仅当行权价在障碍范围内时，公式才是成立的。
        Haug(1998)的解析解与Ikeda and Kunitomo(1992)的结果几乎一致，两者对比，
            Ikeda and Kunitomo(1992)适用于标的持有成本不为0的情形, Haug(1998)适用于行权价在障碍范围之外的情形。
    """

    def __init__(self, stoch_process: StochProcessBase = None, formula_type="Ikeda&Kunitomo1992",
                 series_num=10, delta1=0, delta2=0, *,
                 s=None, r=None, q=None, vol=None):
        """
        Args:
            stoch_process: 随机过程StochProcessBase对象
            self.formula_type: 解析解类型，Ikeda&Kunitomo1992或Haug1998
        其他三个参数是Ikeda&Kunitomo1992的参数：
            self.series_num: 用于计算近似解的级数项 默认 i = -10 ~ 10
            self.delta1: 上边界的曲率
            self.delta2: 下边界的曲率，
                当delta1 = delta2 = 0时，对应两条平行边界
                当delta1 < 0 < delta2 时，对应下边界随时间呈指数增长，而上百年姐随时间呈指数衰减
                当delta1 > 0 > delta2 时，对应一个向下凸的下边界和一个向上凸的上边界
        在未设置stoch_process时，(stoch_process=None)，会默认创建BSMprocess，需要输入以下变量进行初始化
            s: float，标的价格
            r: float，无风险利率
            q: float，分红/融券率
            vol: float，波动率
        """
        super().__init__(stoch_process=stoch_process, s=s, r=r, q=q, vol=vol)
        self.formula_type = formula_type
        self.series_num = series_num
        self.i_vec = np.array(range(- series_num, series_num + 1)).astype(float)  # 用于计算近似解的级数项
        self.delta1 = delta1
        self.delta2 = delta2
        # 以下是计算过程的中间变量
        self.prod = None  # Product产品对象

    # pylint: disable=invalid-name, too-many-locals
    def calc_present_value(self, prod, t=None, spot=None):
        """计算现值
        Args:
            prod: Product产品对象
            t: datetime.date，估值日; 如果是None，则使用全局估值日globalEvaluationDate
            spot: float，估值日标的价格，如果是None，则使用随机过程的当前价格
        Returns: float，现值
        """
        self.prod = prod
        calculate_date = global_evaluation_date() if t is None else t
        tau = prod.trade_calendar.business_days_between(calculate_date,
                                                        prod.end_date) / prod.t_step_per_year  # 从估值日到到期日的交易日时间（年）
        tenor = (prod.stop_date - prod.start_date).days / prod.annual_days.value  # 从起始日到到期日的自然日时间（年）
        # 保底收益率
        _floor_yield = 0
        if prod.floor_yield != 0:  # 保底收益率需要输入期初价格s0作为名义本金
            _floor_yield = prod.floor_yield * tenor * prod.s0

        if spot is None:
            spot = self.process.spot()
        r = self.process.interest(tau)
        q = self.process.div(tau)
        vol = self.process.vol(tau, spot)

        if calculate_date == prod.stop_date:  # 如果估值日就是终止日，则直接计算payoff
            value = self.calc_maturity_payoff(prod, np.array([spot]), tenor, _floor_yield)[0]
            return value

        if prod.obs_type == ExerciseType.European:  # 如果是欧式障碍期权（到期观察）
            return self._calc_european_barrier(prod, spot, tau, r, q, vol, tenor, _floor_yield)
        elif prod.obs_type == ExerciseType.American:  # 如果是美式障碍期权
            assert prod.payment_type != PaymentType.Hit, "Error: 美式双边障碍期权解析解不支持立即支付的情形，只支持到期支付。"
            assert (prod.rebate[0] == 0 and prod.rebate[1] == 0 and prod.floor_yield == 0
                    ), ("美式双边障碍期权解析解不支持现金返还和保底收益率，可以尝试使用双鲨期权解析解定价引擎，"
                        "双鲨使用了美式双边障碍期权解析解+美式双接触/双不接触解析解的组合，支持了现金返还和保底收益率")
            # 由于不支持现金返还，也就无所谓支付方式payment_type是立即支付还是到期支付了
            if prod.discrete_obs_interval is not None:
                # 均匀离散观察，M. Broadie, P. Glasserman, S.G. Kou(1997) 在连续障碍期权解析解上加调整项，调整障碍价格水平
                # 指数上的beta = -zeta(1/2) / sqrt(2*pi) = 0.5826, 其中zeta是黎曼zeta函数
                bound = [None, None]
                bound[1] = prod.bound[1] * np.exp(0.5826 * vol * np.sqrt(prod.discrete_obs_interval))
                bound[0] = prod.bound[0] * np.exp(-0.5826 * vol * np.sqrt(prod.discrete_obs_interval))
            else:  # 连续观察
                bound = prod.bound
            if self.formula_type == "Ikeda&Kunitomo1992":
                assert (bound[0] < prod.strike < bound[1]), "Error: Ikeda and Kunitomo(1992)的解析解仅当行权价在障碍范围内时，公式才是成立的。"
                return self._Ikeda_Kunitomo1992_pv(spot, r, q, vol, tau, bound)
            if self.formula_type == "Haug1998":
                assert (r - q == 0), "Error: Haug(1998)的解析解仅当标的持有成本为0时(期货期权，或r-q=0)，公式才是准确的。"
                return self._Haug1998_pv(spot, r, q, vol, tau, bound)
            raise NotImplementedError(f"{self.formula_type}不支持的解析解类型, 仅支持Ikeda&Kunitomo1992和Haug1998")
        else:
            raise ValueError("obs_type must be European or American")

    def calc_maturity_payoff(self, prod, s_vec, _tenor, _floor_yield=0.):
        """计算终止时的期权价值，在障碍价格内侧，默认设定未敲入，未敲出
        Args:
            prod: Product产品对象
            s_vec: np.ndarray, 网格的价格格点
            _tenor: float, 从起始日到终止日的自然日时间（年）
            _floor_yield: float，处理后的保底收益绝对数值
        Returns:
            value: np.ndarray, 期末价值向量
        """
        payoff = prod.callput.value * (s_vec - prod.strike) * prod.parti
        if prod.inout == InOut.Out:
            _rebate = self._preprocess_extra_returns(prod, _tenor, prod.rebate)  # 高低敲出现金返还绝对数值
            value = np.where(s_vec <= prod.bound[0], _rebate[0],
                             np.where(s_vec >= prod.bound[1], _rebate[1],
                                      np.where(payoff < 0, 0, payoff) + _floor_yield))
        elif prod.inout == InOut.In:
            _rebate = self._preprocess_extra_returns(prod, _tenor, [prod.rebate[0], ])  # 中间的敲入现金返还绝对数值
            value = np.where((s_vec > prod.bound[0]) & (s_vec < prod.bound[1]), _rebate[0],
                             np.where(payoff < 0, 0, payoff) + _floor_yield)
        else:
            raise ValueError("InOut must be In or Out")
        return value

    @staticmethod
    def _preprocess_extra_returns(prod, period, rebate_list):
        """预处理可能存在的年化现金返还rebate
        Args:
            prod: Product产品对象
            period: float 或 np.ndarray, 从起始到期权终止的时间（年）
                    对欧式障碍、美式敲入而言是整个存续期；对美式敲出而言，期权可能会因为敲出提前终止，此时period是起始日到敲出日的时间
            rebate_list: List[float], 现金返还率或绝对数值
        Returns:
            _rebate: float，处理后的现金返还绝对数值
        """
        if prod.rebate_annual:  # 年化百分比
            _rebate = []
            for r in rebate_list:
                _rebate.append(r * period * prod.s0)
        else:
            _rebate = rebate_list  # 绝对数值
        return _rebate

    def _extra_returns(self, prod, spot, tau, r, q, vol, tenor, _floor_yield):
        """处理欧式双边障碍期权可能存在的额外payoff：现金返还rebate、保底收益率floor_yield
            若 Out敲出， 障碍两侧为现金返还rebate，障碍中间为保底收益floor_yield
            若 In敲入， 障碍两侧为保底收益floor_yield，障碍中间为现金返还rebate
        Args:
            prod: Product产品对象
            spot: float，估值日标的价格
            tau: float，交易日到期时间（年）
            r: float，无风险利率（常数）
            q: float，连续分红率（常数）
            vol: float，波动率（常数）
        Returns:
        """
        if prod.inout == InOut.Out:  # 双边敲出
            _rebate = self._preprocess_extra_returns(prod, tenor, prod.rebate)  # 高低敲出现金返还绝对数值
            extra = (euro_cash_or_nothing(s=spot, k=prod.bound[0], t=tau, r=r, q=q, sigma=vol, sign=-1) * _rebate[0]
                     + euro_cash_or_nothing(s=spot, k=prod.bound[1], t=tau, r=r, q=q, sigma=vol, sign=1) * (
                             _rebate[1] - _floor_yield)
                     + euro_cash_or_nothing(s=spot, k=prod.bound[0], t=tau, r=r, q=q, sigma=vol, sign=1)
                     * _floor_yield)
        elif prod.inout == InOut.In:  # 双边敲入
            _rebate = self._preprocess_extra_returns(prod, tenor, [prod.rebate[0], ])  # 中间的敲入现金返还绝对数值
            extra = (euro_cash_or_nothing(s=spot, k=prod.bound[0], t=tau, r=r, q=q, sigma=vol, sign=-1) * _floor_yield
                     + euro_cash_or_nothing(s=spot, k=prod.bound[1], t=tau, r=r, q=q, sigma=vol, sign=1) * (
                             _floor_yield - _rebate[0])
                     + euro_cash_or_nothing(s=spot, k=prod.bound[0], t=tau, r=r, q=q, sigma=vol, sign=1)
                     * _rebate[0])
        else:
            raise ValueError("InOut must be In or Out")
        return extra

    def _calc_european_barrier(self, prod, spot, tau, r, q, vol, tenor, _floor_yield=0.):
        """欧式双边障碍期权的解析解
            欧式香草与欧式资产或无的解析解的组合，欧式现金或无的解析解可用于复制现金返还。
        Args:
            prod: Product产品对象
            spot: float，估值日标的价格
            tau: float，交易日到期时间（年）
            r: float，无风险利率（常数）
            q: float，连续分红率（常数）
            vol: float，波动率（常数）
            tenor: float, 从起始日到终止日的自然日存续时间（年）
            _floor_yield: float, 保底收益绝对数值，计入时间为从起始日到终止日
        Returns:
            欧式双边障碍期权的解析解
        """
        # 敲出：障碍两侧获得rebate，中间获得floor_yield；敲入：障碍两侧获得floor_yield，中间获得rebate
        extra_values = self._extra_returns(prod, spot, tau, r, q, vol, tenor, _floor_yield)
        # 障碍中香草部分的收益
        if prod.inout == InOut.Out:  # 双边敲出
            if (prod.callput == CallPut.Call and prod.bound[1] <= prod.strike) or (
                    prod.callput == CallPut.Put and prod.strike <= prod.bound[0]):
                # 若香草payoff位于敲出范围，香草的收益=0，此时相当于双边二元期权 + rebate和保底收益
                value = 0 + extra_values
            elif (prod.bound[0] < prod.strike < prod.bound[1]):
                # 若执行价位于两个障碍价之间，除了rebate和保底收益之外，call/put在两个障碍中间的收益等价于如下资产组合：
                # 多一份call/put(k), 空一份方向向上/下、执行价为高障碍价/低障碍价的[资产或无-执行价*现金或无]
                if prod.callput == CallPut.Call:
                    _barrier = prod.bound[1]
                    sign = 1
                else:
                    _barrier = prod.bound[0]
                    sign = -1
                value = (bs_formula(s=spot, k=prod.strike, t=tau, r=r, q=q, sigma=vol, sign=sign) - sign *
                         (euro_asset_or_nothing(s=spot, k=_barrier, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                          euro_cash_or_nothing(s=spot, k=_barrier, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                          )) * prod.parti + extra_values
            elif (prod.callput == CallPut.Call and prod.strike <= prod.bound[0]) or (
                    prod.callput == CallPut.Put and prod.bound[1] <= prod.strike):
                # 若香草payoff跨过了两个障碍价，除了rebate和保底收益之外，call/put在两个障碍外侧的收益等价于如下资产组合：
                # 多一份方向向上/下、执行价为低障碍价/高障碍价的[资产或无-执行价*现金或无]，
                # 空一份方向向上/下、执行价为高障碍价/低障碍价的[资产或无-执行价*现金或无]，
                if prod.callput == CallPut.Call:
                    _barrier1 = prod.bound[0]
                    _barrier2 = prod.bound[1]
                    sign = 1
                else:
                    _barrier1 = prod.bound[1]
                    _barrier2 = prod.bound[0]
                    sign = -1
                value = ((euro_asset_or_nothing(s=spot, k=_barrier1, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                          euro_cash_or_nothing(s=spot, k=_barrier1, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                          ) -
                         (euro_asset_or_nothing(s=spot, k=_barrier2, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                          euro_cash_or_nothing(s=spot, k=_barrier2, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                          )) * sign * prod.parti + extra_values
            else:
                raise ValueError(
                    f"Unhandled strike-barrier situation: strike={prod.strike}, barrier_lower={prod.bound[0]}, barrier_upper={prod.bound[1]}")
        elif prod.inout == InOut.In:  # 双边敲入
            if (prod.callput == CallPut.Call and prod.bound[1] <= prod.strike) or (
                    prod.callput == CallPut.Put and prod.strike <= prod.bound[0]):
                # 若香草payoff全部位于敲入范围，此时相当于香草的价值+rebate和保底收益
                value = bs_formula(s=spot, k=prod.strike, t=tau, r=r, q=q, sigma=vol, sign=prod.callput.value
                                   ) * prod.parti + extra_values
            elif (prod.bound[0] < prod.strike < prod.bound[1]):
                # 若执行价位于两个障碍价之间，除了rebate和保底收益之外，call/put在两个障碍外侧的收益等价于如下资产组合：
                # 多一份方向向上/下、执行价为高障碍价/低障碍价的[资产或无-执行价*现金或无]
                if prod.callput == CallPut.Call:
                    _barrier = prod.bound[1]
                    sign = 1
                else:
                    _barrier = prod.bound[0]
                    sign = -1
                value = (euro_asset_or_nothing(s=spot, k=_barrier, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                         euro_cash_or_nothing(s=spot, k=_barrier, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                         ) * sign * prod.parti + extra_values
            elif (prod.callput == CallPut.Call and prod.strike <= prod.bound[0]) or (
                    prod.callput == CallPut.Put and prod.bound[1] <= prod.strike):
                # 若香草payoff跨过了两个障碍价，除了rebate和保底收益之外，call/put在两个障碍外侧的收益等价于如下资产组合：
                # 多一份call/put，
                # 空一份方向向上/下、执行价为低障碍价/高障碍价的[资产或无-执行价*现金或无]，
                # 多一份方向向上/下、执行价为高障碍价/低障碍价的[资产或无-执行价*现金或无]。
                if prod.callput == CallPut.Call:
                    _barrier1 = prod.bound[0]
                    _barrier2 = prod.bound[1]
                    sign = 1
                else:
                    _barrier1 = prod.bound[1]
                    _barrier2 = prod.bound[0]
                    sign = -1
                value = (bs_formula(s=spot, k=prod.strike, t=tau, r=r, q=q, sigma=vol, sign=sign) - sign *
                         (euro_asset_or_nothing(s=spot, k=_barrier1, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                          euro_cash_or_nothing(s=spot, k=_barrier1, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                          ) + sign *
                         (euro_asset_or_nothing(s=spot, k=_barrier2, t=tau, r=r, q=q, sigma=vol, sign=sign) -
                          euro_cash_or_nothing(s=spot, k=_barrier2, t=tau, r=r, q=q, sigma=vol, sign=sign) * prod.strike
                          )) * prod.parti + extra_values
            else:
                raise ValueError(
                    f"Unhandled strike-barrier situation: strike={prod.strike}, barrier_lower={prod.bound[0]}, barrier_upper={prod.bound[1]}")
        else:
            raise ValueError("InOut must be In or Out")
        return value

    # pylint: disable=invalid-name, too-many-locals
    def _Ikeda_Kunitomo1992_pv(self, spot, r, q, vol, tau, bound):
        """Ikeda and Kunitomo(1992)级数解，仅当行权价在障碍范围内时，公式才是成立的"""
        if spot >= bound[1] or spot <= bound[0]:  # 标的价格不在障碍范围内时，直接计算payoff
            if self.prod.inout == InOut.Out:  # 发生敲出，payoff为0
                return 0
            elif self.prod.inout == InOut.In:  # 发生敲入，返回香草期权的价值
                return bs_formula(s=spot, k=self.prod.strike, t=tau, r=r, q=q, sigma=vol,
                                  sign=self.prod.callput.value) * self.prod.parti
            else:
                raise ValueError("inout只能是InOut.In或InOut.Out")

        if self.prod.callput == CallPut.Call:
            E = bound[1] * math.exp(self.delta1 * tau)
        else:
            E = bound[0] * math.exp(self.delta2 * tau)
        numerator = (r - q + 0.5 * vol ** 2) * tau
        denominator = vol * math.sqrt(tau)
        d1 = (np.log(
            spot * np.power(bound[1], 2 * self.i_vec) / (self.prod.strike * np.power(bound[0], 2 * self.i_vec)))
              + numerator) / denominator
        d2 = (np.log(spot * np.power(bound[1], 2 * self.i_vec) / (E * np.power(bound[0], 2 * self.i_vec)))
              + numerator) / denominator
        d3 = (np.log(
            np.power(bound[0], 2 * self.i_vec + 2) / (self.prod.strike * spot * np.power(bound[1], 2 * self.i_vec)))
              + numerator) / denominator
        d4 = (np.log(
            np.power(bound[0], 2 * self.i_vec + 2) / (E * spot * np.power(bound[1], 2 * self.i_vec)))
              + numerator) / denominator
        mu1 = 2 * (r - q - self.delta2 - self.i_vec * (self.delta1 - self.delta2)) / vol ** 2 + 1
        mu2 = 2 * self.i_vec * (self.delta1 - self.delta2) / vol ** 2
        mu3 = 2 * (r - q - self.delta2 + self.i_vec * (self.delta1 - self.delta2)) / vol ** 2 + 1

        Nd1a = (np.power(np.power(bound[1], self.i_vec) / np.power(bound[0], self.i_vec), mu1) * (
                bound[0] / spot) ** mu2 * (norm.cdf(d1) - norm.cdf(d2)) * self.prod.callput.value)
        Nd1b = (np.power(np.power(bound[0], self.i_vec + 1) / (np.power(bound[1], self.i_vec) * spot), mu3) * (
                norm.cdf(d3) - norm.cdf(d4)) * self.prod.callput.value)
        Nd2a = (np.power(np.power(bound[1], self.i_vec) / np.power(bound[0], self.i_vec), mu1 - 2) * (
                bound[0] / spot) ** mu2 * self.prod.callput.value * (
                        norm.cdf(d1 - denominator) - norm.cdf(d2 - denominator)))
        Nd2b = (np.power(np.power(bound[0], self.i_vec + 1) / (np.power(bound[1], self.i_vec) * spot), mu3 - 2) * (
                norm.cdf(d3 - denominator) - norm.cdf(d4 - denominator)) * self.prod.callput.value)

        double_out = (self.prod.callput.value * spot * math.exp(-q * tau) * np.sum(Nd1a - Nd1b) -
                      self.prod.callput.value * self.prod.strike * math.exp(-r * tau) * np.sum(Nd2a - Nd2b))
        if self.prod.inout == InOut.Out:
            return double_out * self.prod.parti
        elif self.prod.inout == InOut.In:
            double_in = bs_formula(s=spot, k=self.prod.strike, t=tau, r=r, q=q, sigma=vol,
                                   sign=self.prod.callput.value) - double_out
            return double_in * self.prod.parti
        else:
            raise ValueError("inout只能是InOut.In或InOut.Out")

    # pylint: disable=too-many-locals
    def _Haug1998_pv(self, spot, r, q, vol, tau, bound):
        """Haug(1998)的解析解，仅当标的持有成本为0时(期货期权，或r-q=0)，公式才是准确的"""
        value = 0
        if self.prod.callput == CallPut.Call:
            opposite_callput = CallPut.Put
        else:
            opposite_callput = CallPut.Call
        from ...products.barrier.barrier_option import BarrierOption
        barrier_analytic_engine = AnalyticBarrierEngine(self.process, for_haug=True)
        up_barrier = BarrierOption(maturity=tau, strike=self.prod.strike, barrier=bound[1],
                                   updown=UpDown.Up, callput=self.prod.callput, inout=InOut.In, rebate=0,
                                   discrete_obs_interval=None,
                                   engine=barrier_analytic_engine)
        down_barrier = BarrierOption(maturity=tau, strike=self.prod.strike, barrier=bound[0],
                                     updown=UpDown.Down, callput=self.prod.callput, inout=InOut.In, rebate=0,
                                     discrete_obs_interval=None,
                                     engine=barrier_analytic_engine)
        up_barrier_opposite = BarrierOption(strike=bound[1] ** 2 / self.prod.strike, barrier=bound[1] ** 2 / bound[0],
                                            maturity=tau, rebate=0, inout=InOut.In,
                                            updown=UpDown.Up, callput=opposite_callput,
                                            discrete_obs_interval=None,
                                            engine=barrier_analytic_engine)
        down_barrier_opposite = BarrierOption(strike=bound[0] ** 2 / self.prod.strike, barrier=bound[0] ** 2 / bound[1],
                                              maturity=tau, rebate=0, inout=InOut.In,
                                              updown=UpDown.Down, callput=opposite_callput,
                                              discrete_obs_interval=None,
                                              engine=barrier_analytic_engine)
        value += (up_barrier.price() + down_barrier.price()
                  - up_barrier_opposite.price() * self.prod.strike / bound[1]
                  - down_barrier_opposite.price() * self.prod.strike / bound[0])

        up_barrier.strike = self.prod.strike * bound[1] ** 2 / bound[0] ** 2
        up_barrier.barrier = math.pow(bound[1], 3) / bound[0] ** 2
        down_barrier.strike = self.prod.strike * bound[0] ** 2 / bound[1] ** 2
        down_barrier.barrier = math.pow(bound[0], 3) / bound[1] ** 2
        up_barrier_opposite.strike = math.pow(bound[1], 4) / (bound[0] ** 2 * self.prod.strike)
        up_barrier_opposite.barrier = math.pow(bound[1], 4) / math.pow(bound[0], 3)
        down_barrier_opposite.strike = math.pow(bound[0], 4) / (bound[1] ** 2 * self.prod.strike)
        down_barrier_opposite.barrier = math.pow(bound[0], 4) / math.pow(bound[1], 3)

        value += (up_barrier.price() * bound[0] / bound[1] + down_barrier.price() * bound[1] / bound[0]
                  - up_barrier_opposite.price() * self.prod.strike * bound[0] / bound[1] ** 2
                  - down_barrier_opposite.price() * self.prod.strike * bound[1] / bound[0] ** 2)

        up_barrier.strike = self.prod.strike * (math.pow(bound[1], 4) / math.pow(bound[0], 4))
        up_barrier.barrier = math.pow(bound[1], 5) / math.pow(bound[0], 4)
        down_barrier.strike = self.prod.strike * (math.pow(bound[0], 4) / math.pow(bound[1], 4))
        down_barrier.barrier = math.pow(bound[0], 5) / math.pow(bound[1], 4)

        value += (up_barrier.price() * bound[0] ** 2 / bound[1] ** 2
                  + down_barrier.price() * bound[1] ** 2 / bound[0] ** 2)

        double_in = min(value, bs_formula(s=spot, k=self.prod.strike, t=tau, r=r, q=q, sigma=vol,
                                          sign=self.prod.callput.value))
        if self.prod.inout == InOut.In:
            return double_in * self.prod.parti
        elif self.prod.inout == InOut.Out:
            double_out = bs_formula(s=spot, k=self.prod.strike, t=tau, r=r, q=q, sigma=vol,
                                    sign=self.prod.callput.value) - double_in
            return double_out * self.prod.parti
        else:
            raise ValueError("inout只能是InOut.In或InOut.Out")
