Skip to content

Commit 1272790

Browse files
PricingFuture fixes (#232)
1 parent ebb8ee5 commit 1272790

File tree

18 files changed

+187
-79
lines changed

18 files changed

+187
-79
lines changed

gs_quant/api/gs/risk_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def upload_risk_model_data(cls,
170170
url += '?partialUpload=true'
171171
if target_universe_size:
172172
url += f'&targetUniverseSize={target_universe_size}'
173-
return GsSession.current._post(url, model_data)
173+
return GsSession.current._post(url, model_data, timeout=85)
174174

175175
@classmethod
176176
def get_risk_model_data(cls, model_id: str, start_date: dt.date, end_date: dt.date = None,

gs_quant/backtests/generic_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def run_backtest(self, strategy, start=None, end=None, frequency='1m', states=No
345345
# test to see if new trades have been added and calc
346346
port = []
347347
for t in backtest.portfolio_dict[d]:
348-
if t.name not in list(backtest.results[d].to_frame().index):
348+
if t.name not in backtest.results[d].portfolio:
349349
port.append(t)
350350

351351
with PricingContext(is_batch=True, csa_term=csa_term, show_progress=show_progress,

gs_quant/base.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,6 @@ def location(self):
518518
def to_dict(self):
519519
return self.market.to_dict()
520520

521-
def to_dict(self):
522-
return self.market.to_dict()
523-
524-
def to_dict(self):
525-
return self.market.to_dict()
526-
527521

528522
class Sentinel:
529523

gs_quant/datetime/date.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import numpy as np
1919
import calendar as cal
2020
from enum import Enum, IntEnum
21+
from pytz import timezone
2122
from typing import Iterable, Optional, Tuple, Union
2223
from gs_quant.datetime.gscalendar import GsCalendar
24+
from gs_quant.target.common import PricingLocation
2325

2426
DateOrDates = Union[dt.date, Iterable[dt.date]]
2527

@@ -203,6 +205,24 @@ def f():
203205
raise ValueError('begin must be a date or int')
204206

205207

208+
def today(location: Optional[PricingLocation] = None) -> dt.date:
209+
if not location:
210+
return dt.date.today()
211+
212+
if location == PricingLocation.LDN:
213+
tz = 'Europe/London'
214+
elif location == PricingLocation.NYC:
215+
tz = 'America/New_York'
216+
elif location == PricingLocation.HKG:
217+
tz = 'Asia/Hong_Kong'
218+
elif location == PricingLocation.TKO:
219+
tz = 'Asia/Tokyo'
220+
else:
221+
raise ValueError(f'Unrecognized timezone {location}')
222+
223+
return dt.datetime.now(timezone(tz)).date()
224+
225+
206226
def has_feb_29(start: dt.date, end: dt.date):
207227
"""
208228
Determine if date range has a leap day (29Feb)

gs_quant/markets/core.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from gs_quant.base import InstrumentBase, RiskKey, Scenario, get_enum_value
3131
from gs_quant.common import PricingLocation, RiskMeasure
3232
from gs_quant.context_base import ContextBaseWithDefault
33-
from gs_quant.datetime.date import business_day_offset
33+
from gs_quant.datetime.date import business_day_offset, today
3434
from gs_quant.risk import CompositeScenario, DataFrameWithInfo, ErrorValue, FloatWithInfo, MarketDataScenario, \
3535
StringWithInfo
3636
from gs_quant.risk.results import PricingFuture
@@ -171,16 +171,19 @@ def __init__(self,
171171
else:
172172
market_data_location = market.location
173173

174-
self.__pricing_date = pricing_date or (self.prior_context.pricing_date if self.prior_context else
175-
business_day_offset(dt.date.today(), 0, roll='preceding'))
174+
market_data_location = get_enum_value(PricingLocation, market_data_location)
175+
176+
self.__pricing_date = pricing_date or (
177+
self.prior_context.pricing_date if self.prior_context else business_day_offset(
178+
today(market_data_location), 0, roll='preceding'))
176179
self.__csa_term = csa_term
177180
self.__market_behaviour = market_behaviour
178181
self.__is_async = is_async
179182
self.__is_batch = is_batch
180183
self.__timeout = timeout
181184
self.__use_cache = use_cache
182185
self.__visible_to_gs = visible_to_gs
183-
self.__market_data_location = get_enum_value(PricingLocation, market_data_location)
186+
self.__market_data_location = market_data_location
184187
self.__market = market or CloseMarket(
185188
date=close_market_date(self.__market_data_location, self.__pricing_date) if pricing_date else None,
186189
location=self.__market_data_location if market_data_location else None)

gs_quant/markets/markets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def market(self):
173173

174174
@property
175175
def location(self) -> PricingLocation:
176-
return market_location(self.market.location)
176+
return market_location(self.__location)
177177

178178

179179
class LiveMarket(Market):

gs_quant/markets/portfolio.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
specific language governing permissions and limitations
1414
under the License.
1515
"""
16-
from dataclasses import dataclass
1716
import datetime as dt
1817
import logging
18+
from dataclasses import dataclass
1919
from itertools import chain
2020
from typing import Iterable, Optional, Tuple, Union
2121
from urllib.parse import quote
@@ -27,7 +27,7 @@
2727
from gs_quant.api.gs.portfolios import GsPortfolioApi
2828
from gs_quant.base import InstrumentBase
2929
from gs_quant.common import RiskMeasure
30-
from gs_quant.instrument import Instrument
30+
from gs_quant.instrument import Instrument, AssetType
3131
from gs_quant.markets import HistoricalPricingContext, OverlayMarket, PricingContext, PositionContext
3232
from gs_quant.priceable import PriceableImpl
3333
from gs_quant.risk import ResolvedInstrumentValues
@@ -71,6 +71,31 @@ def __init__(self,
7171
self.__id = None
7272
self.__quote_id = None
7373

74+
def _to_records(self):
75+
def get_name(obj, idx):
76+
if isinstance(obj, InstrumentBase) and hasattr(obj, 'type_'):
77+
type_name = obj.type_.name if isinstance(obj.type_, AssetType) else obj.type_
78+
else:
79+
type_name = 'Portfolio'
80+
return f'{type_name}_{idx}' if obj.name is None else obj.name
81+
82+
stack = [(None, self)]
83+
records = []
84+
while stack:
85+
temp_records = []
86+
parent, portfolio = stack.pop()
87+
current_record = {} if len(records) == 0 else records.pop(0)
88+
for idx, priceable in enumerate(portfolio.__priceables):
89+
path = parent + PortfolioPath(idx) if parent is not None else PortfolioPath(idx)
90+
priceable_name = get_name(priceable, idx)
91+
if isinstance(priceable, Portfolio):
92+
stack.insert(0, (path, priceable))
93+
temp_records.append({**current_record, f'portfolio_name_{len(path)-1}': priceable_name})
94+
else:
95+
temp_records.append({**current_record, 'instrument_name': priceable_name})
96+
records.extend(temp_records)
97+
return records
98+
7499
def __getitem__(self, item):
75100
if isinstance(item, (int, slice)):
76101
return self.__priceables[item]

gs_quant/risk/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def _to_records(self, extra_dict, display_options: DisplayOptions = None):
134134
options = display_options if display_options is not None else gs_quant.config.display_options
135135
show_na = options.show_na
136136

137-
return [{**extra_dict, 'value': self}] if show_na else [None]
137+
return [{**extra_dict, 'value': self}] if show_na else []
138138

139139

140140
class ScalarWithInfo(ResultInfo, metaclass=ABCMeta):
@@ -322,7 +322,7 @@ def _to_records(self, extra_dict, display_options: DisplayOptions = None):
322322
options = display_options if display_options is not None else gs_quant.config.display_options
323323
show_na = options.show_na
324324

325-
return [{**extra_dict, 'value': None}] if show_na else [None]
325+
return [{**extra_dict, 'value': None}] if show_na else []
326326

327327
return [dict(item, **{**extra_dict}) for item in self.raw_value.to_dict('records')]
328328

gs_quant/risk/results.py

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020
from concurrent.futures import Future
2121
from itertools import chain
2222
from typing import Any, Iterable, Mapping, Optional, Tuple, Union
23+
import weakref
2324

2425
import pandas as pd
2526
from gs_quant.base import Priceable, RiskKey, Sentinel, InstrumentBase, is_instance_or_iterable, is_iterable
2627
from gs_quant.common import RiskMeasure
2728
from gs_quant.config import DisplayOptions
2829
from gs_quant.risk import DataFrameWithInfo, ErrorValue, FloatWithInfo, SeriesWithInfo, ResultInfo, \
2930
ScalarWithInfo, aggregate_results
30-
from gs_quant.target.common import AssetType
31-
from more_itertools import unique_everseen
3231

3332
_logger = logging.getLogger(__name__)
3433

@@ -128,8 +127,13 @@ class PricingFuture(Future):
128127

129128
def __init__(self, result: Optional[Any] = __RESULT_SENTINEL):
130129
super().__init__()
130+
self.__pricing_context = None
131+
131132
if result is not self.__RESULT_SENTINEL:
132133
self.set_result(result)
134+
else:
135+
from gs_quant.markets import PricingContext
136+
self.__pricing_context = weakref.ref(PricingContext.current.active_context)
133137

134138
def __add__(self, other):
135139
if isinstance(other, (int, float)):
@@ -162,9 +166,10 @@ def result(self, timeout=None):
162166
163167
Exception: If the call raised then that exception will be raised.
164168
"""
165-
from gs_quant.markets import PricingContext
166-
if not self.done() and PricingContext.current.active_context.is_entered:
167-
raise RuntimeError('Cannot evaluate results under the same pricing context being used to produce them')
169+
if not self.done():
170+
pricing_context = self.__pricing_context() if self.__pricing_context else None
171+
if pricing_context is not None and pricing_context.is_entered:
172+
raise RuntimeError('Cannot evaluate results under the same pricing context being used to produce them')
168173

169174
return super().result(timeout=timeout)
170175

@@ -595,51 +600,26 @@ def aggregate(self, allow_mismatch_risk_keys=False) -> Union[float, pd.DataFrame
595600
else:
596601
return aggregate_results(self.__results(), allow_mismatch_risk_keys=allow_mismatch_risk_keys)
597602

603+
def _to_records(self, display_options: DisplayOptions = None):
604+
def get_records(rec):
605+
temp = []
606+
for f in rec.futures:
607+
if isinstance(f.result(), ResultInfo) or isinstance(f.result(), MultipleRiskMeasureResult):
608+
temp.append(f.result())
609+
else:
610+
temp.extend(get_records(f.result()))
611+
return temp
612+
613+
future_records = get_records(self)
614+
portfolio_records = self.__portfolio._to_records()
615+
records = []
616+
if len(future_records) == len(portfolio_records):
617+
for i in range(len(future_records)):
618+
records.extend(future_records[i]._to_records({**portfolio_records[i]}, display_options))
619+
return records
620+
598621
def to_frame(self, values='default', index='default', columns='default', aggfunc=sum,
599622
display_options: DisplayOptions = None):
600-
def get_name(obj, idx):
601-
if isinstance(obj, InstrumentBase) and hasattr(obj, 'type'):
602-
type_name = obj.type.name if isinstance(obj.type, AssetType) else obj.type_
603-
else:
604-
type_name = 'Portfolio'
605-
return f'{type_name}_{idx}' if obj.name is None else obj.name
606-
607-
def get_df(priceable):
608-
portfolio_paths = list(unique_everseen(p.path[:-1] for p in priceable.all_paths if len(p.path) > 1))
609-
inst_paths = [p.path for p in priceable.all_paths if len(p.path) == 1]
610-
final_records = []
611-
if len(portfolio_paths):
612-
for portfolio_path in portfolio_paths:
613-
curr_res = self.__result(PortfolioPath(portfolio_path))
614-
portfolio_info = {}
615-
for p_idx, p in enumerate(portfolio_path):
616-
portfolio = self.__result(PortfolioPath(portfolio_path[:p_idx + 1])).__portfolio
617-
portfolio_info[f'portfolio_name_{p_idx}'] = get_name(portfolio, p)
618-
619-
records = list(chain.from_iterable((i._to_records(
620-
{**portfolio_info, 'instrument_name': get_name(curr_res.__portfolio[slice_idx], slice_idx)},
621-
display_options) for slice_idx, i in enumerate(curr_res))))
622-
623-
records = list(filter(lambda x: x is not None, records))
624-
for rec_idx, rec in enumerate(records):
625-
if 'risk_measure' not in rec:
626-
records[rec_idx]['risk_measure'] = curr_res.risk_measures[0]
627-
628-
final_records.extend(records)
629-
630-
if len(inst_paths):
631-
for path in inst_paths:
632-
final_records.extend(self.__result(PortfolioPath(path))._to_records(
633-
{'instrument_name': get_name(priceable[path[0]], path[0])}, display_options))
634-
635-
final_records = list(filter(lambda x: x is not None, final_records))
636-
637-
if len(final_records) > 0:
638-
final_df = pd.DataFrame.from_records(final_records)
639-
if 'risk_measure' not in final_df.columns.values:
640-
final_df['risk_measure'] = self.risk_measures[0]
641-
return final_df
642-
643623
def get_default_pivots(ori_cols, has_dates: bool, multi_measures: bool, simple_port: bool) -> tuple:
644624
portfolio_names = list(filter(lambda x: 'portfolio_name_' in x, ori_cols))
645625
port_and_inst_names = portfolio_names + ['instrument_name']
@@ -668,9 +648,12 @@ def match(rule_value, check_value) -> bool:
668648
return rule_output
669649
return None, None, None
670650

671-
ori_df = get_df(self.portfolio)
672-
673-
if ori_df is None:
651+
final_records = self._to_records(display_options=display_options)
652+
if len(final_records) > 0:
653+
ori_df = pd.DataFrame.from_records(final_records)
654+
if 'risk_measure' not in ori_df.columns.values:
655+
ori_df['risk_measure'] = self.risk_measures[0]
656+
else:
674657
return
675658
df_cols = list(ori_df.columns.values)
676659
# fill n/a values for different sub-portfolio depths

gs_quant/session.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
under the License.
1515
"""
1616

17-
1817
from abc import abstractmethod
1918
import backoff
2019
import certifi
@@ -437,14 +436,26 @@ def __init__(self, environment_or_domain: str, api_version: str = API_VERSION,
437436
class PassThroughGSSSOSession(KerberosSessionMixin, GsSession):
438437

439438
def __init__(self, environment: str, token, api_version=API_VERSION,
440-
application=DEFAULT_APPLICATION, http_adapter=None):
439+
application=DEFAULT_APPLICATION, http_adapter=None, csrf_token=None):
441440
domain, verify = self.domain_and_verify(environment)
442441
GsSession.__init__(self, domain, api_version=api_version, application=application, verify=verify,
443442
http_adapter=http_adapter)
444443

445444
self.token = token
445+
self.csrf_token = csrf_token
446446

447447
def _authenticate(self):
448-
self._handle_cookies(self.token)
448+
if not (self.token and self.csrf_token):
449+
self._handle_cookies(self.token)
450+
return
451+
452+
cookie = requests.cookies.create_cookie(domain='.gs.com', name='GSSSO', value=self.token)
453+
self._session.cookies.set_cookie(cookie)
454+
if self.csrf_token:
455+
cookie = requests.cookies.create_cookie(domain='.gs.com', name='MARQUEE-CSRF-TOKEN',
456+
value=self.csrf_token)
457+
self._session.cookies.set_cookie(cookie)
458+
self._session.headers.update({'X-MARQUEE-CSRF-TOKEN': self.csrf_token})
459+
449460
except ModuleNotFoundError:
450461
pass

gs_quant/target/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3984,6 +3984,7 @@ class RiskMeasureType(EnumBase, Enum):
39843984
Description = 'Description'
39853985
Dollar_Price = 'Dollar Price'
39863986
DV01 = 'DV01'
3987+
FairPremium = 'FairPremium'
39873988
Fair_Price = 'Fair Price'
39883989
FairVarStrike = 'FairVarStrike'
39893990
FairVolStrike = 'FairVolStrike'
@@ -5703,6 +5704,7 @@ class RiskRequest(Base):
57035704
parameters: Optional[RiskRequestParameters] = field(default=None, metadata=field_metadata)
57045705
request_visible_to_gs: Optional[bool] = field(default=False, metadata=field_metadata)
57055706
use_cache: Optional[bool] = field(default=False, metadata=field_metadata)
5707+
priority: Optional[int] = field(default=None, metadata=field_metadata)
57065708
name: Optional[str] = field(default=None, metadata=name_metadata)
57075709

57085710

gs_quant/target/measures.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,16 @@ def __call__(self, aggregation_level=None, bump_size=None, currency=None, fin
260260
EqAnnualImpliedVol = RiskMeasure(name="EqAnnualImpliedVol", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Annual Implied Volatility"), unit=RiskMeasureUnit("Percent"))
261261
EqAnnualImpliedVol.__doc__ = "Equity Annual Implied Volatility (%)"
262262

263-
EqDelta = RiskMeasure(name="EqDelta", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Delta"))
264-
EqDelta.__doc__ = "Change in Dollar Price (USD present value) due to individual $1 moves in the spot price of underlying equity security"
263+
EqDelta = RiskMeasureWithCurrencyParameter(name="EqDelta", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Delta"))
264+
EqDelta.__doc__ = "Change in Dollar Price (USD present value) due to individual 1% move in the spot price of underlying equity security"
265265

266-
EqGamma = RiskMeasure(name="EqGamma", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Gamma"))
267-
EqGamma.__doc__ = "Change in EqDelta for a $1 shift in the price of the underlying equity security"
266+
EqGamma = RiskMeasureWithCurrencyParameter(name="EqGamma", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Gamma"))
267+
EqGamma.__doc__ = "Change in EqDelta for a 1% move in the price of the underlying equity security"
268268

269269
EqSpot = RiskMeasure(name="EqSpot", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Spot"))
270270
EqSpot.__doc__ = "Equity Spot"
271271

272-
EqVega = RiskMeasure(name="EqVega", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Vega"))
272+
EqVega = RiskMeasureWithCurrencyParameter(name="EqVega", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Vega"))
273273
EqVega.__doc__ = "Change in Dollar Price (USD present value) due to individual 1bp moves in the implied volatility of the underlying equity security"
274274

275275
FXAnnualATMImpliedVol = RiskMeasure(name="FXAnnualATMImpliedVol", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Annual ATM Implied Volatility"), unit=RiskMeasureUnit("Percent"))
@@ -332,6 +332,9 @@ def __call__(self, aggregation_level=None, bump_size=None, currency=None, fin
332332
FXVega = RiskMeasureWithFiniteDifferenceParameter(name="FXVega", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Vega"))
333333
FXVega.__doc__ = "Change in Dollar Price due to a 1 vol move in the implied volatility of ATM instruments used to build the volatility surface"
334334

335+
FairPremium = RiskMeasureWithCurrencyParameter(name="FairPremium", measure_type=RiskMeasureType("FairPremium"))
336+
FairPremium.__doc__ = "Fair Premium is the instrument present value discounted to the premium settlement date"
337+
335338
FairPrice = RiskMeasure(name="FairPrice", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Fair Price"))
336339
FairPrice.__doc__ = "FairPrice"
337340

0 commit comments

Comments
 (0)