Exemplo n.º 1
0
class FXForwardsPricer(AbstractPricer):
    """Prices forwards for odd dates which are not quoted using linear interpolation,
    eg. if we have forward points for 1W and 1M, and spot date but we want to price a 3W forward, or any arbitrary horizon
    date that lies in that interval

    Also calculates the implied deposit rate from FX spot, FX forward points and deposit rate.
    """
    def __init__(self, market_df=None, quoted_delivery_df=None):
        self._calendar = Calendar()
        self._market_df = market_df
        self._quoted_delivery_df = quoted_delivery_df

    def price_instrument(self,
                         cross,
                         horizon_date,
                         delivery_date,
                         market_df=None,
                         quoted_delivery_df=None,
                         fx_forwards_tenor_for_interpolation=market_constants.
                         fx_forwards_tenor_for_interpolation):
        """Creates an interpolated outright FX forward (and the associated points), for horizon dates/delivery dates
        given by the user from FX spot rates and FX forward points. This can be useful when we have an odd/broken date
        which isn't quoted.

        Uses linear interpolation between quoted dates to calculate the appropriate interpolated forward. Eg. if we
        ask for a delivery date in between 1W and 1M, we will interpolate between those.

        Parameters
        ----------
        cross : str
            Currency pair

        horizon_date : DateTimeIndex
            Horizon dates for forward contracts

        delivery_date : DateTimeIndex
            Delivery dates for forward contracts

        market_df : DataFrame
            Contains FX spot and FX forward points data

        quoted_delivery_df : DataFrame (DateTimeIndex)
            Delivery dates for every quoted forward point

        fx_forwards_tenor_for_interpolation : str(list)
            Which forwards to use for interpolation

        Returns
        -------
        DataFrame
        """

        if market_df is None: market_df = self._market_df
        if quoted_delivery_df is None:
            quoted_delivery_df = self._quoted_delivery_df

        if quoted_delivery_df is None:
            quoted_delivery_df = self.generate_quoted_delivery(
                cross, market_df, quoted_delivery_df,
                fx_forwards_tenor_for_interpolation, cross)

        # Make horizon date and delivery date pandas DatetimeIndex
        if isinstance(horizon_date, pd.Timestamp):
            horizon_date = pd.DatetimeIndex([horizon_date])
            delivery_date = pd.DatetimeIndex([delivery_date])
        else:
            horizon_date = pd.DatetimeIndex(horizon_date)
            delivery_date = pd.DatetimeIndex(delivery_date)

        # Truncate the market data so covers only the dates where we want to price the forwards
        market_df = market_df[market_df.index.isin(horizon_date)]
        quoted_delivery_df = quoted_delivery_df[quoted_delivery_df.index.isin(
            horizon_date)]

        cal = cross

        # Get the spot date (different currency pairs have different conventions for this!)
        spot_date = self._calendar.get_spot_date_from_horizon_date(
            horizon_date, cal)

        # How many days between spot date (typically T+1 or T+2) and delivery date
        spot_delivery_days_arr = (delivery_date - spot_date).days

        spot_arr = market_df[cross + '.close'].values

        quoted_delivery_df, quoted_delivery_days_arr, forwards_points_arr, divisor = \
            self._setup_forward_calculation(cross, spot_date, market_df, quoted_delivery_df, fx_forwards_tenor_for_interpolation)

        interpolated_outright_forwards_arr = _forwards_interpolate_numba(
            spot_arr, spot_delivery_days_arr, quoted_delivery_days_arr,
            forwards_points_arr, len(fx_forwards_tenor_for_interpolation))

        interpolated_df = pd.DataFrame(
            index=market_df.index,
            columns=[
                cross + '-interpolated-outright-forward.close',
                cross + "-interpolated-forward-points.close"
            ])

        interpolated_df[
            cross +
            '-interpolated-outright-forward.close'] = interpolated_outright_forwards_arr
        interpolated_df[cross + "-interpolated-forward-points.close"] = (
            interpolated_outright_forwards_arr - spot_arr) * divisor

        return interpolated_df

    def get_day_count_conv(self, currency):
        if currency in market_constants.currencies_with_365_basis:
            return 365.0

        return 360.0

    def get_forwards_divisor(self, currency):
        # Typically most divisors of forward points are 10000.0
        divisor = 10000.0

        if currency in market_constants.fx_forwards_points_divisor_100:
            divisor = 100.0

        if currency in market_constants.fx_forwards_points_divisor_1000:
            divisor = 1000.0

        return divisor

    def _setup_forward_calculation(self, cross, spot_date, market_df,
                                   quoted_delivery_df, fx_forwards_tenor):

        cal = cross

        # Get the quoted delivery dates for every quoted tenor in our forwards market data
        # Eg. what's the delivery date for EURUSD SN, 1W etc.
        quoted_delivery_df = self.generate_quoted_delivery(
            cross, market_df, quoted_delivery_df, fx_forwards_tenor, cal)

        # Typically most divisors of forward points are 10000.0 (but JPY is an exception)
        divisor = self.get_forwards_divisor(cross[3:6])

        forwards_points_arr = market_df[[
            cross + x + ".close" for x in fx_forwards_tenor
        ]].values / divisor

        quoted_delivery_days_arr = np.zeros(
            (len(quoted_delivery_df.index), len(fx_forwards_tenor)))

        # Get difference between each quoted point and the spot date (NOT horizon date)
        # eg. for 1W this is generally 7 days, for 2W it's 14 days etc.
        for i, tenor in enumerate(fx_forwards_tenor):
            quoted_delivery_days_arr[:, i] = (pd.DatetimeIndex(
                quoted_delivery_df[cross + tenor + ".delivery"]) -
                                              spot_date).days

        return quoted_delivery_df, quoted_delivery_days_arr, forwards_points_arr, divisor

    def generate_quoted_delivery(self, cross, market_df, quoted_delivery_df,
                                 fx_forwards_tenor, cal):
        # Get the quoted delivery dates for every quoted tenor in our forwards market data
        # Eg. what's the delivery date for EURUSD SN, 1W etc.
        if quoted_delivery_df is None:
            quoted_delivery_df = pd.DataFrame(
                index=market_df.index,
                columns=[
                    cross + tenor + ".delivery" for tenor in fx_forwards_tenor
                ])

            for tenor in fx_forwards_tenor:
                quoted_delivery_df[cross + tenor + ".delivery"] = \
                    self._calendar.get_delivery_date_from_horizon_date(quoted_delivery_df.index, tenor, cal=cal)

        return quoted_delivery_df

    def calculate_implied_depo(
            self,
            cross,
            implied_currency,
            market_df=None,
            quoted_delivery_df=None,
            fx_forwards_tenor=market_constants.fx_forwards_trading_tenor,
            depo_tenor=None):
        """Calculates implied deposit rates for a particular currency from spot, forward points and deposit rate
        for the other currency. Uses the theory of covered interest rate parity.

        See BIS publication from 2016, Covered interest parity lost: understanding the cross-currency basis downloadable from
        https://www.bis.org/publ/qtrpdf/r_qt1609e.pdf for an explanation

        Eg. using EURUSD spot, EURUSD 1W forward points and USDON deposit rate, we can calculate the implied deposit
        for EUR 1W

        Parameters
        ----------
        cross : str
            Currency pair

        implied_currency : str
            Currency for which we want to imply deposit

        market_df : DataFrame
            With FX spot rate, FX forward points and deposit rates

        fx_forwards_tenor : str (list)
            Tenors of forwards where we want to imply deposit

        depo_tenor : str
            Deposit rate to use (default - ON)

        Returns
        -------
        DataFrame
        """

        if market_df is None: market_df = self._market_df
        if quoted_delivery_df is None:
            quoted_delivery_df = self._quoted_delivery_df
        if depo_tenor is None: depo_tenor = fx_forwards_tenor

        if not (isinstance(depo_tenor, list)):
            depo_tenor = [depo_tenor] * len(fx_forwards_tenor)

        cal = cross

        # Get the spot date (different currency pairs have different conventions for this!)
        spot_date = self._calendar.get_spot_date_from_horizon_date(
            market_df.index, cal)

        quoted_delivery_df, quoted_delivery_days_arr, forwards_points_arr, divisor = \
            self._setup_forward_calculation(cross, spot_date, market_df, quoted_delivery_df,
                                            fx_forwards_tenor)

        spot_arr = market_df[cross + '.close'].values

        outright_forwards_arr = np.vstack(
            [spot_arr] * len(fx_forwards_tenor)).T + forwards_points_arr

        base_conv = self.get_day_count_conv(cross[0:3])
        terms_conv = self.get_day_count_conv(cross[3:6])

        # Infer base currency
        if implied_currency == cross[0:3]:
            original_currency = cross[3:6]

            depo_arr = market_df[[
                original_currency + d + '.close' for d in depo_tenor
            ]].values / 100.0

            implied_depo_arr = _infer_base_currency_depo_numba(
                spot_arr, outright_forwards_arr, depo_arr,
                quoted_delivery_days_arr, base_conv, terms_conv,
                len(fx_forwards_tenor))

            # for i in range(len(implied_depo_arr)):
            #     for j, tenor in enumerate(fx_forwards_tenor):
            #         implied_depo_arr[i, j] = (((1 + depo_arr[i,j] * (quoted_delivery_days_arr[i, j] / terms_conv))
            #                                    / (outright_forwards_arr[i,j] / spot_arr[i])) - 1) \
            #                                  / (quoted_delivery_days_arr[i, j] / base_conv)

        # Infer terms currency
        if implied_currency == cross[3:6]:
            original_currency = cross[0:3]

            depo_arr = market_df[[
                original_currency + d + '.close' for d in depo_tenor
            ]].values / 100.0

            implied_depo_arr = _infer_terms_currency_depo_numba(
                spot_arr, outright_forwards_arr, depo_arr,
                quoted_delivery_days_arr, base_conv, terms_conv,
                len(fx_forwards_tenor))

            # for i in range(len(implied_depo_arr)):
            #     for j, tenor in enumerate(fx_forwards_tenor):
            #         implied_depo_arr[i,j] = ((outright_forwards_arr[i,j] / spot_arr[i]) *
            #                                  (1 + depo_arr[i,j] * (quoted_delivery_days_arr[i,j] / base_conv)) - 1) \
            #                                 / (quoted_delivery_days_arr[i,j] / terms_conv)

        return pd.DataFrame(index=market_df.index,
                            columns=[
                                implied_currency + x + "-implied-depo.close"
                                for x in fx_forwards_tenor
                            ],
                            data=implied_depo_arr * 100.0)
Exemplo n.º 2
0
            calendar.get_holidays(start_date='01 Jan 2000 00:10',
                                  end_date='31 Dec 2000',
                                  cal='EURUSD'))

        # Get the holidays (which are weekends)
        print(
            calendar.get_holidays(start_date='01 Jan 1999 00:50',
                                  end_date='31 Dec 1999',
                                  cal='WKY'))

    if run_example == 2 or run_example == 0:

        # Get delivery dates for these horizon dates - typically would use to get forward maturities
        print(
            calendar.get_delivery_date_from_horizon_date(pd.to_datetime(
                [pd.Timestamp('02 Nov 2020')]),
                                                         'ON',
                                                         cal='EURUSD'))

        print(
            calendar.get_delivery_date_from_horizon_date(pd.to_datetime(
                [pd.Timestamp('02 Nov 2020')]),
                                                         '1W',
                                                         cal='EURUSD'))

        print(
            calendar.get_delivery_date_from_horizon_date(pd.to_datetime(
                [pd.Timestamp('27 Nov 2020')]),
                                                         '1W',
                                                         cal='EURUSD'))

        # Get 1M expires for these horizon dates - typically would use to get option expiries
Exemplo n.º 3
0
class FXOptionsPricer(AbstractPricer):
    """Prices various vanilla FX options, using FinancePy underneath.
    """
    def __init__(self,
                 fx_vol_surface=None,
                 premium_output=market_constants.fx_options_premium_output,
                 delta_output=market_constants.fx_options_delta_output):

        self._calendar = Calendar()
        self._fx_vol_surface = fx_vol_surface
        self._fx_forwards_price = FXForwardsPricer()
        self._premium_output = premium_output
        self._delta_output = delta_output

    def price_instrument(self,
                         cross,
                         horizon_date,
                         strike,
                         expiry_date=None,
                         vol=None,
                         notional=1000000,
                         contract_type='european-call',
                         tenor=None,
                         fx_vol_surface=None,
                         premium_output=None,
                         delta_output=None,
                         depo_tenor=None,
                         return_as_df=True):
        """Prices FX options for horizon dates/expiry dates given by the user from FX spot rates, FX volatility surface
        and deposit rates.

        Parameters
        ----------
        cross : str
            Currency pair

        horizon_date : DateTimeIndex
            Horizon dates for options

        expiry_date : DateTimeIndex
            expiry dates for options

        market_df : DataFrame
            Contains FX spot, FX vol surface quotes, FX forwards and base depos

        Returns
        -------
        DataFrame
        """

        # if market_df is None: market_df = self._market_df
        if fx_vol_surface is None: fx_vol_surface = self._fx_vol_surface
        if premium_output is None: premium_output = self._premium_output
        if delta_output is None: delta_output = self._delta_output

        logger = LoggerManager().getLogger(__name__)

        field = fx_vol_surface._field

        # Make horizon date and expiry date pandas DatetimeIndex
        if isinstance(horizon_date, pd.Timestamp):
            horizon_date = pd.DatetimeIndex([horizon_date])
        else:
            horizon_date = pd.DatetimeIndex(horizon_date)

        if expiry_date is not None:
            if isinstance(expiry_date, pd.Timestamp):
                expiry_date = pd.DatetimeIndex([expiry_date])
            else:
                expiry_date = pd.DatetimeIndex(expiry_date)
        else:
            expiry_date = self._calendar.get_expiry_date_from_horizon_date(
                horizon_date, tenor, cal=cross)

        # If the strike hasn't been supplied need to work this out
        if not (isinstance(strike, np.ndarray)):
            old_strike = strike

            if isinstance(strike, str):
                strike = np.empty(len(horizon_date), dtype=object)
            else:
                strike = np.empty(len(horizon_date))

            strike.fill(old_strike)

        # If the vol hasn't been supplied need to work this out
        if not (isinstance(vol, np.ndarray)):

            if vol is None:
                vol = np.nan

            old_vol = vol

            vol = np.empty(len(horizon_date))
            vol.fill(old_vol)

        option_values = np.zeros(len(horizon_date))
        spot = np.zeros(len(horizon_date))
        delta = np.zeros(len(horizon_date))
        intrinsic_values = np.zeros(len(horizon_date))

        def _price_option(contract_type_, contract_type_fin_):
            for i in range(len(expiry_date)):
                built_vol_surface = False

                # If we have a "key strike" need to fit the vol surface
                if isinstance(strike[i], str):
                    if not (built_vol_surface):

                        fx_vol_surface.build_vol_surface(horizon_date[i])
                        fx_vol_surface.extract_vol_surface(
                            num_strike_intervals=None)

                        built_vol_surface = True

                    if strike[i] == 'atm':
                        strike[i] = fx_vol_surface.get_atm_strike(tenor)
                        vol[i] = fx_vol_surface.get_atm_vol(tenor) / 100.0
                    elif strike[i] == 'atms':
                        strike[i] = fx_vol_surface.get_spot()
                    elif strike[i] == 'atmf':

                        delivery_date = self._calendar.get_delivery_date_from_horizon_date(
                            horizon_date[i], cal=cross)

                        strike[i] = self._fx_forwards_price.price_instrument(
                            cross,
                            delivery_date,
                            market_df=fx_vol_surface.get_all_market_data())
                    elif strike[i] == '25d-otm':
                        if 'call' in contract_type_:
                            strike[i] = fx_vol_surface.get_25d_call_strike(
                                tenor)
                            vol[i] = fx_vol_surface.get_25d_call_strike(
                                tenor) / 100.0
                        elif 'put' in contract_type_:
                            strike[i] = fx_vol_surface.get_25d_put_strike(
                                tenor)
                            vol[i] = fx_vol_surface.get_25d_put_strike(
                                tenor) / 100.0
                    elif strike[i] == '10d-otm':
                        if 'call' in contract_type_:
                            strike[i] = fx_vol_surface.get_10d_call_strike(
                                tenor)
                            vol[i] = fx_vol_surface.get_10d_call_strike(
                                tenor) / 100.0
                        elif 'put' in contract_type_:
                            strike[i] = fx_vol_surface.get_10d_put_strike(
                                tenor)
                            vol[i] = fx_vol_surface.get_10d_put_strike(
                                tenor) / 100.0

                if not (built_vol_surface):
                    fx_vol_surface.build_vol_surface(horizon_date[i])
                    # fx_vol_surface.extract_vol_surface(num_strike_intervals=None)

                # If an implied vol hasn't been provided, interpolate that one, fit the vol surface (if hasn't already been
                # done)
                if np.isnan(vol[i]):

                    if tenor is None:
                        vol[i] = fx_vol_surface.calculate_vol_for_strike_expiry(
                            strike[i], expiry_date=expiry_date[i], tenor=None)
                    else:
                        vol[i] = fx_vol_surface.calculate_vol_for_strike_expiry(
                            strike[i], expiry_date=None, tenor=tenor)

                model = FinModelBlackScholes(float(vol[i]))

                logger.info("Pricing " + contract_type_ +
                            " option, horizon date = " + str(horizon_date[i]) +
                            ", expiry date = " + str(expiry_date[i]))

                option = FinFXVanillaOption(self._findate(expiry_date[i]),
                                            strike[i], cross,
                                            contract_type_fin_, notional,
                                            cross[0:3])

                spot[i] = fx_vol_surface.get_spot()
                """ FinancePy will return the value in the following dictionary for values
                    {'v': vdf,
                    "cash_dom": cash_dom,
                    "cash_for": cash_for,
                    "pips_dom": pips_dom,
                    "pips_for": pips_for,
                    "pct_dom": pct_dom,
                    "pct_for": pct_for,
                    "not_dom": notional_dom,
                    "not_for": notional_for,
                    "ccy_dom": self._domName,
                    "ccy_for": self._forName}
                """

                option_values[i] = option_values[i] + option.value(
                    self._findate(horizon_date[i]), spot[i],
                    fx_vol_surface.get_dom_discount_curve(),
                    fx_vol_surface.get_for_discount_curve(),
                    model)[premium_output.replace('-', '_')]

                intrinsic_values[i] = intrinsic_values[i] + option.value(
                    self._findate(expiry_date[i]), spot[i],
                    fx_vol_surface.get_dom_discount_curve(),
                    fx_vol_surface.get_for_discount_curve(),
                    model)[premium_output.replace('-', '_')]
                """FinancePy returns this dictionary for deltas
                    {"pips_spot_delta": pips_spot_delta,
                    "pips_fwd_delta": pips_fwd_delta,
                    "pct_spot_delta_prem_adj": pct_spot_delta_prem_adj,
                    "pct_fwd_delta_prem_adj": pct_fwd_delta_prem_adj}
                """

                delta[i] = delta[i] + option.delta(
                    self._findate(horizon_date[i]), spot[i],
                    fx_vol_surface.get_dom_discount_curve(),
                    fx_vol_surface.get_for_discount_curve(),
                    model)[delta_output.replace('-', '_')]

        if contract_type == 'european-call':
            contract_type_fin = FinOptionTypes.EUROPEAN_CALL

            _price_option(contract_type, contract_type_fin)
        elif contract_type == 'european-put':
            contract_type_fin = FinOptionTypes.EUROPEAN_PUT

            _price_option(contract_type, contract_type_fin)
        elif contract_type == 'european-straddle' or contract_type == 'european-strangle':
            contract_type = 'european-call'
            contract_type_fin = FinOptionTypes.EUROPEAN_CALL

            _price_option(contract_type, contract_type_fin)

            contract_type = 'european-put'
            contract_type_fin = FinOptionTypes.EUROPEAN_PUT

            _price_option(contract_type, contract_type_fin)

        if return_as_df:
            option_prices_df = pd.DataFrame(index=horizon_date)

            option_prices_df[cross + '-option-price.' + field] = option_values
            option_prices_df[cross + '.' + field] = spot
            option_prices_df[cross + '-strike.' + field] = strike
            option_prices_df[cross + '-vol.' + field] = vol
            option_prices_df[cross + '-delta.' + field] = delta
            option_prices_df[cross + '.expiry-date'] = expiry_date
            option_prices_df[cross + '-intrinsic-value.' +
                             field] = intrinsic_values

            return option_prices_df

        return option_values, spot, strike, vol, delta, expiry_date, intrinsic_values

    def get_day_count_conv(self, currency):
        if currency in market_constants.currencies_with_365_basis:
            return 365.0

        return 360.0

    def _findate(self, timestamp):

        return FinDate(timestamp.day,
                       timestamp.month,
                       timestamp.year,
                       hh=timestamp.hour,
                       mm=timestamp.minute,
                       ss=timestamp.second)
Exemplo n.º 4
0
class FXForwardsCurve(object):
    """Constructs continuous forwards time series total return indices from underlying forwards contracts.


    """

    def __init__(self, market_data_generator=None, fx_forwards_trading_tenor=market_constants.fx_forwards_trading_tenor,
                 roll_days_before=market_constants.fx_forwards_roll_days_before,
                 roll_event=market_constants.fx_forwards_roll_event, construct_via_currency='no',
                 fx_forwards_tenor_for_interpolation=market_constants.fx_forwards_tenor_for_interpolation,
                 base_depos_tenor=data_constants.base_depos_tenor,
                 roll_months=market_constants.fx_forwards_roll_months,
                 cum_index=market_constants.fx_forwards_cum_index,
                 output_calculation_fields=market_constants.output_calculation_fields):
        """Initializes FXForwardsCurve

        Parameters
        ----------
        market_data_generator : MarketDataGenerator
            Used for downloading market data

        fx_forwards_trading_tenor : str
            What is primary forward contract being used to trade (default - '1M')

        roll_days_before : int
            Number of days before roll event to enter into a new forwards contract

        roll_event : str
            What constitutes a roll event? ('month-end', 'quarter-end', 'year-end', 'expiry')

        construct_via_currency : str
            What currency should we construct the forward via? Eg. if we asked for AUDJPY we can construct it via
            AUDUSD & JPYUSD forwards, as opposed to AUDJPY forwards (default - 'no')

        fx_forwards_tenor_for_interpolation : str(list)
            Which forwards should we use for interpolation

        base_depos_tenor : str(list)
            Which base deposits tenors do we need (this is only necessary if we want to start inferring depos)

        roll_months : int
            After how many months should we initiate a roll. Typically for trading 1M this should 1, 3M this should be 3
            etc.

        cum_index : str
            In total return index, do we compute in additive or multiplicative way ('add' or 'mult')

        output_calculation_fields : bool
            Also output additional data should forward expiries etc. alongside total returns indices
        """

        self._market_data_generator = market_data_generator
        self._calculations = Calculations()
        self._calendar = Calendar()
        self._filter = Filter()

        self._fx_forwards_trading_tenor = fx_forwards_trading_tenor
        self._roll_days_before = roll_days_before
        self._roll_event = roll_event

        self._construct_via_currency = construct_via_currency
        self._fx_forwards_tenor_for_interpolation = fx_forwards_tenor_for_interpolation
        self._base_depos_tenor = base_depos_tenor

        self._roll_months = roll_months
        self._cum_index = cum_index
        self._output_calcultion_fields = output_calculation_fields

    def generate_key(self):
        from findatapy.market.ioengine import SpeedCache

        # Don't include any "large" objects in the key
        return SpeedCache().generate_key(self, ['_market_data_generator', '_calculations', '_calendar', '_filter'])

    def fetch_continuous_time_series(self, md_request, market_data_generator, fx_forwards_trading_tenor=None,
                                     roll_days_before=None, roll_event=None,
                                     construct_via_currency=None, fx_forwards_tenor_for_interpolation=None, base_depos_tenor=None,
                                     roll_months=None, cum_index=None, output_calculation_fields=False):

        if market_data_generator is None: market_data_generator = self._market_data_generator
        if fx_forwards_trading_tenor is None: fx_forwards_trading_tenor = self._fx_forwards_trading_tenor
        if roll_days_before is None: roll_days_before = self._roll_days_before
        if roll_event is None: roll_event = self._roll_event
        if construct_via_currency is None: construct_via_currency = self._construct_via_currency
        if fx_forwards_tenor_for_interpolation is None: fx_forwards_tenor_for_interpolation = self._fx_forwards_tenor_for_interpolation
        if base_depos_tenor is None: base_depos_tenor = self._base_depos_tenor
        if roll_months is None: roll_months = self._roll_months
        if cum_index is None: cum_index = self._cum_index
        if output_calculation_fields is None: output_calculation_fields

        # Eg. we construct EURJPY via EURJPY directly (note: would need to have sufficient forward data for this)
        if construct_via_currency == 'no':
            # Download FX spot, FX forwards points and base depos etc.
            market = Market(market_data_generator=market_data_generator)

            md_request_download = MarketDataRequest(md_request=md_request)

            fx_conv = FXConv()

            # CAREFUL: convert the tickers to correct notation, eg. USDEUR => EURUSD, because our data
            # should be fetched in correct convention
            md_request_download.tickers = [fx_conv.correct_notation(x) for x in md_request.tickers]
            md_request_download.category = 'fx-forwards-market'
            md_request_download.fields = 'close'
            md_request_download.abstract_curve = None
            md_request_download.fx_forwards_tenor = fx_forwards_tenor_for_interpolation
            md_request_download.base_depos_tenor = base_depos_tenor

            forwards_market_df = market.fetch_market(md_request_download)

            # Now use the original tickers
            return self.construct_total_return_index(md_request.tickers, forwards_market_df,
                                                     fx_forwards_trading_tenor=fx_forwards_trading_tenor,
                                                     roll_days_before=roll_days_before, roll_event=roll_event,
                                                     fx_forwards_tenor_for_interpolation=fx_forwards_tenor_for_interpolation,
                                                     roll_months=roll_months,
                                                     cum_index=cum_index,
                                                     output_calculation_fields=output_calculation_fields)
        else:
            # eg. we calculate via your domestic currency such as USD, so returns will be in your domestic currency
            # Hence AUDJPY would be calculated via AUDUSD and JPYUSD (subtracting the difference in returns)
            total_return_indices = []

            for tick in md_request.tickers:
                base = tick[0:3]
                terms = tick[3:6]

                md_request_base = MarketDataRequest(md_request=md_request)
                md_request_base.tickers = base + construct_via_currency

                md_request_terms = MarketDataRequest(md_request=md_request)
                md_request_terms.tickers = terms + construct_via_currency

                # Construct the base and terms separately (ie. AUDJPY => AUDUSD & JPYUSD)
                base_vals = self.fetch_continuous_time_series(md_request_base, market_data_generator,
                                     fx_forwards_trading_tenor=fx_forwards_trading_tenor,
                                     roll_days_before=roll_days_before, roll_event=roll_event,
                                     fx_forwards_tenor_for_interpolation=fx_forwards_tenor_for_interpolation,
                                     base_depos_tenor=base_depos_tenor,
                                     roll_months=roll_months, output_calculation_fields=False,
                                     cum_index=cum_index,
                                     construct_via_currency='no')

                terms_vals = self.fetch_continuous_time_series(md_request_terms, market_data_generator,
                                     fx_forwards_trading_tenor=fx_forwards_trading_tenor,
                                     roll_days_before=roll_days_before, roll_event=roll_event,
                                     fx_forwards_tenor_for_interpolation=fx_forwards_tenor_for_interpolation,
                                     base_depos_tenor=base_depos_tenor,
                                     roll_months=roll_months,
                                     cum_index=cum_index,
                                     output_calculation_fields=False,
                                     construct_via_currency='no')

                # Special case for USDUSD case (and if base or terms USD are USDUSD
                if base + terms == construct_via_currency + construct_via_currency:
                    base_rets = self._calculations.calculate_returns(base_vals)
                    cross_rets = pd.DataFrame(0, index=base_rets.index, columns=base_rets.columns)
                elif base + construct_via_currency == construct_via_currency + construct_via_currency:
                    cross_rets = -self._calculations.calculate_returns(terms_vals)
                elif terms + construct_via_currency == construct_via_currency + construct_via_currency:
                    cross_rets = self._calculations.calculate_returns(base_vals)
                else:
                    base_rets = self._calculations.calculate_returns(base_vals)
                    terms_rets = self._calculations.calculate_returns(terms_vals)

                    cross_rets = base_rets.sub(terms_rets.iloc[:, 0], axis=0)

                # First returns of a time series will by NaN, given we don't know previous point
                cross_rets.iloc[0] = 0

                cross_vals = self._calculations.create_mult_index(cross_rets)
                cross_vals.columns = [tick + '-forward-tot.close']

                total_return_indices.append(cross_vals)

            return self._calculations.pandas_outer_join(total_return_indices)

    def unhedged_asset_fx(self, assets_df, asset_currency, home_curr, start_date, finish_date, spot_df=None):
        pass

    def hedged_asset_fx(self, assets_df, asset_currency, home_curr, start_date, finish_date, spot_df=None,
                        total_return_indices_df=None):
        pass

    def get_day_count_conv(self, currency):
        if currency in market_constants.currencies_with_365_basis:
            return 365.0

        return 360.0

    def construct_total_return_index(self, cross_fx, forwards_market_df,
                                     fx_forwards_trading_tenor=None,
                                     roll_days_before=None,
                                     roll_event=None,
                                     roll_months=None,
                                     fx_forwards_tenor_for_interpolation=None,
                                     cum_index=None,
                                     output_calculation_fields=False):

        if not (isinstance(cross_fx, list)):
            cross_fx = [cross_fx]

        if fx_forwards_trading_tenor is None: fx_forwards_trading_tenor = self._fx_forwards_trading_tenor
        if roll_days_before is None: roll_days_before = self._roll_days_before
        if roll_event is None: roll_event = self._roll_event
        if roll_months is None: roll_months = self._roll_months
        if fx_forwards_tenor_for_interpolation is None: fx_forwards_tenor_for_interpolation = self._fx_forwards_tenor_for_interpolation
        if cum_index is None: cum_index = self._cum_index

        total_return_index_df_agg = []

        # Remove columns where there is no data (because these points typically aren't quoted)
        forwards_market_df = forwards_market_df.dropna(how='all', axis=1)

        fx_forwards_pricer = FXForwardsPricer()

        def get_roll_date(horizon_d, delivery_d, asset_hols, month_adj=1):
            if roll_event == 'month-end':
                roll_d = horizon_d + CustomBusinessMonthEnd(roll_months + month_adj, holidays=asset_hols)
            elif roll_event == 'delivery-date':
                roll_d = delivery_d

            return (roll_d - CustomBusinessDay(n=roll_days_before, holidays=asset_hols))

        for cross in cross_fx:

            # Eg. if we specify USDUSD
            if cross[0:3] == cross[3:6]:
                total_return_index_df_agg.append(
                    pd.DataFrame(100, index=forwards_market_df.index, columns=[cross + "-forward-tot.close"]))
            else:
                # Is the FX cross in the correct convention
                old_cross = cross
                cross = FXConv().correct_notation(cross)

                horizon_date = forwards_market_df.index

                delivery_date = []
                roll_date = []

                new_trade = np.full(len(horizon_date), False, dtype=bool)

                asset_holidays = self._calendar.get_holidays(cal=cross)

                # Get first delivery date
                delivery_date.append(
                    self._calendar.get_delivery_date_from_horizon_date(horizon_date[0],
                                                                       fx_forwards_trading_tenor, cal=cross, asset_class='fx')[0])

                # For first month want it to expire within that month (for consistency), hence month_adj=0 ONLY here
                roll_date.append(get_roll_date(horizon_date[0], delivery_date[0], asset_holidays, month_adj=0))

                # New trade => entry at beginning AND on every roll
                new_trade[0] = True

                # Get all the delivery dates and roll dates
                # At each "roll/trade" day we need to reset them for the new contract
                for i in range(1, len(horizon_date)):

                    # If the horizon date has reached the roll date (from yesterday), we're done, and we have a
                    # new roll/trade
                    if (horizon_date[i] - roll_date[i-1]).days == 0:
                        new_trade[i] = True
                    # else:
                    #    new_trade[i] = False

                    # If we're entering a new trade/contract, we need to get new delivery and roll dates
                    if new_trade[i]:
                        delivery_date.append(self._calendar.get_delivery_date_from_horizon_date(horizon_date[i],
                            fx_forwards_trading_tenor, cal=cross, asset_class='fx')[0])

                        roll_date.append(get_roll_date(horizon_date[i], delivery_date[i], asset_holidays))
                    else:
                        # Otherwise use previous delivery and roll dates, because we're still holding same contract
                        delivery_date.append(delivery_date[i-1])
                        roll_date.append(roll_date[i-1])

                interpolated_forward = fx_forwards_pricer.price_instrument(cross, horizon_date, delivery_date, market_df=forwards_market_df,
                         fx_forwards_tenor_for_interpolation=fx_forwards_tenor_for_interpolation)[cross + '-interpolated-outright-forward.close'].values

                # To record MTM prices
                mtm = np.copy(interpolated_forward)

                # Note: may need to add discount factor when marking to market forwards?

                # Special case: for very first trading day
                # mtm[0] = interpolated_forward[0]

                # On rolling dates, MTM will be the previous forward contract (interpolated)
                # otherwise it will be the current forward contract
                for i in range(1, len(horizon_date)):
                    if new_trade[i]:
                        mtm[i] = fx_forwards_pricer.price_instrument(cross, horizon_date[i], delivery_date[i-1],
                            market_df=forwards_market_df,
                            fx_forwards_tenor_for_interpolation=fx_forwards_tenor_for_interpolation) \
                                [cross + '-interpolated-outright-forward.close'].values
                    # else:
                    #    mtm[i] = interpolated_forward[i]

                # Eg. if we asked for USDEUR, we first constructed spot/forwards for EURUSD
                # and then need to invert it
                if old_cross != cross:
                    mtm = 1.0 / mtm
                    interpolated_forward = 1.0 / interpolated_forward

                forward_rets = mtm / np.roll(interpolated_forward, 1) - 1.0
                forward_rets[0] = 0

                if cum_index == 'mult':
                    cum_rets = 100 * np.cumprod(1.0 + forward_rets)
                elif cum_index == 'add':
                    cum_rets = 100 + 100 * np.cumsum(forward_rets)

                total_return_index_df = pd.DataFrame(index=horizon_date, columns=[cross + "-forward-tot.close"])
                total_return_index_df[cross + "-forward-tot.close"] = cum_rets

                if output_calculation_fields:
                    total_return_index_df[cross + '-interpolated-outright-forward.close'] = interpolated_forward
                    total_return_index_df[cross + '-mtm.close'] = mtm
                    total_return_index_df[cross + '-roll.close'] = new_trade
                    total_return_index_df[cross + '.roll-date'] = roll_date
                    total_return_index_df[cross + '.delivery-date'] = delivery_date
                    total_return_index_df[cross + '-forward-return.close'] = forward_rets

                total_return_index_df_agg.append(total_return_index_df)

        return self._calculations.pandas_outer_join(total_return_index_df_agg)
Exemplo n.º 5
0
        # Get the holidays (which aren"t weekends)
        print(calendar.get_holidays(start_date="01 Jan 1999 00:50",
                                    end_date="31 Dec 1999", cal="FX"))
        print(calendar.get_holidays(start_date="01 Jan 2000 00:10",
                                    end_date="31 Dec 2000", cal="EUR"))
        print(calendar.get_holidays(start_date="01 Jan 2000 00:10",
                                    end_date="31 Dec 2000", cal="EURUSD"))

        # Get the holidays (which are weekends)
        print(calendar.get_holidays(start_date="01 Jan 1999 00:50",
                                    end_date="31 Dec 1999", cal="WKD"))

    if run_example == 2 or run_example == 0:
        # Get delivery dates for these horizon dates - typically would use 
        # to get forward maturities
        print(calendar.get_delivery_date_from_horizon_date(
            pd.to_datetime([pd.Timestamp("02 Nov 2020")]), "ON", cal="EURUSD"))

        print(calendar.get_delivery_date_from_horizon_date(
            pd.to_datetime([pd.Timestamp("02 Nov 2020")]), "1W", cal="EURUSD"))

        print(calendar.get_delivery_date_from_horizon_date(
            pd.to_datetime([pd.Timestamp("27 Nov 2020")]), "1W", cal="EURUSD"))

        # Get 1M expires for these horizon dates - typically would use to get 
        # option expiries
        print(calendar.get_expiry_date_from_horizon_date(
            pd.to_datetime([pd.Timestamp("26 Oct 2020")]), "1M", cal="EURUSD"))

    if run_example == 3 or run_example == 0:
        # Create a list of business days and one which is + 1 day