#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
import math
import datetime
from contextlib import suppress
from pricelib.common.product_base.option_base import OptionBase
from pricelib.common.utilities.enums import CallPut, ExerciseType, AverageMethod, AsianAveSubstitution
from pricelib.common.date_handler import CN_CALENDAR, AnnualDays, global_evaluation_date
from pricelib.common.utilities.patterns import Observer, global_auto_update_status
from pricelib.common.utilities.utility import time_this, logging
from pricelib.pricing_engines.analytic_engines import AnalyticAsianEngine
from pricelib.pricing_engines.mc_engines import MCAsianEngine
from pricelib.pricing_engines.tree_engines import BiTreeAsianEngine


class AsianOption(OptionBase, Observer):
    """
    亚式期权：几何平均/算术平均；替代标的资产价格/替代执行价；增强亚式
        解析解只支持平均结算价，无增强亚式
        蒙特卡洛支持几何平均/算术平均，支持增强亚式，支持替代标的资产价格/替代执行价
        二叉树支持几何平均/算术平均，支持增强亚式，支持替代标的资产价格，不支持替代执行价
        todo:若定价时已经处于均价观察期内，需要获得当前已经实现的均价，并对行权价进行调整
    """
    exercise_type = ExerciseType.Asian

    def __init__(self, callput=CallPut.Call, ave_method=AverageMethod.Geometric, strike=None,
                 substitute=AsianAveSubstitution.Underlying, parti=1, enhanced=False, limited_price=None,
                 engine=None, maturity=None, start_date=None, end_date=None, obs_start=None, obs_end=None,
                 status=None, trade_calendar=CN_CALENDAR, annual_days=AnnualDays.N365, t_step_per_year=243,
                 stoch_process=None, s=None, r=None, q=None, vol=None, engine_method=None):
        """亚式期权初始化
        产品参数:
            callput: 看涨看跌类型，CallPut枚举类，Call/Put
            ave_method: 平均价计算方法，AverageMethod枚举类，几何平均Geometric/算术平均Arithmetic
            strike: float, 执行价
            substitute: 以均价代替标的资产价格/替代行权价，AsianAveSubstitution枚举类，Underlying/Strike
            parti: float, 参与率，默认为1
            enhanced: bool，是否为增强亚式期权，默认为False
            limited_price: float，增强型亚式限定标的资产价格的上/下限
            status: [int, float]，算术平均：[已经过的观察日天数, 已实现的采集价的累加值].未进入平均观察期时，默认为 [0, 0]
                                  几何平均：[已经过的观察日天数, 已实现的采集价的自然对数累加值].未进入平均观察期时，默认为 [0, 0]
        时间参数: 要么输入年化期限，要么输入起始日和到期日
            maturity: float，年化期限
            start_date: datetime.date，起始日
            end_date: datetime.date，到期日
            obs_start: datetime.date，观察期起始日
            obs_end: datetime.date，观察期终止日
            trade_calendar: 交易日历，Calendar类，默认为中国内地交易日历
            annual_days: int，每年的自然日数量
            t_step_per_year: int，每年的交易日数量
        可选参数:
            engine: 定价引擎，PricingEngine类
                    解析解: AnalyticAsianEngine
                           几何平均替代结算价亚式: Kemma Vorst (1990) 连续观察，平均价观察期为整个期权有效期内
                           算术平均替代结算价亚式: Turnbull Wakeman (1991) 近似估计，平均观察期的起点任意，终点为期权到期日
                    二叉树: BiTreeAsianEngine 只支持算术平均替代结算价亚式
                    蒙特卡洛: MCAsianEngine
                           几何平均/算术平均替代结算价亚式
                           几何平均/算术平均替代执行价亚式
                           增强亚式
            engine_method: str, 定价引擎种类。若未输入engine，可以用engine_method快捷生成指定定价引擎。可选Analytic、MC/MonteCarlo、PDE/FDM、QUAD。
            若未提供引擎engine、也未指定引擎种类engine_method的情况下，如果提供了 随机过程 或 标的价格、无风险利率、分红/融券率、波动率，
            则平均替代收盘价亚式默认使用解析解定价引擎；平均替代执行价亚式和增强亚式默认使用蒙特卡洛定价引擎
            stoch_process: 随机过程StochProcessBase对象
            s: float，标的价格
            r: float，无风险利率
            q: float，分红/融券率
            vol: float，波动率
        """
        super().__init__()
        self.trade_calendar = trade_calendar
        self.annual_days = annual_days
        self.t_step_per_year = t_step_per_year
        assert maturity is not None or (start_date is not None and end_date is not None), "Error: 到期时间或起止时间必须输入一个"
        self.start_date = start_date if start_date is not None else global_evaluation_date()  # 起始时间
        self.end_date = end_date if end_date is not None else (
                self.start_date + datetime.timedelta(days=round(maturity * annual_days.value)))  # 结束时间
        if self.trade_calendar.isholiday(self.end_date):  # 如果到期日不是交易日，向后顺延一个交易日
            self.end_date = self.trade_calendar.advance(self.end_date, datetime.timedelta(days=1))
        self.maturity = maturity if maturity is not None else (self.end_date - self.start_date).days / annual_days.value
        self.obs_start = obs_start if obs_start is not None else self.start_date
        self.obs_end = obs_end if obs_end is not None else self.end_date
        self.callput = callput
        self.ave_method = ave_method
        self.substitute = substitute
        self.parti = parti
        self.strike = strike
        self.enhanced = enhanced
        if enhanced and limited_price is None:
            # 增强型亚式的限定标的价格上/下限参数默认为执行价格
            self.limited_price = self.strike
        else:
            self.limited_price = limited_price
        assert strike is not None or substitute == AsianAveSubstitution.Strike, "Error: 替代平均结算价，必需输入执行价参数"
        if status is None:
            # 算术平均：[已经过的观察日天数, 已实现的采集价的累加值]；几何平均：[已经过的观察日天数, 已实现的采集价的自然对数累加值]
            self.status = [0, 0]
        else:
            self.status = list(status)
        if engine is not None:
            self.set_pricing_engine(engine)
        else:
            if engine_method is None:
                # 平均结算价，非增强亚式，当几何平均且整个存续期观察，或者算数平均且观察结束日等于终止日期，并且未到平均观察期内时，使用解析解定价引擎
                if (self.substitute == AsianAveSubstitution.Underlying and (not self.enhanced)
                        and self.end_date == self.obs_end and (self.ave_method == AverageMethod.Arithmetic or (
                                self.ave_method == AverageMethod.Geometric and self.start_date == self.obs_start))
                        and global_evaluation_date() <= self.obs_start):
                    default_engine = AnalyticAsianEngine
                else:  # 其他情形：例如替代执行价亚式/增强亚式，使用MC定价引擎
                    default_engine = MCAsianEngine
            elif engine_method == "Analytic":
                default_engine = AnalyticAsianEngine
            elif engine_method in ["MC", "MonteCarlo"]:
                default_engine = MCAsianEngine
            elif engine_method == "TREE":
                default_engine = BiTreeAsianEngine
            else:
                raise NotImplementedError(
                    f"不支持的engine_method{engine_method}, {self.__repr__()}仅支持Analytic/MC/TREE")
            if stoch_process is not None:
                # 未提供引擎，但提供了随机过程
                self.set_pricing_engine(default_engine(stoch_process=stoch_process))
            elif s is not None and r is not None and q is not None and vol is not None:
                # 未提供引擎，但提供了标的价格、无风险利率、分红/融券率、波动率
                self.set_pricing_engine(default_engine(s=s, r=r, q=q, vol=vol))

    def set_pricing_engine(self, engine):
        """设置定价引擎"""
        with suppress(AttributeError):
            self.engine.process.spot.remove_observer(self)
        self.engine = engine
        self.engine.process.spot.add_observer(self)
        logging.info(f"{self}当前定价方法为{engine.engine_type.value}")

    def remove_self(self):
        """删除对象自己，del自己之前，先将自己从被观察者的列表中移除"""
        self.engine.process.spot.remove_observer(self)
        del self

    def update(self, observable, *args, **kwargs):
        """收到标的价格变化的通知时，自动更新亚式期权的已实现平均观察价"""
        if global_auto_update_status():
            calculate_date = global_evaluation_date()
            if calculate_date < self.start_date or calculate_date > self.end_date:
                return  # 如果在起始日前或到期日之后，状态维持不变
            if (observable == self.engine.process.spot
                    and self.obs_start <= calculate_date <= self.obs_end):
                self.status[0] += 1
                if self.enhanced:  # 如果是增强亚式，采集价受到约定价格的限制
                    if self.callput == CallPut.Call:
                        observed_price = max(self.limited_price, observable.data)
                    elif self.callput == CallPut.Put:
                        observed_price = min(self.limited_price, observable.data)
                    else:
                        raise ValueError(f"Error: 不支持的看涨看跌方向{self.callput}")
                else:
                    observed_price = observable.data
                if self.ave_method == AverageMethod.Arithmetic:  # 算术平均
                    self.status[1] += round(self.status[1] + observed_price, 4)
                    logging.info(f"{self} 已实现采集价累加值 更新为: {self.status[1]}, 已观察{self.status[0]}天")
                elif self.ave_method == AverageMethod.Geometric:  # 几何平均
                    self.status[1] = round(self.status[1] + math.log(observed_price), 4)
                    logging.info(f"{self} 已实现采集价的对数累加值 更新为: {self.status[1]}, 已观察{self.status[0]}天")
                else:
                    raise ValueError(f"Error: 未知的平均价计算方法{self.ave_method}")

    def __repr__(self):
        """返回期权的描述"""
        enhance_str = "增强型" if self.enhanced else ""
        return f"{self.ave_method.value}-{self.substitute.value}-{self.callput.name}{enhance_str}亚式期权"

    @time_this
    def price(self, t: datetime.date = None, spot=None):
        """计算期权价格
        Args:
            t: datetime.date，计算期权价格的日期
            spot: float，标的价格
        Returns: 期权现值
        """
        self.validate_parameters(t=t)
        price = self.engine.calc_present_value(prod=self, t=t, spot=spot) * self.parti
        return price
