Example #1
0
    def __init__(self,
                 cash_flows: Collection[pd.Series],
                 rates: Optional[Union[float, Collection[float]]] = None,
                 prices: Optional[Union[float, Collection[float]]] = None,
                 day_count_convention: str = 'bus/252',
                 calendar: str = 'cdr_anbima',
                 ref_date: Date = TODAY):

        msg = 'Parameters rates and prices cannot be both None!'
        assert rates is not None or prices is not None, msg

        msg = 'Parameter cash_flows needs to be a Collection of Pandas Series!'
        assert all(map(lambda x: isinstance(x, pd.Series), cash_flows)), msg

        if rates is not None and isinstance(rates, float):
            rates = [rates]

        if prices is not None and isinstance(prices, float):
            prices = [prices]

        if rates is not None and prices is not None:
            msg = 'Both parameters rates and prices given, dropping prices!'
            warnings.warn(msg)
            prices = None

        self.ref_date = ref_date
        self.dc = DayCounts(dc=day_count_convention, calendar=calendar)

        self.zero_curve = self._initial_zero_curve(cash_flows=cash_flows,
                                                   rates=rates,
                                                   prices=prices,
                                                   dc=self.dc,
                                                   ref_date=self.ref_date)

        self.bootstrap(cash_flows=cash_flows, rates=rates, prices=prices)
Example #2
0
def swap_fixed_leg_pv(today,
                      rate,
                      busdays,
                      calendartype,
                      maturity=10,
                      periodcupons=6,
                      notional=1000000):
    global zero_curve
    dc1 = DayCounts(busdays, calendar=calendartype)
    today = pd.to_datetime(today)
    date_range = pd.date_range(start=today,
                               end=today + DateOffset(years=maturity),
                               freq=DateOffset(months=periodcupons))
    date_range = dc1.modified_following(date_range)

    df = pd.DataFrame(data=date_range[:-1], columns=['Accrual Start'])
    df['Accrual End'] = date_range[1:]
    df['days'] = (df['Accrual End'] - df['Accrual Start']).dt.days
    df['Notional'] = notional

    df['Principal'] = 0
    lastline = df.tail(1)
    df.loc[lastline.index, 'Principal'] = notional

    df['Payment'] = (df['days'] / 360) * rate * df['Notional']

    df['Cash Flow'] = df['Payment'] + df['Principal']

    df['Cumulative Days'] = df['days'].cumsum()

    days = pd.DataFrame(index=df['Accrual End'])

    zero_curve_discount = pd.concat([zero_curve, days], sort=True).sort_index()

    zero_curve_discount = zero_curve_discount.interpolate(
        method='linear',
        axis=0,
        limit=None,
        inplace=False,
        limit_direction='forward',
        limit_area=None,
        downcast=None)

    zero_curve_discount = zero_curve_discount.drop(index=zero_curve.index)
    zero_curve_discount = pd.DataFrame(data=zero_curve_discount.values)
    df['zero_curve_discount'] = zero_curve_discount / 100

    df['Discount'] = 1 / (
        1 + (df['zero_curve_discount'] * df['Cumulative Days'] / 360))
    df['Present Value'] = (df['Cash Flow'] * df['Discount'])
    fixed = np.sum(df['Present Value'])

    return fixed
Example #3
0
    def __init__(self,
                 prices: Union[float, Collection[float]],
                 cash_flows: Union[pd.Series, Collection[pd.Series]],
                 day_count_convention: str = 'bus/252',
                 calendar: str = 'cdr_anbima',
                 ref_date: Date = TODAY,
                 lambdas: Optional[np.array] = ANBIMA_LAMBDAS):

        if isinstance(prices, float):
            prices = [prices]

        if isinstance(cash_flows, pd.Series):
            cash_flows = [cash_flows]

        self.ref_date = ref_date
        self.dc = DayCounts(dc=day_count_convention, calendar=calendar)

        self.lambdas = np.ones(2) if lambdas is None else lambdas
        self.betas = self.estimate_betas(prices=prices,
                                         cash_flows=cash_flows,
                                         dc=self.dc,
                                         ref_date=self.ref_date,
                                         lambdas=self.lambdas)
Example #4
0
from bloomberg import BBG
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from calendars import DayCounts

dc = DayCounts(dc='bus/252', calendar='anbima')
start_date = "28-aug-1997"
end_date = '28-jun-2019'

bbg = BBG()
try:
    df2 = bbg.fetch_series(securities=['LLU99 Comdty', 'LLV99 Comdty'],
                           fields=['LAST_PRICE'],
                           startdate=start_date,
                           enddate=end_date)
except KeyError as e:
    pass

d_c1 = df2['LLU99 Comdty'].dropna().index[-1][1].to_pydatetime()
d_c2 = df2.index[-1][1].to_pydatetime()

res = dc.days(pd.to_datetime(start_date), d_c1)
print(res)
res = dc.days(pd.to_datetime(start_date), d_c2)
print(res)
Example #5
0
from bloomberg import BBG
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from calendars import DayCounts

dc = DayCounts(dc='bus/252', calendar='anbima')
d1 = pd.to_datetime('2015-01-01')
res = dc.eom_preceding(d1)  # pega fim do mes
res = dc.isbus(d1)  # verifica se é bd
res = dc.preceding(d1)  # pegar um bd anterior

letras = ['F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'U', 'V', 'X', 'Z']
meses = [
    'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct',
    'nov', 'dec'
]


def build_ticker(commodity, month, year):
    if month <= 12:
        letra = letras[month - 1]
        y = year % 100
        year_2d = f"{y:02d}"
    else:
        letra = letras[(month - 1) % 12]
        y = (year + month // 12) % 100
        year_2d = f"{y:02d}"
    return commodity + letra + year_2d + " Comdty"

Example #6
0
import pandas as pd
from tqdm import tqdm
from calendars import DayCounts

dc = DayCounts('BUS/252', calendar='anbima')

# BW path
# file_path = r'C:\Users\gamarante\Dropbox\Aulas\Insper - Financas Quantitativas\VNA Raw.xlsx'

# macbook path
file_path = r'/Users/gusamarante/Dropbox/Aulas/Insper - Financas Quantitativas/VNA Raw.xlsx'

df_mensal = pd.read_excel(file_path, 'Mensal', index_col=0)
df_diario = pd.read_excel(file_path, 'Diario', index_col=0, na_values=['#N/A N/A'])
df_release = pd.read_excel(file_path, 'Release')
df_release.columns = ['Date', 'IPCA']

df = pd.DataFrame(index=pd.date_range('2003-03-18', 'today', freq='D'),
                  columns=['dia util', 'ultima virada', 'DU desde virada', 'DU entre viradas', 'time fraction',
                           'proj anbima', 'saiu IPCA', 'ultimo IPCA', 'proj IPCA', 'VNA'])
df.index.name = 'Date'

df['dia util'] = dc.isbus(df.index)

# TODO com certeza existe um meio mais eficiente de fazer isso
for d in tqdm(df.index, 'Filling "ultima virada"'):
    if d.day >= 15:
        df.loc[d, 'ultima virada'] = pd.datetime(d.year, d.month, 15)
    else:
        if d.month - 1 == 0:
            df.loc[d, 'ultima virada'] = pd.datetime(d.year-1, 12, 15)
Example #7
0
 def __init__(self, db_connect):
     self.conn = db_connect
     self.time_series = self._get_time_series()
     self.dc = DayCounts(dc=self.dc_convention, calendar=self.calendar)
Example #8
0
class B3AbstractDerivative(object):

    monthdict = {
        'F': 1,
        'G': 2,
        'H': 3,
        'J': 4,
        'K': 5,
        'M': 6,
        'N': 7,
        'Q': 8,
        'U': 9,
        'V': 10,
        'X': 11,
        'Z': 12
    }

    calendar = None
    contract = None
    dc_convention = None
    pilar_day = None
    roll_method = None

    def __init__(self, db_connect):
        self.conn = db_connect
        self.time_series = self._get_time_series()
        self.dc = DayCounts(dc=self.dc_convention, calendar=self.calendar)

    def time_menu(self, code=None):
        """
        Gets all of the available trading dates for the contract in 'code'.
        If 'code' is None, all dates are returned.
        :param code: str with the contract code
        :return: list of timestamps
        """
        if code is None:
            tm = self.time_series.index.levels[0]
        else:
            tm = self.time_series.index.get_loc_level(code, 1)[1]

        return list(tm)

    def market_menu(self, t=None):
        """
        Gets all of the available contracts codes for date 't'.
        If 't' is None, all contract codes are returned.
        :param t: any format accepted by pandas.to_datetime()
        :return: list of contract codes
        """
        if t is None:
            mm = self.time_series.index.levels[1]
        else:
            t = pd.to_datetime(t)
            mm = self.time_series.index.get_loc_level(t, 0)[1]

        return list(mm)

    def maturity(self, code):
        """
        Given a contract code, returns the timestamp of the maturity date.
        THIS FUNCTION WILL BREAK WHEN CONTRACTS MATURE IN 2092 START TRADING
        :param code: str with contract code
        :return: tuple (year, month)
        """
        month = self.monthdict[code.upper()[0]]
        year = int(code.upper()[1:])

        if year >= 92:
            year = year + 1900
        else:
            year = year + 2000

        mat_date = date(year, month, self.pilar_day)
        mat_date = self.dc.busdateroll(mat_date, self.roll_method)
        return mat_date

    def du2maturity(self, t, code):
        """
        returns the number of business days between t and the maturity date of the contract
        :param t: current date
        :param code: contract code
        :return: int
        """
        mat_date = self.maturity(code)
        du = self.dc.days(t, mat_date)
        return du

    def dc2maturity(self, t, code):
        """
        returns the number of actual days between t and the maturity date of the contract, independent
        of the daycount convention
        :param t: current date
        :param code: contract code
        :return: int
        """
        mat_date = self.maturity(code)
        du = self.dc.daysnodc(t, mat_date)
        return du

    def volume(self, code, t=None):
        """
        returns the trading volume. If 't' is None, returns a pandas Series of the volume of 'code'.
        """
        if t is None:
            filter_contract = self.time_series.index.get_loc_level(code, 1)[0]
            v = self.time_series[filter_contract]['trading_volume'].droplevel(
                1)
        else:
            v = self.time_series['trading_volume'].loc[t, code]

        return v

    def open_interest(self, code, t=None):
        """
        returns the open interest at the close of date 't'.
        If 't' is None, returns a pandas Series of the volume of 'code'.
        """
        if t is None:
            filter_contract = self.time_series.index.get_loc_level(code, 1)[0]
            v = self.time_series[filter_contract][
                'open_interest_close'].droplevel(1)
        else:
            v = self.time_series['open_interest_close'].loc[t, code]

        return v

    def pnl(self, code, t):
        # TODO implement (precisa da série do CDI, mas acho que da pra pegar com a API do SGS)
        pass

    def build_df(self):
        # TODO implement
        return

    def filter(self):
        # TODO implement
        pass

    def _get_time_series(self):
        """
        Fetches the whole database for the given contract
        """

        sql_query = self._time_series_query()

        df = pd.read_sql(sql=sql_query,
                         con=self.conn.connection,
                         parse_dates={'time_stamp': '%Y-%m-%d'})

        df = df.set_index(['time_stamp', 'maturity_code'])

        return df

    def _time_series_query(self):

        sql_query = f'SELECT TIME_STAMP, MATURITY_CODE, OPEN_INTEREST_OPEN, OPEN_INTEREST_CLOSE, ' \
                    f'NUMBER_OF_TRADES, TRADING_VOLUME, FINANCIAL_VOLUME, PREVIOUS_SETTLEMENT, ' \
                    f'INDEXED_SETTLEMENT, OPENING_PRICE, MINIMUM_PRICE, MAXIMUM_PRICE, AVERAGE_PRICE, ' \
                    f'LAST_PRICE, SETTLEMENT_PRICE, LAST_BID, LAST_OFFER FROM "B3futures" ' \
                    f'WHERE CONTRACT=\'{self.contract}\' ORDER BY(TIME_STAMP, MATURITY_CODE);'

        return sql_query
Example #9
0
focus_selic = [
    5.0, 5.50, 6.13, 6.50, 6.50
]
focus_ipca = [
    4.81,  5.04, 3.61, 3.25, 3.25
]

focus_selic[0] = focus_selic[0]**(126/252)
selic_rates = [x/100 + 0.3477/100 for x in focus_selic]
import sys
sys.path.append(f'../../FinanceHub')

from calendars import DayCounts
import pandas as pd

dc = DayCounts('BUS/252', calendar='anbima')
today = pd.to_datetime('2021-04-26')
d = dc.days(today, pd.to_datetime('2024-12-31'))

a = (1+8.38/100)**(d/252)
print(a)
print(1000*a)
Example #10
0
class CurveBootstrap(object):
    def __init__(self,
                 cash_flows: Collection[pd.Series],
                 rates: Optional[Union[float, Collection[float]]] = None,
                 prices: Optional[Union[float, Collection[float]]] = None,
                 day_count_convention: str = 'bus/252',
                 calendar: str = 'cdr_anbima',
                 ref_date: Date = TODAY):

        msg = 'Parameters rates and prices cannot be both None!'
        assert rates is not None or prices is not None, msg

        msg = 'Parameter cash_flows needs to be a Collection of Pandas Series!'
        assert all(map(lambda x: isinstance(x, pd.Series), cash_flows)), msg

        if rates is not None and isinstance(rates, float):
            rates = [rates]

        if prices is not None and isinstance(prices, float):
            prices = [prices]

        if rates is not None and prices is not None:
            msg = 'Both parameters rates and prices given, dropping prices!'
            warnings.warn(msg)
            prices = None

        self.ref_date = ref_date
        self.dc = DayCounts(dc=day_count_convention, calendar=calendar)

        self.zero_curve = self._initial_zero_curve(cash_flows=cash_flows,
                                                   rates=rates,
                                                   prices=prices,
                                                   dc=self.dc,
                                                   ref_date=self.ref_date)

        self.bootstrap(cash_flows=cash_flows, rates=rates, prices=prices)

    @staticmethod
    def _initial_zero_curve(cash_flows: Collection[pd.Series],
                            rates: Optional[Collection[float]] = None,
                            prices: Optional[Collection[float]] = None,
                            dc: Optional[DayCounts] = None,
                            ref_date: Optional[Date] = None) -> pd.Series:

        if rates is not None:
            bonds = zip(cash_flows, rates)
            isrates = True
            if prices is not None:
                msg = 'Both parameters rates and prices given, dropping prices!'
                warnings.warn(msg)
                del prices
        else:
            bonds = zip(cash_flows, prices)
            isrates = False

        ytm = []
        curve = []
        for cf, y in bonds:
            if len(cf) == 1:
                d = pd.to_datetime(cf.index[-1]).date()
                t = dc.tf(ref_date, d)

                if isrates:
                    r = float(y)
                else:
                    r = (cf.iloc[-1] / float(y))**(1. / t) - 1.

                ytm += [t]
                curve += [r]

        return pd.Series(index=ytm, data=curve).sort_index()

    def rate_for_date(self, t: Union[float, Date]) -> float:

        y = flat_forward_interpolation(t=t,
                                       zero_curve=self.zero_curve,
                                       dc=self.dc,
                                       ref_date=self.ref_date)
        return y

    @staticmethod
    def _bond_pv_for_rate(expanded_rate: float,
                          zero_curve: pd.Series,
                          bond_cash_flows: pd.Series,
                          dc: Optional[DayCounts] = None,
                          ref_date: Optional[Date] = None) -> float:

        zero_curve_end = max(zero_curve.index)
        ytm = dc.tf(ref_date, max(bond_cash_flows.index))
        expanded_point = pd.Series(index=[ytm], data=expanded_rate)
        zero_curve = zero_curve.append(expanded_point).sort_index().copy()

        pv = 0.
        for d, c in bond_cash_flows.items():
            t = dc.tf(ref_date, d)
            if t > zero_curve_end:
                y = flat_forward_interpolation(t=t,
                                               zero_curve=zero_curve,
                                               dc=dc,
                                               ref_date=ref_date)
                pv += c / (1. + y)**t

        return pv

    @staticmethod
    def _bond_strip(
            zero_curve: pd.Series,
            bond_cash_flows: pd.Series,
            dc: Optional[DayCounts] = None,
            ref_date: Optional[Date] = None,
            rate: Optional[float] = None,
            price: Optional[float] = None) -> Tuple[float, float, pd.Series]:

        msg = 'Parameters rate and price cannot be both None!'
        assert rate is not None or price is not None, msg

        msg = 'Bond maturity is shorter than given zero curve!'
        maturity = dc.tf(ref_date, max(bond_cash_flows.index))
        zero_curve_end = max(zero_curve.index)
        assert zero_curve_end <= maturity, msg

        if price is not None:
            price = float(price)
            if rate is not None:
                msg = 'Both parameters rate and price given, dropping rate!'
                warnings.warn(msg)
                del rate
        else:
            price = 0.
            for d, c in bond_cash_flows.items():
                ytm = dc.tf(ref_date, d)
                price += c / (1. + rate)**ytm

        pv = 0.

        for d, c in bond_cash_flows.sort_index().items():
            t = dc.tf(ref_date, d)
            if t <= zero_curve_end:
                y = flat_forward_interpolation(t=t,
                                               zero_curve=zero_curve,
                                               dc=dc,
                                               ref_date=ref_date)
                pv += c / (1. + y)**t

        return price, pv, maturity

    def _expand_zero_curve(self,
                           bond_cash_flows: pd.Series,
                           rate: Optional[float] = None,
                           price: Optional[float] = None):

        price, pv, ytm = self._bond_strip(zero_curve=self.zero_curve,
                                          bond_cash_flows=bond_cash_flows,
                                          dc=self.dc,
                                          ref_date=self.ref_date,
                                          rate=rate,
                                          price=price)

        price_given_rate = lambda x: self._bond_pv_for_rate(
            expanded_rate=x,
            zero_curve=self.zero_curve,
            bond_cash_flows=bond_cash_flows,
            dc=self.dc,
            ref_date=self.ref_date) + pv

        pct_error = lambda x: (price - price_given_rate(x)) / price
        obj_fun = lambda x: pct_error(x)**2.
        x0 = self.zero_curve.iloc[-1]
        res = opt.minimize(obj_fun, x0, method='SLSQP')
        if not res['message'] == 'Optimization terminated successfully.':
            raise ArithmeticError('Optimization convergence may have failed!')

        expanded_point = pd.Series(index=[ytm], data=res.x)

        return self.zero_curve.append(expanded_point).sort_index()

    def bootstrap(self,
                  cash_flows: Collection[pd.Series],
                  rates: Optional[Collection[float]] = None,
                  prices: Optional[Collection[float]] = None):

        msg = 'Parameters rates and prices cannot be both None!'
        assert rates is not None or prices is not None, msg

        tmax = max(self.zero_curve.index)
        for i in range(len(cash_flows)):
            cf = list(cash_flows)[i]
            if len(cf) > 1 and self.dc.tf(self.ref_date, max(cf.index)) > tmax:
                if rates is None or list(rates)[i] is None:
                    new_curve = self._expand_zero_curve(bond_cash_flows=cf,
                                                        rate=None,
                                                        price=list(prices)[i])
                elif prices is None or list(prices)[i] is None:
                    new_curve = self._expand_zero_curve(bond_cash_flows=cf,
                                                        rate=list(rates)[i],
                                                        price=None)
                else:
                    continue
                self.zero_curve = new_curve
            else:
                continue
Example #11
0
# d1 = pd.to_datetime('2015-01-01')
# d2 = pd.to_datetime('2019-07-07')

# Case 2 - d1 is a collection and d2 is a Timestamp
# d1 = pd.date_range('2019-01-01', '2019-12-31', freq='M')
# d2 = pd.to_datetime('2019-07-07')

# Case 3 - d1 is a Timestamp and d2 is a collection
# d1 = pd.to_datetime('2019-07-07')
# d2 = pd.date_range('2019-01-01', '2019-12-31', freq='M')

# Case 4 - d1 and d2 are collections
d1 = pd.date_range('2015-01-01', '2015-12-31', freq='M')
d2 = pd.date_range('2019-01-01', '2019-12-31', freq='M')

dc = DayCounts(dc='bus/252', calendar='anbima')

print('tf - Time Fraction')
res = dc.tf(d1, d2)
print(res, '\n')

print('days - Number of days')
res = dc.days(d1, d2)
print(res, '\n')

print('Adjust - ????')

res = dc.adjust(d1)
print(res, '\n')

print('daysnodc - Actual number of days, irrespective of daycount')
Example #12
0
import pandas as pd
from tqdm import tqdm
from calendars import DayCounts

dc = DayCounts('BUS/252', calendar='anbima')

# BW path
# file_path = r'C:\Users\gamarante\Dropbox\Aulas\Insper - Financas Quantitativas\VNA Raw.xlsx'

# macbook path
# file_path = r'/Users/gusamarante/Dropbox/Aulas/Insper - Financas Quantitativas/VNA Raw.xlsx'

# mac path
file_path = r'/Users/gustavoamarante/Dropbox/Aulas/Insper - Financas Quantitativas/VNA Raw.xlsx'

df_mensal = pd.read_excel(file_path, 'Mensal', index_col=0)
df_diario = pd.read_excel(file_path,
                          'Diario',
                          index_col=0,
                          na_values=['#N/A N/A'])
df_release = pd.read_excel(file_path, 'Release')
df_release.columns = ['Date', 'IPCA']

df = pd.DataFrame(index=pd.date_range('2003-03-18', 'today', freq='D'),
                  columns=[
                      'dia util', 'ultima virada', 'DU desde virada',
                      'DU entre viradas', 'time fraction', 'proj anbima',
                      'saiu IPCA', 'ultimo IPCA', 'proj IPCA', 'ultimo index',
                      'VNA'
                  ])
df.index.name = 'Date'
Example #13
0
class FwdIRSTrackers(object):
    """
    Class for creating excess return indices for rolling interest rate swaps using data from bloomberg.
    At the start date, we assume we trade 100 notional 1M forward starting swaps with user provided maturity.
    We mark-to-market the position over the month and then roll it into the new 1M forward at the end of the month
    """

    # TODO: Include more currencies
    currency_bbg_code_dict = {
        'AUD': 'AD',
        'CAD': 'CD',
        'CHF': 'SF',
        'EUR': 'EU',
        'GBP': 'BP',
        'JPY': 'JY',
        'NZD': 'ND',
        'SEK': 'SK',
        'USD': 'US'
    }

    # TODO: Check these are correct
    currency_calendar_dict = {
        'USD': DayCounts('30E/360 ISDA', calendar='us_trading'),
        'AUD': DayCounts('ACT/365', calendar='us_trading'),
        'CAD': DayCounts('ACT/365', calendar='us_trading'),
        'CHF': DayCounts('30A/360', calendar='us_trading'),
        'EUR': DayCounts('30U/360', calendar='us_trading'),
        'GBP': DayCounts('ACT/365', calendar='us_trading'),
        'JPY': DayCounts('ACT/365F', calendar='us_trading'),
        'NZD': DayCounts('ACT/365', calendar='us_trading'),
        'SEK': DayCounts('30A/360', calendar='us_trading')
    }

    def __init__(self,
                 ccy='USD',
                 tenor=10,
                 start_date='2004-01-05',
                 end_date='today'):
        """
        Returns an object with the following attributes:
            - spot_swap_rates: Series with the rate for the spot starting swaps
            - fwd_swap_rates: Series with the rate for the 1M forward starting swaps
            - er_index: Series with the excess return index
        :param ccy_symbol: str, Currency symbol
        :param tenor: int, Tenor for the swap
        :param start_date: str, when the tracker should start
        :param end_date: str, when the tracker should end
        """

        self.ccy = ccy
        self.tenor = tenor
        self.start_date = (pd.to_datetime(start_date) + BDay(1)).date()
        self.end_date = pd.to_datetime(end_date).date()
        self.dc = self.currency_calendar_dict[ccy]

        self.bbg = BBG()
        self.spot_swap_rates = self._get_spot_swap_rates()
        self.fwd_swap_rates = self._get_1m_fwd_swap_rates()
        self.df_tracker = self._calculate_tr_index()

        self.country = self.bbg.fetch_contract_parameter(
            self.ticker_spot, 'COUNTRY_ISO').iloc[0, 0].upper()
        self.exchange_symbol = self.bbg.fetch_contract_parameter(
            self.ticker_fwd, 'TICKER').iloc[0, 0]
        self.fh_ticker = 'irs ' + self.country.lower() + ' ' + self.ccy.lower()

        self.df_metadata = pd.DataFrame(data={
            'fh_ticker': self.fh_ticker,
            'asset_class': 'fixed income',
            'type': 'swap',
            'currency': self.ccy.upper(),
            'country': self.country.upper(),
            'maturity': self.tenor,
            'roll_method': '1 month'
        },
                                        index=[self.ticker_spot])

        self.df_tracker = self._get_tracker_melted()

    def _calculate_tr_index(self):
        spot_rate_series = self.spot_swap_rates.iloc[:, 0].dropna()
        fwd_rate_series = self.fwd_swap_rates.iloc[:, 0].dropna()

        ts_df = pd.concat([
            spot_rate_series.to_frame('spot'),
            fwd_rate_series.to_frame('fwd_1m')
        ],
                          axis=1,
                          sort=True).fillna(method='ffill').dropna()

        er_index = pd.Series(index=ts_df.index)
        er_index.iloc[0] = 100

        start_date = er_index.index[0]
        fwd_swap_rate = ts_df.loc[start_date, 'fwd_1m'] / 100
        ref_swap_rate_d_minus_1 = fwd_swap_rate

        roll_date = self.dc.busdateroll(start_date + relativedelta(months=1),
                                        'modifiedfollowing')
        fwd_mat = self.dc.busdateroll(
            start_date + relativedelta(months=1 + self.tenor * 12),
            'modifiedfollowing')

        for d in er_index.index[1:]:
            current_fwd_swap_rate = ts_df.loc[d, 'fwd_1m'] / 100
            current_spot_swap_rate = ts_df.loc[d, 'spot'] / 100
            curr_fwd_mat = self.dc.busdateroll(
                d + relativedelta(months=1 + self.tenor * 12),
                'modifiedfollowing')
            curr_spot_mat = self.dc.busdateroll(
                d + relativedelta(months=self.tenor * 12), 'modifiedfollowing')

            w = 1 if d >= roll_date else self.dc.tf(
                curr_fwd_mat, fwd_mat) / self.dc.tf(curr_fwd_mat,
                                                    curr_spot_mat)

            ref_swap_rate = w * current_spot_swap_rate + (
                1 - w) * current_fwd_swap_rate

            # This is the present value of a basis point
            # Using the interpolated forward swap rate as an internal rate of return
            pv01 = np.sum([(1 / 2) * ((1 + ref_swap_rate / 2)**(-i))
                           for i in range(1, self.tenor * 2 + 1)])

            if np.isnan((ref_swap_rate_d_minus_1 - ref_swap_rate) * pv01):
                ret = 0
            else:
                ret = (ref_swap_rate_d_minus_1 - ref_swap_rate) * pv01

            er_index[d] = er_index[:d].iloc[-2] * (1 + ret)

            if d >= roll_date:
                roll_date = self.dc.busdateroll(d + relativedelta(months=1),
                                                'modifiedfollowing')
                fwd_mat = self.dc.busdateroll(
                    d + relativedelta(months=1 + self.tenor * 12),
                    'modifiedfollowing')
                ref_swap_rate_d_minus_1 = ts_df.loc[d, 'fwd_1m'] / 100
            else:
                ref_swap_rate_d_minus_1 = ref_swap_rate

        return er_index.to_frame('er_index')

    def _get_spot_swap_rates(self):
        if self.ccy == 'EUR':
            ticker = self.currency_bbg_code_dict[self.ccy] + 'SA' + str(
                self.tenor) + ' Curncy'
        elif self.ccy == 'NZD':
            ticker = self.currency_bbg_code_dict[self.ccy] + 'SWAP' + str(
                self.tenor) + ' Curncy'
        else:
            # The AUD is using the 6M floating leg case as default to be consistent with the forward ticker
            # The correct ticker is ADSW10Q Curncy 3M floating leg but then
            # you cannot use S0302FS 1M10Y BLC Curncy for the forward swap
            ticker = self.currency_bbg_code_dict[self.ccy] + 'SW' + str(
                self.tenor) + ' Curncy'

        bbg_raw_spot_data = self.bbg.fetch_series(securities=ticker,
                                                  fields='PX_LAST',
                                                  startdate=self.start_date,
                                                  enddate=self.end_date)

        self.ticker_spot = ticker

        bbg_raw_spot_data.columns = [self.ccy + str(self.tenor) + 'Y']
        bbg_raw_spot_data.index = pd.to_datetime(bbg_raw_spot_data.index)

        return bbg_raw_spot_data.dropna(how='all')

    def _get_1m_fwd_swap_rates(self):
        if self.ccy == 'EUR':
            ticker = self.currency_bbg_code_dict[self.ccy] + 'SAA' + str(
                self.tenor).zfill(2) + ' Curncy'
        elif self.ccy == 'GBP':
            ticker = self.currency_bbg_code_dict[self.ccy] + 'SWA' + str(
                self.tenor).zfill(2) + ' Curncy'
        elif self.ccy == 'AUD':
            # The AUD is using the 6M floating leg case as default to be consistent with the forward ticker
            # The correct ticker is ADSW10Q Curncy 3M floating leg but then
            # you cannot use S0302FS 1M10Y BLC Curncy for the forward swap
            ticker = 'S0302FS 1M' + str(self.tenor) + 'Y BLC Curncy'
        elif self.ccy == 'USD':
            ticker = self.currency_bbg_code_dict[self.ccy] + 'FS0A' + str(
                self.tenor) + ' Curncy'
        else:
            ticker = self.currency_bbg_code_dict[self.ccy] + 'FS0A' + str(
                self.tenor).zfill(2) + ' Curncy'

        bbg_fwd_data = self.bbg.fetch_series(securities=ticker,
                                             fields='PX_LAST',
                                             startdate=self.start_date,
                                             enddate=self.end_date)

        self.ticker_fwd = ticker

        bbg_fwd_data.columns = [self.ccy + str(self.tenor) + 'Y']
        bbg_fwd_data.index = pd.to_datetime(bbg_fwd_data.index)

        return bbg_fwd_data.dropna(how='all')

    def _get_tracker_melted(self):
        df = self.df_tracker[['er_index'
                              ]].rename({'er_index': self.ticker_spot}, axis=1)
        df['time_stamp'] = df.index.to_series()
        df = df.melt(id_vars='time_stamp',
                     var_name='fh_ticker',
                     value_name='value')
        df = df.dropna()

        return df
Example #14
0
"""
Author: Gustavo Soares
"""
import warnings
from typing import Optional
import pandas as pd
import numpy as np
from calendars import DayCounts
from calendars.custom_date_types import Date, TODAY
from scipy import optimize

dc = DayCounts('bus/252', calendar='cdr_anbima')


def truncate(number, decimals=0):
    """Returns a value truncated to a specific number of decimal places"""
    if not isinstance(decimals, int):
        raise TypeError("decimal places must be an integer.")
    elif decimals < 0:
        raise ValueError("decimal places has to be 0 or more.")
    elif decimals == 0:
        return np.trunc(number)
    factor = 10.0**decimals
    return np.trunc(number * factor) / factor


class LTN(object):
    def __init__(self,
                 expiry: Date,
                 rate: Optional[float] = None,
                 price: Optional[float] = None,