"""
可转债估值测试-2023.9
"""
import hbshare as hbs
import pandas as pd
import numpy as np
import re
from tqdm import tqdm
import pickle
import os
from datetime import datetime, timedelta
from cb_pricing_BS import cbPricingBlackScholes
from cb_pricing_Tree import cbPricingTree
from cb_pricing_MC import cbPricingMC
from WindPy import w

w.start()


class CbValuation:
    """
    计算对应正股的波动率和股票价格
    """
    def __init__(self, date, stock_ticker):
        self.date = date
        self.stock_ticker = stock_ticker
        self._load_data()

    def _load_data(self):
        pre_dt = datetime.strptime(self.date, '%Y/%m/%d') - timedelta(days=400)
        pre_date = pre_dt.strftime('%Y%m%d')
        end_date = datetime.strptime(self.date, '%Y/%m/%d').strftime('%Y%m%d')
        sql_script = "SELECT SYMBOL, TDATE, TCLOSE FROM finchina.CHDQUOTE WHERE" \
                     " TDATE >= {} and TDATE <= {} and SYMBOL = {}".format(pre_date, end_date, self.stock_ticker)
        res = hbs.db_data_query('readonly', sql_script, page_size=5000)
        data = pd.DataFrame(res['data']).rename(
            columns={"SYMBOL": "ticker", "TDATE": "trade_date", "TCLOSE": "price"})
        data['trade_date'] = data['trade_date'].apply(lambda x: str(x))
        data = data[data['price'] > 0]

        self.vol = data['price'].pct_change()[-250:].std() * np.sqrt(250)
        self.stock_price = data.set_index('trade_date').iloc[-1]['price']


class CbValuationTest:
    """
    批量计算可转债价值
    """
    def __init__(self, basic_info, bond_ytm):
        self.basic_info = basic_info
        self.bond_ytm = bond_ytm
        self.prepared_data = None
        self._load_data()
        self._load_cb_price()

    def _load_data(self):
        if os.path.exists("C:\\Users\\kai.zhang\\Desktop\\可转债\\cb_params.pkl"):
            with open("C:\\Users\\kai.zhang\\Desktop\\可转债\\cb_params.pkl", "rb") as f:
                self.prepared_data = pickle.load(f)
        else:
            basic_info = self.basic_info.copy()
            basic_info = basic_info[basic_info['发行信用等级'] != 'A']
            cb_id_list = basic_info['证券代码'].tolist()
            basic_info = basic_info.set_index('证券代码')
            prepared_data = {}
            for cb_id in tqdm(cb_id_list):
                ConvPrice = basic_info.loc[cb_id, '转股价']
                Maturity = datetime.strftime(basic_info.loc[cb_id, '到期日期'], '%Y/%m/%d')
                ConvertStart = 5.5
                coupon_info = basic_info.loc[cb_id, '票面利率说明']
                coupon = [float(x) for x in re.findall(r"\d+\.?\d*", coupon_info)]
                coupon[-1] = basic_info.loc[cb_id, '到期赎回价']

                recall = [ConvertStart,
                          basic_info.loc[cb_id, '赎回触发计算时间区间'].astype(int),
                          basic_info.loc[cb_id, '赎回触发计算最大时间区间'].astype(int),
                          basic_info.loc[cb_id, '赎回触发比例'].astype(int)]

                resell = [(basic_info.loc[cb_id, '相对回售期'] / 24).astype(int),
                          basic_info.loc[cb_id, '回售触发计算时间区间'].astype(int),
                          basic_info.loc[cb_id, '回售触发计算时间区间'].astype(int),
                          basic_info.loc[cb_id, '回售触发比例'].astype(int),
                          103]

                term_CB = {
                    "ConvPrice": ConvPrice,
                    "Maturity": Maturity,
                    "ConvertStart": ConvertStart,
                    "Coupon": coupon,
                    "Recall": recall,
                    "Resell": resell
                }
                term_CB['Recall'][-1] *= term_CB['ConvPrice'] / 100
                term_CB['Resell'][-2] *= term_CB['ConvPrice'] / 100
                term_CB['Resell'][-1] *= term_CB['ConvPrice'] / 100

                # now_date = '2021/6/1'
                now_date = '2023/9/22'
                ticker = basic_info.loc[cb_id, '正股代码'].split('.')[0]

                cb_value = CbValuation(date=now_date, stock_ticker=ticker)
                vol, stock_price = cb_value.vol, cb_value.stock_price

                credit = basic_info.loc[cb_id, '发行信用等级']
                year_to_maturity = \
                    (datetime.strptime(Maturity, '%Y/%m/%d') - datetime.strptime(now_date, '%Y/%m/%d')).days // 365
                year_to_maturity = max(year_to_maturity, 1)

                rate = self.bond_ytm[self.bond_ytm['信用评级'] == credit][year_to_maturity].values[0] / 100

                prepared_data[cb_id] = {
                    "stock_price": stock_price,
                    "term_CB": term_CB,
                    "now_date": now_date,
                    "vol": vol,
                    "rate": rate
                }

            self.prepared_data = prepared_data

        with open("C:\\Users\\kai.zhang\\Desktop\\可转债\\cb_params.pkl", "wb") as f:
            pickle.dump(self.prepared_data, f)

    def _load_cb_price(self):
        basic_info = self.basic_info.copy()
        basic_info = basic_info[basic_info['发行信用等级'] != 'A']
        cb_id_list = basic_info['证券代码'].tolist()

        # res = w.wsd(','.join(cb_id_list), "close", "2021-06-01", "2021-06-01", "")
        res = w.wsd(','.join(cb_id_list), "close", "2023-09-22", "2023-09-22", "")

        if res.ErrorCode != 0:
            data = pd.DataFrame()
            print("fetch cb data error")
        else:
            if len(res.Data) == 1:
                data = pd.DataFrame(res.Data[0], index=res.Codes, columns=res.Times)
            else:
                data = pd.DataFrame(res.Data, index=res.Codes, columns=res.Times)
            data.columns = ['close']

        self.cb_price = data

    def calculate(self):
        bs_list = []
        tree_list = []
        mc_list = []
        for key, value in tqdm(self.prepared_data.items()):
            stock_price, term_CB, now_date, vol, rate = \
                value['stock_price'], value['term_CB'], value['now_date'], value['vol'], value['rate']

            # B-S估值
            straightBondValue, call_value = cbPricingBlackScholes(stock_price, term_CB, now_date, vol, rate)
            bs_list.append(straightBondValue + call_value)
            # 二叉树估值
            tree_list.append(cbPricingTree(stock_price, term_CB, now_date, vol, rate))
            # MC估值
            mc_list.append(cbPricingMC(stock_price, term_CB, now_date, vol, rate))

        valuation_df = pd.DataFrame({"cb_id": list(self.prepared_data.keys()), "B-S": bs_list,
                                     "tree": tree_list, "mc": mc_list})
        valuation_df = pd.merge(valuation_df, self.cb_price, left_on='cb_id', right_index=True)
        valuation_df.to_csv('C:\\Users\\kai.zhang\\Desktop\\可转债\\可转债估值结果_新.csv', index=False)


if __name__ == '__main__':
    # # 可转债的基本资料
    # term_CB = {
    #     "ConvPrice": 10.66,
    #     "Maturity": "2021/2/1",
    #     "ConvertStart": 5.5,
    #     "Coupon": [0.2, 0.5, 1.0, 1.5, 1.5, 106.6],
    #     "Recall": [5.5, 15, 30, 130],
    #     "Resell": [2, 30, 30, 70, 103]
    # }
    #
    # now_date = '2015/9/22'
    # ticker = '601727'
    #
    # cb_value = CbValuation(date=now_date, stock_ticker=ticker)
    # vol, stock_price = cb_value.vol, cb_value.stock_price
    # # 同等级/期限的企业债收益率
    # rate = 0.029659
    #
    # cb_price_bs = cbPricingBlackScholes(stock_price, term_CB, now_date, vol, rate)
    # cb_price_tree = cbPricingTree(stock_price, term_CB, now_date, vol, rate)
    # term_CB['Recall'][-1] *= term_CB['ConvPrice'] / 100
    # term_CB['Resell'][-2] *= term_CB['ConvPrice'] / 100
    # term_CB['Resell'][-1] *= term_CB['ConvPrice'] / 100
    # cb_price_mc = cbPricingMC(stock_price, term_CB, now_date, vol, rate)

    cb_info = pd.read_excel("C:\\Users\\kai.zhang\\Desktop\\可转债\\可转债基本资料_新.xlsx").dropna()
    ytm_data = pd.read_excel("C:\\Users\\kai.zhang\\Desktop\\可转债\\企业债到期收益率_新.xlsx")
    CbValuationTest(basic_info=cb_info, bond_ytm=ytm_data).calculate()