Skip to content

Commit

Permalink
Chore: Make release 1.0.56
Browse files Browse the repository at this point in the history
  • Loading branch information
martinroberson authored and Vanden Bon, David V [GBM Public] committed Jan 25, 2024
1 parent 2177d12 commit 47aa0bd
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 3 deletions.
77 changes: 76 additions & 1 deletion gs_quant/api/gs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@
import cachetools
import pandas as pd
from cachetools import TTLCache

import json
from gs_quant.api.data import DataApi
from gs_quant.base import Base
from gs_quant.data.core import DataContext, DataFrequency
from gs_quant.data.log import log_debug, log_warning
from gs_quant.errors import MqValueError
from gs_quant.json_encoder import JSONEncoder
from gs_quant.markets import MarketDataCoordinate
from gs_quant.target.common import MarketDataVendor, PricingLocation, Format
from gs_quant.target.coordinates import MDAPIDataBatchResponse, MDAPIDataQuery, MDAPIDataQueryResponse, MDAPIQueryField
from gs_quant.target.data import DataQuery, DataQueryResponse, DataSetCatalogEntry
from gs_quant.target.data import DataSetEntity, DataSetFieldEntity
from dateutil import parser
from .assets import GsIdType
from ..api_cache import ApiRequestCache
from ...target.assets import EntityQuery, FieldFilterMap
Expand Down Expand Up @@ -472,6 +474,79 @@ def get_many_coordinates(
else:
raise NotImplementedError('Unsupported return type')

@classmethod
def _to_zulu(cls, d):
return d.strftime('%Y-%m-%dT%H:%M:%SZ')

@classmethod
def _resolve_default_csa_for_builder(cls, builder):
dict_builder = builder.to_dict()
properties = dict_builder.get('properties')

if not properties:
return 'USD-1'

clearing_house = properties.get('clearinghouse')

if 'payccy' in properties:
pay_ccy = properties['payccy']
if pay_ccy == 'USD':
default_csa = 'USD-SOFR'
elif pay_ccy == 'EUR':
default_csa = 'EUR-EUROSTR'
else:
default_csa = pay_ccy + "-1"

if clearing_house and clearing_house != 'LCH':
default_csa = f'CB LCH/{clearing_house.upper()} {default_csa}'

return default_csa
else:
return "USD-1"

@classmethod
def get_mxapi_backtest_data(cls, builder, start_time=None, end_time=None, num_samples=60,
csa=None, request_id=None) -> pd.DataFrame:
if not start_time:
start_time = DataContext.current.start_time
if not end_time:
end_time = DataContext.current.end_time
if not csa:
csa = cls._resolve_default_csa_for_builder(builder)

leg = builder.resolve(in_place=False)
leg_dict_string = json.dumps(leg, cls=JSONEncoder)
leg_dict = json.loads(leg_dict_string)

request_dict = {
'type': 'MxAPI Backtest Request MQ',
'builder': leg_dict,
'startTime': cls._to_zulu(start_time),
'endTime': cls._to_zulu(end_time),
'sampleSize': num_samples,
'csa': csa
}

start = time.perf_counter()
try:
body = cls._post_with_cache_check('/mxapi/mq/backtest', payload=request_dict)
except Exception as e:
log_warning(request_id, _logger, f'Mxapi backtest query {request_dict} failed due to {e}')
raise e
log_debug(request_id, _logger, 'MxAPI backtest query (%s) with payload (%s) ran in %.3f ms',
body.get('requestId'), request_dict, (time.perf_counter() - start) * 1000)

values = body['valuations']
valuation_times = body['valuationTimes']
timestamps = [parser.parse(s) for s in valuation_times]
column_name = body['valuationName']

d = {column_name: values, 'timeStamp': timestamps}
df = MarketDataResponseFrame(pd.DataFrame(data=d))
df = df.set_index('timeStamp')

return df

@staticmethod
def build_market_data_query(asset_ids: List[str],
query_type: Union[QueryType, str],
Expand Down
61 changes: 61 additions & 0 deletions gs_quant/test/timeseries/test_measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,67 @@ def test_currency_to_tdapi_basis_swap_rate_asset(mocker):
replace.restore()


def test_currency_to_tdapi_swap_rate_asset_for_intraday(mocker):
replace = Replacer()
mocker.patch.object(GsSession.__class__, 'current',
return_value=GsSession.get(Environment.QA, 'client_id', 'secret'))
mocker.patch.object(GsSession.current, '_get', side_effect=mock_request)
mocker.patch.object(SecurityMaster, 'get_asset', side_effect=mock_request)

bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock())

with tm.PricingContext(dt.date.today()):
asset = Currency('MAZ7RWC904JYHYPS', 'USD')
bbid_mock.return_value = 'USD'
correct_id = tm_rates._currency_to_tdapi_swap_rate_asset_for_intraday(asset)
assert 'MACF6R4J5FY4KGBZ' == correct_id
replace.restore()


def my_mocked_mxapi_backtest(cls=None, builder=None, start_time=None, end_time=None, num_samples=60,
csa=None, request_id=None):
d = {'column_name': [], 'timeStamp': []}
df = MarketDataResponseFrame(pd.DataFrame(data=d))
df = df.set_index('timeStamp')
return df


def test_swap_rate_calc(mocker):
replace = Replacer()

mocker.patch.object(GsDataApi, 'get_mxapi_backtest_data', )
bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock())

mocker.patch.object(GsDataApi, 'get_mxapi_backtest_data', side_effect=my_mocked_mxapi_backtest)

asset = Currency('MAZ7RWC904JYHYPS', 'USD')
bbid_mock.return_value = 'USD'
val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='SOFR', real_time=True, forward_tenor='0b')
assert len(val.keys()) == 0

try:
val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='SOFR', real_time=False)
assert False
except NotImplementedError:
assert True

try:
val = tm_rates.swap_rate_calc(asset, swap_tenor='10x', benchmark_type='SOFR', real_time=True)
assert False
except MqValueError:
assert True

asset = Currency('MA890', 'EGP')
bbid_mock.return_value = 'EGP'
try:
val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='SOFR', real_time=True)
assert False
except NotImplementedError:
assert True

replace.restore()


def test_check_clearing_house():
assert tm_rates._ClearingHouse.LCH == tm_rates._check_clearing_house('lch')
assert tm_rates._ClearingHouse.CME == tm_rates._check_clearing_house(tm_rates._ClearingHouse.CME)
Expand Down
92 changes: 91 additions & 1 deletion gs_quant/timeseries/measures_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from typing import Optional, Union, Dict, List

import pandas as pd
from gs_quant.instrument import IRSwap

from pandas import Series

from gs_quant.api.gs.assets import GsAssetApi
Expand All @@ -30,7 +32,7 @@
from gs_quant.datetime.gscalendar import GsCalendar
from gs_quant.errors import MqValueError
from gs_quant.markets.securities import AssetIdentifier, Asset
from gs_quant.target.common import Currency as CurrencyEnum, AssetClass, AssetType, PricingLocation
from gs_quant.target.common import Currency as CurrencyEnum, AssetClass, AssetType, PricingLocation, SwapClearingHouse
from gs_quant.timeseries import currency_to_default_ois_asset, convert_asset_for_rates_data_set, RatesConversionType
from gs_quant.timeseries.helper import _to_offset, check_forward_looking, plot_measure
from gs_quant.timeseries.measures import _market_data_timed, _range_from_pricing_date, \
Expand Down Expand Up @@ -277,6 +279,20 @@ def get_swaption_parameter(self, currency, field, value=None):
'MXN': 'MAAJ9RAHYBAXGYD2'
}

SUPPORTED_INTRADAY_CURRENCY_TO_DUMMY_SWAP_BBID = {
'CHF': 'MACF6R4J5FY4KGBZ',
'EUR': 'MACF6R4J5FY4KGBZ',
'GBP': 'MACF6R4J5FY4KGBZ',
'JPY': 'MACF6R4J5FY4KGBZ',
'SEK': 'MACF6R4J5FY4KGBZ',
'USD': 'MACF6R4J5FY4KGBZ',
'DKK': 'MACF6R4J5FY4KGBZ',
'NOK': 'MACF6R4J5FY4KGBZ',
'NZD': 'MACF6R4J5FY4KGBZ',
'AUD': 'MACF6R4J5FY4KGBZ',
'CAD': 'MACF6R4J5FY4KGBZ'
}

# FXFwd XCCYSwap rates Defaults
CROSS_BBID_TO_DUMMY_OISXCCY_ASSET = {
'EURUSD': 'MA1VJC1E3SZW8E4S',
Expand Down Expand Up @@ -326,6 +342,14 @@ def _currency_to_tdapi_swap_rate_asset(asset_spec: ASSET_SPEC) -> str:
return result


def _currency_to_tdapi_swap_rate_asset_for_intraday(asset_spec: ASSET_SPEC) -> str:
asset = _asset_from_spec(asset_spec)
bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)
# for each currency, get a dummy asset for checking availability
result = SUPPORTED_INTRADAY_CURRENCY_TO_DUMMY_SWAP_BBID.get(bbid, asset.get_marquee_id())
return result


def _currency_to_tdapi_asset_base(asset_spec: ASSET_SPEC, allowed_bbids=None) -> str:
asset = _asset_from_spec(asset_spec)
bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)
Expand Down Expand Up @@ -634,6 +658,38 @@ def _get_swap_data(asset: Asset, swap_tenor: str, benchmark_type: str = None, fl
return df


def _get_swap_data_calc(asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None,
forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None,
real_time: bool = False, location: PricingLocation = None) -> pd.DataFrame:
currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID))

if currency.value not in SUPPORTED_INTRADAY_CURRENCY_TO_DUMMY_SWAP_BBID.keys():
raise NotImplementedError(f'Data not available for {currency.value} calculated swap rates')
benchmark_type = _check_benchmark_type(currency, benchmark_type)

clearing_house = _check_clearing_house(clearing_house)

defaults = _get_swap_leg_defaults(currency, benchmark_type, floating_rate_tenor)

if not re.fullmatch('(\\d+)([bdwmy])', swap_tenor):
raise MqValueError('invalid swap tenor ' + swap_tenor)

forward_tenor = _check_forward_tenor(forward_tenor)

builder = IRSwap(notional_currency=currency, clearing_house=SwapClearingHouse(clearing_house.value),
floating_rate_designated_maturity=defaults['floating_rate_tenor'],
floating_rate_option=defaults['benchmark_type'],
fixed_rate=0.0, termination_date=swap_tenor)

if forward_tenor:
builder.startdate = forward_tenor

_logger.debug(f'where builder={builder.as_dict()}')

q = GsDataApi.get_mxapi_backtest_data(builder)
return q


def _get_term_struct_date(tenor: Union[str, datetime.datetime], index: datetime.datetime,
business_day) -> datetime.datetime:
if isinstance(tenor, (datetime.datetime, datetime.date)):
Expand Down Expand Up @@ -1215,6 +1271,40 @@ def swap_rate(asset: Asset, swap_tenor: str, benchmark_type: str = None, floatin
return series


@plot_measure((AssetClass.Cash,), (AssetType.Currency,),
[MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday,
query_type=QueryType.SWAP_RATE)])
def swap_rate_calc(asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None,
forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = _ClearingHouse.LCH,
location: PricingLocation = None, *,
source: str = None, real_time: bool = False) -> Series:
"""
GS intra-day Fixed-Floating interest rate swap (IRS) curves across major currencies.
:param asset: asset object loaded from security master
:param swap_tenor: relative date representation of expiration date e.g. 1m
:param benchmark_type: benchmark type e.g. LIBOR
:param floating_rate_tenor: floating index rate
:param forward_tenor: absolute / relative date representation of forward starting point eg: '1y' or 'Spot' for
spot starting swaps, 'imm1' or 'frb1'
:param clearing_house: Example - "LCH", "CME"
:param location: Example - "TKO", "LDN", "NYC"
:param source: name of function caller
:param real_time: whether to retrieve intraday data instead of EOD
:return: swap rate curve
"""
if not real_time:
raise NotImplementedError("Calculated swap rates cannot be used outside realtime")

df = _get_swap_data_calc(asset=asset, swap_tenor=swap_tenor, benchmark_type=benchmark_type,
floating_rate_tenor=floating_rate_tenor, forward_tenor=forward_tenor,
clearing_house=clearing_house, real_time=real_time, location=location)

series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['ATMRate'])
series.dataset_ids = ()
return series


def _get_basis_swap_kwargs(asset: Asset, spread_benchmark_type: str = None, spread_tenor: str = None,
reference_benchmark_type: str = None, reference_tenor: str = None,
forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"nest-asyncio",
"opentracing",
"pandas>1.0.0,<2.0.0;python_version<'3.7'",
"pandas>=1.4;python_version>'3.7'",
"pandas>=1.4,<2.2;python_version>'3.7'",
"pydash<7.0.0",
"python-dateutil>=2.7.0",
"requests",
Expand Down

0 comments on commit 47aa0bd

Please sign in to comment.