#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
from abc import ABCMeta
import numpy as np
import numba as nb
from scipy.stats import norm
from ..utilities.enums import EngineType
from .engine_base import PricingEngineBase


class AnalyticEngine(PricingEngineBase, metaclass=ABCMeta):
    """解析定价引擎基类"""
    engine_type = EngineType.AnEngine


@nb.njit(cache=True, fastmath=True)
def bs_d1(s, k, t, r, sigma, q=0):
    """欧式股票期权BSM公式中的d1
    Args:
        s: float，标的价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        q: float，连续分红率（股息率）（常数）
    Returns:
    """
    d1 = (np.log(s / k) + (r - q + 0.5 * sigma ** 2) * t) / (sigma * np.sqrt(t))
    return d1


@nb.njit(cache=True, fastmath=True)
def bs_d2(s, k, t, r, sigma, q=0):
    """欧式股票期权BSM公式中的d2
    Args:
        s: float，标的价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        q: float，连续分红率（股息率）（常数）
    Returns:
    """
    d2 = (np.log(s / k) + (r - q - 0.5 * sigma ** 2) * t) / (sigma * np.sqrt(t))
    return d2


def bs_formula(s, k, t, r, sigma, sign=1, q=0):
    """欧式股票期权Black-Scholes-Merton公式, 针对现货（股票）期权的定价模型。认购期权的sign为1, 认沽期权的sign为-1
    若股票有稳定的连续分红率q，请传入参数q。股票在0时刻价格 = S(0)exp(qT)，股票在T时间中的分红 = S(0) * [exp(qT) - 1]
    Args:
        s: float，标的价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        sign: int，期权方向，1为认购期权，-1为认沽期权
        q: float，连续分红率（股息率）（常数）
    Returns:
        float，欧式香草股票期权期权价格
    """
    if t == 0:  # 如果估值日就是到期日，直接返回payoff
        return max(sign * (s - k), 0)
    d1 = bs_d1(s=s, k=k, t=t, r=r, q=q, sigma=sigma)
    d2 = d1 - sigma * np.sqrt(t)
    return sign * np.exp(-q * t) * s * norm.cdf(sign * d1) - sign * np.exp(-r * t) * k * norm.cdf(sign * d2)

def black76_formula(f, k, t, r, sigma, sign=1, q=None):
    """期货期权Black76公式, 针对远期或期货合约的期权的定价模型。认购期权的sign为1, 认沽期权的sign为-1。
    期货期权的q通常被假设等于 r, 即持有标的资产的机会成本 b = r-q = 0, 原因是期货合约的持有成本与现货的持有成本相比可以忽略不计，
    因为期货合约是在未来某个时间点交割的标准化合约，并且通常不涉及实际持有标的资产，不需要支付像现货那样的存储、保险和融资成本。
    实践中，期货交易是保证金交易，不需要持有整个标的资产，相关的持有成本可以忽略，b 被设为零可以认为是出于简化模型的考虑。
    Args:
        f: float，标的远期或期货合约价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        sign: int，期权方向，1为认购期权，-1为认沽期权
        q: float，固定等于r。该参数无作用，仅用于保持与bs_formula函数接口一致
    Returns:
        float，标的远期或期货合约的欧式香草期权价格
    """
    if t == 0:  # 如果估值日就是到期日，直接返回payoff
        return max(sign * (f - k), 0)
    d1 = bs_d1(s=f, k=k, t=t, r=r, q=r, sigma=sigma)
    d2 = d1 - sigma * np.sqrt(t)
    return sign * np.exp(-r * t) * (f * norm.cdf(sign * d1) - k * norm.cdf(sign * d2))


def euro_cash_or_nothing(s, k, t, r, sigma, sign=1, q=0):
    """欧式香草期权中的现金或无的价值，其中的现金是1(元)。主要是BSM公式中的一部分 N(d2)，这是在风险中性测度下，期权被执行的概率。
        例如看涨的现金1或无期权，到期时刻，St >= strike, payoff = 1(元) ; St < strike, payoff = 0.
        N(d2)积分推导BSM时，当 St > k，可推出积分变量 x>-d2。x是标准正态分布，所以 St > k 的可能性就是 N(d2).
    Args:
        s: float，标的价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        sign: int，期权方向，1为认购期权，-1为认沽期权
        q: float，连续分红率（股息率）（常数）
    Returns:
        欧式香草期权中的现金1或无的价值
    """
    d2 = bs_d2(s=s, k=k, t=t, r=r, q=q, sigma=sigma)
    return np.exp(-r * t) * norm.cdf(sign * d2)


def euro_asset_or_nothing(s, k, t, r, sigma, sign=1, q=0):
    """欧式香草期权中的资产或无的价值。主要是BSM公式中的一部分 s*N(d1)，代表着行权得到股票的期望收益。
        例如看涨的资产或无期权，到期时刻，St > strike, payoff = St; St < strike, payoff = 0.
    Args:
        s: float，标的价格
        k: float，执行价
        t: float，到期时间（年）
        r: float，无风险利率（常数）
        sigma: float，波动率（常数）
        sign: int，期权方向，1为认购期权，-1为认沽期权
        q: float，连续分红率（股息率）（常数）
    Returns:
        欧式香草期权中的资产或无的价值
    """
    d1 = bs_d1(s=s, k=k, t=t, r=r, q=q, sigma=sigma)
    return np.exp(-q * t) * s * norm.cdf(sign * d1)
