示例#1
0
	def options():
		client = RestClient()
		call = "BTC-29DEC17-11000-C"
		put = "BTC-29DEC17-3000-P"
		myc = client.getsummary(call)
		myp = client.getsummary(put)
		result = str(myc['instrumentName']) + ": " + str(myc['bidPrice']) + "\n" + str(myp['instrumentName']) + ": " + str(myp['bidPrice'])
		return result
class date_selection:
    """This class only output the future maturity dates.
    It is used as a light weight and fast data output"""
    def __init__(self, interest_rate=0, K_range=(0, math.inf)):
        # this is my account. Don't share it to others. But I don't have money in it :P
        self.client = RestClient('2SQfDzW1Kaf3F', 'HOG2A2HCYERM2YONRBTYEBMYRZ2ESN3K')
        data = self.client.getsummary('future')
        # convert this list of dictionaries into data frame
        data = pd.DataFrame.from_dict(data=data)
        # split strings to get date and type
        data_tmp = data['instrumentName'].str.split('-')
        data['ExpirationDate'] = [data_tmp[i][1] for i in range(len(data))]
        data['ExpirationDate'] = pd.to_datetime(data['ExpirationDate'], format='%d%b%y')
        self.data = data

    def get_date(self):
        dates = pd.Series.sort_values(self.data['ExpirationDate'])
        dates = dates.reset_index(drop=True)
        return dates
class trading:
# Real Trading
    def __init__(self,client_id = Tokens['Deribit']['Read_and_Write']['id'],client_secret = Tokens['Deribit']['Read_and_Write']['secret']):
        # Login read only account
        self.MyAccount = RestClient(client_id,client_secret)
        self.Fee = {
            'Future':{
                        'Market':0.00075,'Limit':-0.0025
                    },
            'Option':0.0004
        }
        self.Max_Order_Num = 3
        self.Trading_Frequency = 1
    def post_order(self,Instrument,Short_or_Long,Post_Size):
        def get_Post_Dist(Orderbook,Bid_or_Ask,Order_Num,Post_Size):
            Top_Order = [Orderbook[Bid_or_Ask][i]['quantity'] for i in range(Order_Num)]
            Order_Weight = [Top_Order[i]*Post_Size/sum(Top_Order) for i in range(Order_Num)]
            # If the order weight is smaller than 0.1 then add it to 0.1
            Order_Weight = [round(w,1) for w in Order_Weight]
            Order_Weight[0] = Order_Weight[0] + (Post_Size - sum(Order_Weight))
            # Transfer it as order dictionary
            Post_Dist = [{'price':Orderbook[Bid_or_Ask][i]['price'],'size':Order_Weight[i]} for i in range(Order_Num)]
            return Post_Dist
        Orderbook = self.MyAccount.getorderbook(Instrument)
        # Post orders by weighted-size
        if Short_or_Long == 'Short':
            Bid_or_Ask = 'asks'
        elif Short_or_Long == 'Long':
            Bid_or_Ask = 'bids'
        Order_Num = min(int(Post_Size/0.1),min(self.Max_Order_Num,len(Orderbook[Bid_or_Ask]))) # Total number of orders will be posted
        Post_Dist = [] # Store price and amount of each orders will be posted
        if Orderbook['bids'][min(2,len(Orderbook['bids']))]['cm']>Orderbook['asks'][min(2,len(Orderbook['bids']))]['cm']:
            # Bid pressure is larger than Ask pressure, then post order at the top orders of asks book
            Post_Dist = get_Post_Dist(Orderbook,Bid_or_Ask,Order_Num,Post_Size)
        else:
            # Bid pressure is smaller than Ask pressure, then post order above and at the orders of ask book
            if Orderbook['asks'][0]['price'] - Orderbook['bids'][0]['price']<0.001:
                Post_Dist = [{'price':Orderbook['bids'][0]['price'],'size':Post_Size}]

            else:
                First_Order = max(0.1,round(Post_Size/3,1))
                Post_Dist = [{'price':Orderbook['asks'][0]['price']-0.0005,'size':First_Order}]
                Post_Dist = Post_Dist + get_Post_Dist(Orderbook,Bid_or_Ask,Order_Num - 1,Post_Size - First_Order)

        Post_Dist = [{'price':Post_Dist[i]['price'],'size':round(Post_Dist[i]['size'],1)} for i in range(len(Post_Dist))]
        # Print the result
        print("There will be %d %s orders posted:"%(Order_Num,Short_or_Long.lower()))
        pprint(Post_Dist)
        Execute_or_Not = input('Post those orders? [y/n]')
        if Execute_or_Not == 'y':
            ###---Post Orders---###
            if Short_or_Long == 'Short':
                [self.MyAccount.sell(Instrument, p['size'], p['price'], False) for p in Post_Dist]
                for p in Post_Dist:
                    if p['size']!=0:
                        self.MyAccount.sell(Instrument, p['size'], p['price'], False)
                time.sleep(self.Trading_Frequency)
            elif Short_or_Long == 'Long':
                for p in Post_Dist:
                    if p['size']!=0:
                        self.MyAccount.buy(Instrument, p['size'], p['price'], False)
                time.sleep(self.Trading_Frequency)
            else:
                print('Error occurs')
                return False
            ###------###
            return True
        else:
            return False
    def get_current_btc_postition(self,Instrument):
        if len(self.MyAccount.positions())!=0:
            if sum([p['instrument'] == 'BTC-PERPETUAL' for p in self.MyAccount.positions()])!= 0: # list is not empty
                Perpetual_Position = self.MyAccount.positions()\
                    [\
                        [p['instrument'] == 'BTC-PERPETUAL' for p in self.MyAccount.positions()].index(True)\
                    ]['sizeBtc']
            else:
                Perpetual_Position = 0
            if sum([p['instrument'] == Instrument for p in self.MyAccount.positions()]) != 0: # list is not empty
                Option_Position = self.MyAccount.positions()\
                    [\
                        [p['instrument'] == Instrument for p in self.MyAccount.positions()].index(True)\
                    ]['size']
            else:
                Option_Position = 0
        else:
            Perpetual_Position = 0
            Option_Position = 0
        return Perpetual_Position,Option_Position
    def hedge(self,Instrument,Strike_Price):
        BTC_Price = float(self.MyAccount.index()['btc'])
        Perpetual_Position,Option_Position = self.get_current_btc_postition(Instrument)
        # Hedge current risk exposure
        while (BTC_Price < Strike_Price) and (Perpetual_Position> Option_Position): # Hedging
            sms_notification('Hedging Start.')
            Execute_or_Not = input('Cancel all orders?[y/n]')
            if Execute_or_Not == 'y':
                self.MyAccount.cancelall()
            else:
                return False
            Orderbook = self.MyAccount.getorderbook('BTC-PERPETUAL')
            Post_Size = abs(float(Perpetual_Position - Option_Position))
            Top_Order_Size = Orderbook['bids'][0]['quantity']/Orderbook['bids'][0]['price'] # Top_Order_Size is counted as BTC
            print("There will be %lf perpetual shorted at %lf"%(min(Post_Size,Top_Order_Size),\
                                                                    Orderbook['bids'][0]['price']))
            sms_notification("There will be %lf perpetual shorted at %lf [y/n]"%(min(Post_Size,Top_Order_Size),\
                                                                    Orderbook['bids'][0]['price']))
            Execute_or_Not = input('Post those orders? [y/n]')
            if Execute_or_Not == 'y':
                ###------ Post Order ------###
                Sell_Size = int(min(\
                                    Post_Size,\
                                    Top_Order_Size\
                                   )*Orderbook['bids'][0]['price']/10\
                               )
                if Sell_Size >=1:
                    self.MyAccount.sell('BTC-PERPETUAL',Sell_Size , Orderbook['bids'][0]['price'], False)
                    time.sleep(self.Trading_Frequency)
                else:
                    print("Sell Size is smaller than the minimal requirement.")
                ###------ Post Order Completed ------###
            else:
                print("Trading Stop")
                return False
            BTC_Price = self.MyAccount.index()['btc']
            Perpetual_Position,Option_Position = self.get_current_btc_postition(Instrument)
        # Clear the previous perpetual position
        Orderbook = self.MyAccount.getorderbook('BTC-PERPETUAL')
        while (int(Orderbook['bids'][0]['price']*Perpetual_Position/10-1e-3)<=-1) and (BTC_Price>=Strike_Price): # Closing position
            Execute_or_Not = input('Cancel all orders?[y/n]')
            if Execute_or_Not == 'y':
                self.MyAccount.cancelall()
            else:
                return False
            Perpetual_Position,Option_Position = self.get_current_btc_postition(Instrument)
            BTC_Price = self.MyAccount.index()['btc']
            if Perpetual_Position>0:
                # Short Perpetual
                Orderbook = self.MyAccount.getorderbook('BTC-PERPETUAL')
                Post_Size = min(abs(float(Perpetual_Position)),Orderbook['bids'][0]['quantity']/Orderbook['bids'][0]['price'])
                print("There will be %lf perpetual shorted at %lf[y/n]"%(Post_Size,Orderbook['bids'][0]['price']))
                sms_notification("There will be %lf perpetual shorted at %lf"%(Post_Size,Orderbook['bids'][0]['price']))
                Execute_or_Not = input('Post those orders? [y/n]')
                if Execute_or_Not == 'y':
                    ###------ Post Order ------###
                    Sell_Size = int(Post_Size*Orderbook['bids'][0]['price']/10)
                    if Sell_Size >= 1:
                        self.MyAccount.sell('BTC-PERPETUAL', Sell_Size, Orderbook['bids'][0]['price'], False)
                        # Post_Size is counted as BTC, but sell perpetual is counted as 10 USD, so we do a bit transfer.
                        time.sleep(self.Trading_Frequency)
                    else:
                        print("Sell Size is smaller than the minimal requirement.")
                    ###------ Post Order Completed ------###
                else:
                    return False
            if Perpetual_Position<0:
                # Long Perpetual
                Orderbook = self.MyAccount.getorderbook('BTC-PERPETUAL')
                Post_Size = min(abs(float(Perpetual_Position)),Orderbook['asks'][0]['quantity']/Orderbook['asks'][0]['price'])
                print("There will be %lf perpetual longed at %lf"%(Post_Size,Orderbook['asks'][0]['price']))
                sms_notification("There will be %lf perpetual longed at %lf[y/n]"%(Post_Size,Orderbook['asks'][0]['price']))
                Execute_or_Not = input('Post those orders? [y/n]')
                if Execute_or_Not == 'y':
                    ###------ Post Order ------###
                    Buy_Size = int(Post_Size*Orderbook['asks'][0]['price']/10)
                    if Buy_Size >= 1:
                        self.MyAccount.buy('BTC-PERPETUAL', Buy_Size, Orderbook['asks'][0]['price'], False)
                        # Post_Size is counted as BTC, but sell perpetual is counted as 10 USD, so we do a bit transfer.
                        time.sleep(self.Trading_Frequency)
                    else:
                        print("Buy Size is smaller than the minimal requirement.")
                    ###------ Post Order Completed ------###
                else:
                    return False

        return True

    def short_option(self,Date,Year,Strike_Price,Contract_Type):
        Instrument = "BTC-%s%s-%s-%s"%(Date.upper(),Year,Strike_Price,Contract_Type.upper())
        Strike_Price = float(Strike_Price)
        # Compute margin and fee cost
        markPrice = self.MyAccount.getsummary(Instrument)['markPrice']
        btcPrice = self.MyAccount.index()['btc']
        MM_Option = max(0.075,0.075*markPrice)+markPrice # Maintianence margin
        IM_Option = max(max(0.15 - (btcPrice - float(Strike_Price))/btcPrice,0.1)+markPrice,MM_Option)# Initial margin
        size = 0.25
        IM_Future = 0.01+(0.005/100)*size
        Required_Balance = (IM_Option+self.Fee['Option'])*size + size*(IM_Future+self.Fee['Future']['Market'])
        Account = self.MyAccount.account()
        while Required_Balance<=Account['availableFunds']:
            print('Required Balance = %lf if size = %lf'%(Required_Balance,size))
            size += 0.05
            IM_Future = 0.01+(0.005/100)*size
            Required_Balance = (IM_Option+self.Fee['Option'])*size + size*(IM_Future+self.Fee['Future']['Market'])

        # Short Option
        Short_Size = float(input("Short Size:")) # Total position will be shorted
        pprint({\
                'availableFunds':self.MyAccount.account()['availableFunds']\
                ,'balance':self.MyAccount.account()['balance']\
                ,'equity':self.MyAccount.account()['equity']\
              ,'initialMargin':self.MyAccount.account()['initialMargin']\
              ,'maintenanceMargin':self.MyAccount.account()['maintenanceMargin']\
               })
        while Short_Size !=0:
            if len(self.MyAccount.positions()) !=0:
                Current_Size = self.MyAccount.positions()\
                [\
                    [p['instrument'] == Instrument for p in self.MyAccount.positions()].index(True)\
                ]['size']
            else:
                Current_Size = 0
            Short_Size = Short_Size - abs(float(Current_Size))
            print("Short Option: %lf"%Short_Size) # Print Short Size
            # Clear all open orders
            Execute_or_Not = input('Cancel all orders?[y/n]')
            if Execute_or_Not == 'y':
                self.MyAccount.cancelall()
            else:
                return False
            # Post new orders
            if not self.post_order(Instrument,'Short',Short_Size):
                print("Trading stop")
                pprint(self.MyAccount.account())
                return 0
            Continue_or_Not = input('Continue Short Options or Not [y/n]')
        # Hedge
        Mon = list(calendar.month_abbr).index(Date[(len(Date)-3):len(Date)][0].upper()+Date[(len(Date)-3):len(Date)][1:3].lower())
        Day = int(Date[0:(len(Date)-3)])
        settle_time = datetime.datetime(int("20%s"%Year),Mon,Day,16,0)
        while datetime.datetime.now() <=settle_time:
             if not self.hedge(Instrument,Strike_Price):
                print("Trading stop")
                pprint(self.MyAccount.account())
                return 0
        return 0
    def alert(self):
        BTC_Price = self.MyAccount.index()['btc']
        if BTC_Price<9125:
            sms_notification("Wake up! Price is out of bound")
class option_data:
    """class 'option_data':
    The method 'download_option_price()' will automatically download the latest option prices
    and return the pandas data frame. The method 'generate_implied_vol()' will calculate the
    implied vol and output the data.

    But all these methods are called in __init__ of this class.
    So once you can create this class, implied vols will be calculated.
    Then you can use option_data.data['Implied_Vol'].

    If you don't know which column name to use, use method option_data.show_names()"""

    def __init__(self, interest_rate=0, K_range=(0, math.inf)):
        # this is my account. Don't share it to others. But I don't have money in it :P
        self.client = RestClient('2SQfDzW1Kaf3F', 'HOG2A2HCYERM2YONRBTYEBMYRZ2ESN3K')
        self.rate = interest_rate
        self.index = self.client.index()['btc']
        # download option prices
        min_K, max_K = K_range
        data_tmp = self._download_option_price(min_K, max_K)
        data_on_index = data_tmp.loc[data_tmp['uIx'] == 'index_price', :]
        data = data_tmp.loc[data_tmp['uIx'] != 'index_price', :]
        # reindex
        data = data.dropna(axis=0)
        data_on_index = data_on_index.dropna(axis=0)
        data = data.reset_index()
        data_on_index = data_on_index.reset_index()
        # save data
        self.data = data
        self.data_on_index = data_on_index
        # calculate implied vol
        self.data['Implied_Vol'] = self.generate_implied_vol()
        self.future_data = self._download_future_price()

    def _download_option_price(self, min_K, max_K):
        data = self.client.getsummary('option')
        # convert this list of dictionaries into data frame
        data = pd.DataFrame.from_dict(data=data)
        data = data.iloc[range(len(data)-1),]
        # split strings to get date and type
        data_tmp = data['instrumentName'].str.split('-')
        data['ExpirationDate'] = [data_tmp[i][1] for i in range(len(data))]
        data['Strike'] = [int(data_tmp[i][2]) for i in range(len(data))]
        data['OptionType'] = [data_tmp[i][3] for i in range(len(data))]
        # moneyness
        data['Moneyness'] = -np.log(data['uPx']/data['Strike'])   # the moneyness should be reversed to normal option
        # get the time when the option is created
        data_tmp = data['created'].str.split(' ')
        data['InitialDate'] = [pd.to_datetime(data_tmp[i][0]) for i in range(len(data))]
        # convert Expiration Date
        data['ExpirationDate'] = pd.to_datetime(data['ExpirationDate'], format='%d%b%y')
        now = datetime.now()
        self.now = now
        data['Texp'] = [(data.loc[i, 'ExpirationDate'] - now).total_seconds()/365/24/60/60 \
                        for i in range(data.shape[0])]
        # filter the strike with proper range
        condition = np.array([min_K*(1 - np.sqrt(data.loc[i, 'Texp']))
                              <= data.loc[i, 'Strike'] <=
                              max_K*( 1+ np.sqrt(data.loc[i, 'Texp']))
                              for i in range(data.shape[0])])
        data = data.loc[np.where(condition)[0],:]
        data = data.reindex(index=range(data.shape[0]))
        return data

    def _download_future_price(self):
        data = self.client.getsummary('future')
        # convert this list of dictionaries into data frame
        data = pd.DataFrame.from_dict(data=data)
        # split strings to get date and type
        data_tmp = data['instrumentName'].str.split('-')
        data['ExpirationDate'] = [data_tmp[i][1] for i in range(len(data))]
        data['ExpirationDate'] = pd.to_datetime(data['ExpirationDate'], format='%d%b%y')
        data['Texp'] = [(data.loc[i, 'ExpirationDate'] - self.now).total_seconds() / 365 / 24 / 60 / 60 \
                        for i in range(data.shape[0])]
        return data

    # ======================================================================Calculator methods

    def generate_implied_vol(self):
        # Option Price, Expiration, Future price, Strike, interest rate, Option Type
        input_data = pd.Series([tuple([self.data.loc[i, 'markPrice'],
                                self.data.loc[i, 'Texp'],
                                self.data.loc[i, 'uPx'],
                                self.data.loc[i, 'Strike'],
                                self.rate,
                                self.data.loc[i, 'OptionType']]
                                ) for i in range(self.data.shape[0])])

        return input_data.apply(self._calculate_implied_vol)

    def _calculate_implied_vol(self, input=(1.6, 1, 20, 20, 0.05, 'C')):
        """The input should be Option Price, Expiration, Future Price, Strike, interest rate, Option Type
        Pricing reference: https://quedex.net/edu/options """

        def Vol_fun(vol, *data_in):
            Price, ExpT, F, K, rate, Option_Type = data_in
            rate = np.float64(rate)

            d1 = (math.log(K / F) + (rate + vol ** 2 / 2) * ExpT) / vol / math.sqrt(ExpT)
            d2 = d1 - vol * math.sqrt(ExpT)
            if Option_Type == 'C':
                return K * (1 / K * norm.cdf(-d2) * math.exp(-rate * ExpT) - 1 / F * norm.cdf(-d1)) - Price
            elif Option_Type == 'P':
                return K * (1 / F * norm.cdf(d1) - 1 / K * math.exp(-rate * ExpT) * norm.cdf(d2)) - Price

        result = fsolve(Vol_fun, np.array(1, dtype=float), args=input,
                        full_output=True)   # solve Implied Vol

        # output NA is the solution was not found
        if result[2]==0:
            return None
        else:
            return result[0][0]

    def _price_calculator(self, *data_in):
        """This is a calculator only for external use; won't be called in the class
        output is number of bitcoins as expected payoff"""
        ExpT, F, K, rate, vol, Option_Type = data_in
        rate = np.float64(rate)
        d1 = (np.log(K / F) + (rate + vol ** 2 / 2) * ExpT) / vol / np.sqrt(ExpT)
        d2 = d1 - vol * np.sqrt(ExpT)
        if Option_Type == 'C':
            return K * (1 / K * norm.cdf(-d2) * math.exp(-rate * ExpT) - 1 / F * norm.cdf(-d1))
        elif Option_Type == 'P':
            return K * (1 / F * norm.cdf(d1) - 1 / K * math.exp(-rate * ExpT) * norm.cdf(d2))

    def fitting(self, x_in, option_type='A', ):
        """Fit the implied vol for each maturity date and output the fitting in vector form"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]

        # interpolate the implied vol for every maturity====================
        # interpolation function:
        # This function do not work well
        def iv_curve(k, a, b, rho, m, sigma):
            return a + b * (rho * (k - m) + np.sqrt((k - m) ** 2 + sigma ** 2))
        def iv_curve2(k, a, b, m):
            return np.abs(a*(k-b))+m
        # how many maturities
        time = np.unique(subset['Texp'])
        N_time = time.shape[0]

        # initiate solution
        fitted_x = np.array([])
        fitted_y = np.array([])
        fitted_z = np.array([])

        # interpolate every maturity
        # using correct equation:
        # for i in range(N_time):
        #     subset_sub = subset.loc[subset['Texp'] == time[i],:]
        #     popt, pcov = curve_fit(iv_curve, subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values)
        #     fitted_x = np.append(fitted_x, x_in)
        #     fitted_y = np.append(fitted_y, np.ones(len(x_in))*time[i])
        #     fitted_z = np.append(fitted_z, iv_curve(x_in, *popt))

        subset_sub = subset.loc[subset['Texp'] == time[0],:]
        method = 'lm'
        # change the optimization warning to exception
        warnings.simplefilter("error", OptimizeWarning)
        try:   # try normal fitting
            popt, pcov = curve_fit(iv_curve, subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values,
                                   method=method)
            value = iv_curve(x_in, *popt)
        except (RuntimeError, OptimizeWarning) as error:   # if no optimal parameters found:
            popt, pcov = curve_fit(iv_curve2, subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values,
                                   method=method)
            value = iv_curve2(x_in, *popt)
        #     popt = np.polyfit(subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values, 2)
        #     foo = np.poly1d(popt)
        #     value = foo(x_in)
        try:
            length = len(x_in)
        except TypeError:
            length = 1
        fitted_x = np.append(fitted_x, x_in)
        fitted_y = np.append(fitted_y, np.ones(length)*time[0])
        fitted_z = np.append(fitted_z, value)
        # using polynomial
        for i in range(1, N_time):
            subset_sub = subset.loc[subset['Texp'] == time[i], :]
            try:
                popt, pcov = curve_fit(iv_curve, subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values,
                                       method=method)
                value = iv_curve(x_in, *popt)
            except (RuntimeError, OptimizeWarning) as error:
                popt, pcov = curve_fit(iv_curve2, subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values,
                                       method=method)
                value = iv_curve2(x_in, *popt)
                # popt = np.polyfit(subset_sub['Moneyness'].values, subset_sub['Implied_Vol'].values, 2)
                # foo = np.poly1d(popt)
                # value = foo(x_in)
            fitted_x = np.append(fitted_x, x_in)
            fitted_y = np.append(fitted_y, np.ones(length) * time[i])
            fitted_z = np.append(fitted_z, value)
        return fitted_x, fitted_y, fitted_z

    def iv_interpolation(self, ExpDate, strike, ExpT, option_type='A'):
        """Output the linear interpolated implied vol by selection"""
        # moneyness of this strike
        F = self.future_data.loc[self.future_data['ExpirationDate']==ExpDate, 'markPrice'].values[0]
        moneyness =  np.array(-np.log(F/strike))
        _, fitted_y, fitted_z = self.fitting(moneyness, option_type)
        return fitted_z[fitted_y == ExpT][0]

    def PnL(self, strike, ExpT_id, option_size = 1, option_type='C'):
        """output the PnL vector at the maturity using hedge ratio
        strike: any user defined strike price. must be integers
        ExpT_id: which expiration date to use. e.g. 0 means the first future expiration
        option_size: the size of the contract
        option_type
        price_range: used for the plotting purpose"""
        ExpT = self.get_date_annual()[ExpT_id]
        ExpDate = self.get_date()[ExpT_id]
        imp_vol = self.iv_interpolation(ExpDate, strike, ExpT)
        # get the future price on this date
        F = self.future_data.loc[self.future_data['Texp'] == ExpT, 'markPrice'].values[0]
        FT = np.linspace(100, F*2, 20000)
        # calculate the price of the option in dollars
        price =  self.index* self._price_calculator(ExpT, F, strike, self.rate, imp_vol, option_type)
        #calculate PnL
        if option_type == 'C':
            PnL_position = F - FT       # short position
            PnL_option = option_size * (np.array([np.max([0, 1 / strike - 1 / i]) for i in FT]) * strike * FT - price)
        elif option_type == 'P':
            PnL_position = FT - F      # long position
            PnL_option = option_size * (np.array([np.max([0, 1 / i - 1 / strike]) for i in FT]) * strike * FT - price)
        PnL = PnL_position + PnL_option
        return pd.DataFrame(np.column_stack((FT, PnL, PnL_option, PnL_position)),
                            columns=['FT', 'PnL', 'PnL_option', 'PnL_position'])

    def prob_of_make_money_option(self, strike, ExpT_id, option_type='C'):
        """calculate the probability of making money at maturity according to the implied vol
        strike: any user defined strike price. must be integers
        ExpT_id: which expiration date to use. e.g. 0 means the first future expiration
        """
        ExpT = self.get_date_annual()[ExpT_id]
        ExpDate = self.get_date()[ExpT_id]
        imp_vol = self.iv_interpolation(ExpDate, strike, ExpT, option_type)
        # get the future price on this date
        F = self.future_data.loc[self.future_data['Texp']==ExpT, 'markPrice']
        # if there is no data, raise exception
        if len(F) == 0:
            print('This Date Has No Future Data.')
            return 0
        F = F.values[0]
        # calculate the price of the option in dollars
        price = self.index * self._price_calculator(ExpT, F, strike, self.rate, imp_vol, option_type)

        if option_type == 'C':
            d2 = (math.log(F/ (strike+ price)) +(self.rate - imp_vol ** 2 / 2) * ExpT) / imp_vol / math.sqrt(ExpT)
            return norm.cdf(d2)
        elif option_type == 'P':
            d2 = (math.log(F / (strike - price)) + (self.rate - imp_vol ** 2 / 2) * ExpT) / imp_vol / math.sqrt(ExpT)
            return norm.cdf(-d2)

    def prob_of_make_money(self, strike, ExpT_id, option_size = 1, option_type='C'):
        """calculate the probability of making money at maturity according to the implied vol
        strike: any user defined strike price. must be integers
        ExpT_id: which expiration date to use. e.g. 0 means the first future expiration
        """
        ExpT = self.get_date_annual()[ExpT_id]
        ExpDate = self.get_date()[ExpT_id]
        # get the future price on this date
        F = self.future_data.loc[self.future_data['Texp']==ExpT, 'markPrice']
        # if there is no data, raise exception
        if len(F) == 0:
            print('This Date Has No Future Data.')
            return 0
        F = F.values[0]
        # calculate PnL and take the positive integration
        result = self.PnL(strike, ExpT_id, option_size, option_type)
        condition_u = [result.loc[i, 'PnL']>=0 for i in range(result.shape[0])]
        condition_index = np.where(condition_u)[0]

        # integrate the probability
        PDF_s = self._generate_prob_distribution(ExpT_id)
        Prob = np.sum(PDF_s.loc[condition_index, 'PDF'])

        return Prob

    def _generate_prob_distribution(self, ExpT_id):
        """Calculate the probability distribution using the implied vol
        reference:
        https://quant.stackexchange.com/questions/1621/how-to-derive-the-implied-probability-distribution-from-b-s-volatilities"""
        ExpT = self.get_date_annual()[ExpT_id]
        ExpDate = self.get_date()[ExpT_id]
        # get the future price on this date
        F = self.future_data.loc[self.future_data['Texp'] == ExpT, 'markPrice'].values[0]
        strike = np.linspace(100, F*2, 20000)
        imp_vol = self.iv_interpolation(ExpDate, strike, ExpT)
        price_data = self._price_calculator(ExpT, F, strike, self.rate, imp_vol, 'C')
        # calculate the second order derivative partial2 C/ partial K2
        dK2 = np.min(np.diff(strike))**2
        distribution = np.exp(self.rate*ExpT)*np.diff(price_data, n=2)/dK2
        # normalize the distribution
        distribution = distribution/sum(distribution)
        return pd.DataFrame(np.column_stack((strike[1:-1], distribution)),columns=['Strike', 'PDF'])


    # =====================================================================data output methods

    def get_date(self, option_type='C'):
        """Get the calendar dates of maturity"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C')
                         or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]

        return np.unique(subset['ExpirationDate'])

    def get_date_annual(self, option_type='C'):
        """get the annualized time"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C')
                         or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]

        return np.unique(subset['Texp'])

    def get_strike(self, option_type='C'):
        """return the strike prices of an option type"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]

        return np.unique(subset['Strike'])

    def output_iv_matrix(self, option_type='A', size=(50,50)):
        """Output the fitted implied vol matrix"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]
        # initiate x y axis
        size_x, size_y = size
        moneyness = np.linspace(np.min(subset['Moneyness']), np.max(subset['Moneyness']), size_x)
        timeline = np.linspace(np.min(subset['Texp']), np.max(subset['Texp']), size_y)

        fitted_x, fitted_y, fitted_z= self.fitting(moneyness, option_type)

        def polyfit2d(x, y, z, order=4):
            ncols = (order + 1) ** 2
            G = np.zeros((x.size, ncols))
            ij = itertools.product(range(order + 1), range(order + 1))
            for k, (i, j) in enumerate(ij):
                G[:, k] = x ** i * y ** j
            m, _, _, _ = np.linalg.lstsq(G, z)
            return m

        def polyval2d(x, y, m):
            order = int(np.sqrt(len(m))) - 1
            ij = itertools.product(range(order + 1), range(order + 1))
            z = np.zeros_like(x)
            for a, (i, j) in zip(m, ij):
                z += a * x ** i * y ** j
            return z

        # use spline to get the full surface matrix
        # tck = it.bisplrep(fitted_x, fitted_y, fitted_z)
        # Z = it.bisplev(moneyness, timeline, tck)

        # use polynomial to get the full surface matrix
        m = polyfit2d(fitted_x, fitted_y, fitted_z)
        xx, yy = np.meshgrid(moneyness, timeline)
        Z = polyval2d(xx, yy, m)
        return moneyness, timeline, Z

    # =====================================================================data viz methods
    def plot_iv(self):
        """Plot the implied vol scatter"""
        subset = self.data
        subset1 = subset.loc[subset['OptionType'] == 'C', :]
        subset2 = subset.loc[subset['OptionType'] == 'P', :]
        ax = plt.axes(projection='3d')
        ax.scatter(subset1['Strike'], subset1['Texp'], subset1['Implied_Vol'], c='r')
        ax.scatter(subset2['Strike'], subset2['Texp'], subset2['Implied_Vol'], c='b')
        ax.set_zlim(np.min(subset['Implied_Vol']) - 0.1, np.max(subset['Implied_Vol']) + 0.1)
        ax.set_title('Implied Vol of Call and Put')
        ax.set_xlabel('Strike')
        ax.set_ylabel('Maturity')
        ax.set_zlabel('Implied Vol')
        plt.show()

    def plot_iv_surface(self,  option_type='A', size=(50,50)):
        """Plot the implied vol surface"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type == 'P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]

        # plot result
        x, y, Z = self.output_iv_matrix(option_type, size)
        X, Y = np.meshgrid(x, y)
        ax = plt.axes(projection='3d')
        ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
                        cmap='viridis', edgecolor='none')
        ax.scatter(subset['Moneyness'], subset['Texp'], subset['Implied_Vol'])
        ax.set_zlim(np.min(subset['Implied_Vol']) - 0.1, np.max(subset['Implied_Vol']) + 0.1)
        ax.set_title('Estimated Implied Vol Surface')
        ax.set_xlabel('Moneyness')
        ax.set_ylabel('Maturity')
        ax.set_zlabel('Implied Vol')
        plt.show()

    def plot_fitted(self, option_type='A', size=(50,50)):
        """Plot the implied vol scatter with fitted curve"""
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type=='P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]
        # initialize x axis
        moneyness = np.linspace(np.min(subset['Moneyness']), np.max(subset['Moneyness']), size[0])
        # fit using this axis
        fitted_x, fitted_y, fitted_z = self.fitting(moneyness, option_type)
        # interpolate every maturity
        ax = plt.axes(projection='3d')
        for Texp in np.unique(subset['Texp']):
            ax.plot(xs=fitted_x[np.where(fitted_y==Texp)],
                    ys=fitted_y[np.where(fitted_y==Texp)],
                    zs=fitted_z[np.where(fitted_y==Texp)])
        ax.scatter(subset['Moneyness'], subset['Texp'], subset['Implied_Vol'])
        ax.set_zlim(np.min(subset['Implied_Vol']) - 0.1, np.max(subset['Implied_Vol']) + 0.1)
        ax.set_title('Fitted Implied Vol')
        ax.set_xlabel('Moneyness')
        ax.set_ylabel('Maturity')
        ax.set_zlabel('Implied Vol')
        plt.show()

    # ================================================================================plotly functions
    def plotly_iv(self):
        # get subset
        subset = self.data
        subset1 = subset.loc[subset['OptionType'] == 'C', :]
        subset2 = subset.loc[subset['OptionType'] == 'P', :]
        CallOption = go.Scatter3d(
            x=subset1['Strike'],
            y= subset1['Texp'],
            z= subset1['Implied_Vol'],
            opacity=0.89,
            name='Call Option',
            mode="markers"
        )
        PutOption = go.Scatter3d(
            x= subset2['Strike'],
            y= subset2['Texp'],
            z= subset2['Implied_Vol'],
            opacity=0.89,
            name='Put Option',
            mode="markers"
        )
        layout = go.Layout(title='Implied Volatility Scatterplot',
            scene = dict(
                xaxis = dict(
                    title='Strike'),
                yaxis = dict(
                    title='Maturity'),
                zaxis = dict(
                    title='Implied Volatility'),)
        )
        fig = go.Figure(data=[CallOption, PutOption], layout=layout)
        py.plot(fig,
                filename=getcwd()+'/app/static/ivsurf_show.html',
                auto_open=False)

    def plotly_fitted(self, option_type='A', size=(50, 50)):
        # get subset
        # consider specified option type
        if option_type == 'C' or option_type=='P':
            subset = self.data.loc[self.data['OptionType'] == option_type, :]
        # consider subset with moneyness >= 0 for call and moneyness <0 for put
        else:
            condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                         (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                         for i in range(self.data.shape[0])]
            subset = self.data.loc[np.where(condition)[0], :]
        # initialize x axis
        moneyness = np.linspace(np.min(subset['Moneyness']), np.max(subset['Moneyness']), size[0])
        # fit using this axis
        fitted_x, fitted_y, fitted_z = self.fitting(moneyness, option_type)

        unique_Texp = np.unique(fitted_y)

        trace1 = go.Scatter3d(
            x=subset['Moneyness'],
            y=subset['Texp'],
            z=subset['Implied_Vol'],
            opacity=0.89,
            mode="markers"
        )

        d = [trace1]

        for i in range(len(unique_Texp)):
            trace1 = go.Scatter3d(
                x=fitted_x[fitted_y==unique_Texp[i]],
                y=fitted_y[fitted_y==unique_Texp[i]],
                z=fitted_z[fitted_y==unique_Texp[i]],
                mode='lines'
            )

            d.append(trace1)
        layout = go.Layout(title='Implied Volatility Fitted Line',
            scene = dict(
                xaxis = dict(
                    title='Moneyness'),
                yaxis = dict(
                    title='Maturity'),
                zaxis = dict(
                    title='Implied Volatility'),)
        )
        fig = go.Figure(data=d, layout=layout)
        py.plot(fig,
                filename=getcwd()+'/app/static/ivsurf_show.html',
                auto_open=False)

    def plotly_iv_surface(self, option_type='A', size=(50, 50)):
            # get subset
            # consider specified option type
            if option_type == 'C' or option_type == 'P':
                subset = self.data.loc[self.data['OptionType'] == option_type, :]
            # consider subset with moneyness >= 0 for call and moneyness <0 for put
            else:
                condition = [(self.data.loc[i, 'Moneyness'] >= 0 and self.data.loc[i, 'OptionType'] == 'C') or
                             (self.data.loc[i, 'Moneyness'] < 0 and self.data.loc[i, 'OptionType'] == 'P')
                             for i in range(self.data.shape[0])]
                subset = self.data.loc[np.where(condition)[0], :]

            # plot result
            X, Y, Z = self.output_iv_matrix(option_type, size)
            trace1 = go.Surface(
                colorscale='Viridis',
                opacity=0.89,
                x=X,
                y=Y,
                z=Z
            )
            trace2 = go.Scatter3d(
                x=subset['Moneyness'],
                y=subset['Texp'],
                z=subset['Implied_Vol'],
                opacity=0.89,
                mode="markers"
            )
            layout = go.Layout(title='Implied Volatility Surface',
                scene = dict(
                    xaxis = dict(
                        title='Moneyness'),
                    yaxis = dict(
                        title='Maturity'),
                    zaxis = dict(
                        title='Implied Volatility'),)
            )
            fig = go.Figure(data=[trace1, trace2], layout=layout)
            py.plot(fig,
                    filename=getcwd()+'/app/static/ivsurf_show.html',
                    auto_open=False)